This is an automated email from the ASF dual-hosted git repository.

kylebarron pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-rs-object-store.git


The following commit(s) were added to refs/heads/main by this push:
     new d339aee  Make `reqwest` optional (#724)
d339aee is described below

commit d339aee4b7a8d63eff9df3599ceeced2d4ce03c3
Author: Bram Westerbaan <[email protected]>
AuthorDate: Fri Jun 12 19:07:42 2026 +0200

    Make `reqwest` optional (#724)
    
    * makes reqwest optional
    
    * refactor!: make aws/azure/gcp/http depend on reqwest, not cloud
    
    The role of cloud has been taken over by reqwest + cloud-base.
    
    Implements 
https://github.com/apache/arrow-rs-object-store/pull/724#discussion_r3342155266
    
    BREAKING CHANGE: according to cargo semver-checks: aws/azure/gcp/http no 
longer enable the cloud feature.
    
    * document feature flags exactly as kevinjqliu proposed
    
    * switch from `feature="cloud"` to `feature="reqwest"`
    
    * Apply suggestion from @kevinjqliu
    
    Co-authored-by: Kevin Liu <[email protected]>
    
    ---------
    
    Co-authored-by: Andrew Lamb <[email protected]>
    Co-authored-by: Kyle Barron <[email protected]>
    Co-authored-by: Kyle Barron <[email protected]>
    Co-authored-by: Kevin Liu <[email protected]>
---
 .github/workflows/ci.yml      | 22 ++++++++++++++
 Cargo.toml                    | 33 +++++++++++++++++----
 src/aws/builder.rs            | 10 ++++++-
 src/aws/client.rs             |  5 ++++
 src/aws/credential.rs         | 12 ++++++--
 src/aws/mod.rs                | 12 ++++----
 src/aws/precondition.rs       |  6 ++--
 src/aws/resolve.rs            |  2 +-
 src/azure/client.rs           |  1 +
 src/azure/credential.rs       |  4 +++
 src/azure/mod.rs              |  4 +--
 src/client/builder.rs         | 22 +++++++++-----
 src/client/get.rs             |  6 ++--
 src/client/header.rs          |  6 ++--
 src/client/http/body.rs       |  6 ++--
 src/client/http/connection.rs | 69 ++++++++++++++++++++++++++++++++++++-------
 src/client/http/spawn.rs      |  2 +-
 src/client/mod.rs             | 40 +++++++++++++------------
 src/client/retry.rs           | 13 +++++---
 src/client/s3.rs              |  2 +-
 src/client/token.rs           |  2 +-
 src/config.rs                 |  2 +-
 src/gcp/builder.rs            |  7 +++++
 src/gcp/mod.rs                |  1 +
 src/http/client.rs            |  2 +-
 src/integration.rs            |  4 +--
 src/lib.rs                    | 38 +++++++++++++-----------
 src/parse.rs                  | 24 ++++++++-------
 src/prefix.rs                 |  6 ++--
 src/signer.rs                 |  2 +-
 src/util.rs                   | 12 ++++----
 tests/http.rs                 |  8 ++---
 32 files changed, 268 insertions(+), 117 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 50041ee..c14df42 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -57,6 +57,26 @@ jobs:
         run: cargo clippy --features azure -- -D warnings
       - name: Run clippy with http feature
         run: cargo clippy --features http -- -D warnings
+      - name: Run clippy with aws-base feature
+        run: cargo clippy --no-default-features --features aws-base -- -D 
warnings
+      - name: Run clippy with azure-base feature
+        run: cargo clippy --no-default-features --features azure-base -- -D 
warnings
+      - name: Run clippy with gcp-base feature
+        run: cargo clippy --no-default-features --features gcp-base -- -D 
warnings
+      - name: Run clippy with http-base feature
+        run: cargo clippy --no-default-features --features http-base -- -D 
warnings
+      - name: Verify base features do not enable reqwest
+        run: |
+          if cargo tree \
+            --no-default-features \
+            --features aws-base,azure-base,gcp-base,http-base \
+            --edges normal \
+            --prefix none \
+            -f '{p}' \
+            | grep -E '^reqwest v'; then
+            echo "reqwest must not be enabled by *-base features"
+            exit 1
+          fi
       - name: Run clippy with integration feature
         run: cargo clippy --no-default-features --features integration -- -D 
warnings
       - name: Run clippy with all features
@@ -196,6 +216,8 @@ jobs:
         run: rustup target add wasm32-wasip1
       - name: Build wasm32-wasip1
         run: cargo build --all-features --target wasm32-wasip1
+      - name: Build wasm32-wasip1 without reqwest
+        run: cargo build --no-default-features --features aws-base --target 
wasm32-wasip1
       - name: Install wasm-pack
         run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf 
| sh
       - uses: actions/setup-node@v6
diff --git a/Cargo.toml b/Cargo.toml
index fa7dfa0..ddb4336 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -82,12 +82,35 @@ futures-channel = {version = "0.3", features = ["sink"]}
 
 [features]
 default = ["fs"]
-cloud = ["serde", "serde_json", "quick-xml", "hyper", "reqwest", 
"reqwest/stream", "chrono/serde", "base64", "rand", "ring", "http-body-util", 
"form_urlencoded", "serde_urlencoded", "tokio"]
-azure = ["cloud", "httparse"]
+
+# Shared cloud/provider implementation dependencies.
+# This intentionally does NOT include reqwest.
+cloud-base = ["serde", "serde_json", "quick-xml", "hyper", "chrono/serde", 
"base64", "rand", "ring", "http-body-util", "form_urlencoded", 
"serde_urlencoded", "tokio"]
+
+# Built-in reqwest-based HTTP transport.
+reqwest = ["dep:reqwest", "reqwest/stream"]
+
+# Compatibility/convenience alias.
+# Historically, cloud meant "common cloud support", including reqwest.
+# Keep that behavior for existing users.
+cloud = ["cloud-base", "reqwest"]
+
+# Provider base features.
+# These compile provider logic without forcing reqwest.
+azure-base = ["cloud-base", "httparse"]
+gcp-base = ["cloud-base", "rustls-pki-types"]
+aws-base = ["cloud-base", "crc-fast", "md-5"]
+http-base = ["cloud-base"]
+
+# Batteries-included provider features.
+# These preserve existing behavior: enabling aws/azure/gcp/http gives
+# the default reqwest transport.
+azure = ["azure-base", "reqwest"]
+gcp = ["gcp-base", "reqwest"]
+aws = ["aws-base", "reqwest"]
+http = ["http-base", "reqwest"]
+
 fs = ["walkdir", "tokio", "nix", "windows-sys"]
-gcp = ["cloud", "rustls-pki-types"]
-aws = ["cloud", "crc-fast", "md-5"]
-http = ["cloud"]
 tls-webpki-roots = ["reqwest?/rustls-tls-webpki-roots"]
 integration = ["rand", "tokio"]
 tokio = ["dep:tokio", "dep:tracing"]
diff --git a/src/aws/builder.rs b/src/aws/builder.rs
index 5a6d2b4..c9fd6e3 100644
--- a/src/aws/builder.rs
+++ b/src/aws/builder.rs
@@ -29,9 +29,9 @@ use crate::config::ConfigValue;
 use crate::{ClientConfigKey, ClientOptions, Result, RetryConfig, 
StaticCredentialProvider};
 use base64::Engine;
 use base64::prelude::BASE64_STANDARD;
+use http::header::{HeaderMap, HeaderValue};
 use itertools::Itertools;
 use md5::{Digest, Md5};
-use reqwest::header::{HeaderMap, HeaderValue};
 use serde::{Deserialize, Serialize};
 use std::str::FromStr;
 use std::sync::Arc;
@@ -1573,6 +1573,7 @@ mod tests {
         assert!(builder.unsigned_payload.get().unwrap());
     }
 
+    #[cfg(feature = "reqwest")]
     #[test]
     fn s3_test_endpoint_url_s3_config() {
         // Verify aws_endpoint_url_s3 parses to S3Endpoint config key
@@ -1687,6 +1688,7 @@ mod tests {
         );
     }
 
+    #[cfg(feature = "reqwest")]
     #[test]
     fn s3_default_region() {
         let builder = AmazonS3Builder::new()
@@ -1696,6 +1698,7 @@ mod tests {
         assert_eq!(builder.client.config.region, "us-east-1");
     }
 
+    #[cfg(feature = "reqwest")]
     #[test]
     fn s3_test_bucket_endpoint() {
         let builder = AmazonS3Builder::new()
@@ -1795,6 +1798,7 @@ mod tests {
         }
     }
 
+    #[cfg(feature = "reqwest")]
     #[tokio::test]
     async fn s3_test_proxy_url() {
         let s3 = AmazonS3Builder::new()
@@ -1823,6 +1827,7 @@ mod tests {
         assert_eq!("Generic HTTP client error: builder error", err);
     }
 
+    #[cfg(feature = "reqwest")]
     #[test]
     fn test_invalid_config() {
         let err = AmazonS3Builder::new()
@@ -1865,6 +1870,7 @@ mod tests {
         );
     }
 
+    #[cfg(feature = "reqwest")]
     #[test]
     fn test_request_payer_config() {
         let s3 = AmazonS3Builder::new()
@@ -1938,6 +1944,7 @@ mod tests {
         }
     }
 
+    #[cfg(feature = "reqwest")]
     #[test]
     fn test_builder_eks_with_config() {
         let builder = AmazonS3Builder::new()
@@ -1960,6 +1967,7 @@ mod tests {
         );
     }
 
+    #[cfg(feature = "reqwest")]
     #[test]
     fn test_builder_web_identity_with_config() {
         let builder = AmazonS3Builder::new()
diff --git a/src/aws/client.rs b/src/aws/client.rs
index d6b2534..f72155f 100644
--- a/src/aws/client.rs
+++ b/src/aws/client.rs
@@ -1041,6 +1041,7 @@ mod tests {
     use hyper::Request;
     use hyper::body::Incoming;
 
+    #[cfg(feature = "reqwest")]
     #[tokio::test]
     async fn test_create_multipart_has_content_length() {
         let mock = MockServer::new().await;
@@ -1138,6 +1139,7 @@ mod tests {
         }
     }
 
+    #[cfg(feature = "reqwest")]
     #[tokio::test]
     async fn test_default_headers_signed_request() {
         let mock = MockServer::new().await;
@@ -1162,6 +1164,7 @@ mod tests {
         mock.shutdown().await;
     }
 
+    #[cfg(feature = "reqwest")]
     #[tokio::test]
     async fn test_default_headers_signed_bulk_delete() {
         let mock = MockServer::new().await;
@@ -1338,6 +1341,7 @@ mod tests {
         mock.shutdown().await;
     }
 
+    #[cfg(feature = "reqwest")]
     #[tokio::test]
     async fn test_default_headers_signed_get_request() {
         let mock = MockServer::new().await;
@@ -1360,6 +1364,7 @@ mod tests {
         mock.shutdown().await;
     }
 
+    #[cfg(feature = "reqwest")]
     #[tokio::test]
     async fn test_default_headers_signed_complete_multipart() {
         let mock = MockServer::new().await;
diff --git a/src/aws/credential.rs b/src/aws/credential.rs
index 170c3e4..df2ea67 100644
--- a/src/aws/credential.rs
+++ b/src/aws/credential.rs
@@ -865,11 +865,13 @@ mod tests {
     use crate::aws::{AmazonS3Builder, AmazonS3ConfigKey};
     use crate::client::HttpClient;
     use crate::client::mock_server::MockServer;
-    use http::Response;
-    use reqwest::{Client, Method};
+    use http::{Method, Response};
+    #[cfg(feature = "reqwest")]
+    use reqwest::Client;
     use std::env;
 
     // Test generated using 
https://docs.aws.amazon.com/general/latest/gr/sigv4-signed-request-examples.html
+    #[cfg(feature = "reqwest")]
     #[test]
     fn test_sign_with_signed_payload() {
         let client = HttpClient::new(Client::new());
@@ -914,6 +916,7 @@ mod tests {
         )
     }
 
+    #[cfg(feature = "reqwest")]
     #[test]
     fn test_sign_with_signed_payload_request_payer() {
         let client = HttpClient::new(Client::new());
@@ -958,6 +961,7 @@ mod tests {
         )
     }
 
+    #[cfg(feature = "reqwest")]
     #[test]
     fn test_sign_with_unsigned_payload() {
         let client = HttpClient::new(Client::new());
@@ -1085,6 +1089,7 @@ mod tests {
         );
     }
 
+    #[cfg(feature = "reqwest")]
     #[test]
     fn test_sign_port() {
         let client = HttpClient::new(Client::new());
@@ -1128,6 +1133,7 @@ mod tests {
         )
     }
 
+    #[cfg(feature = "reqwest")]
     #[tokio::test]
     async fn test_instance_metadata() {
         if env::var("TEST_INTEGRATION").is_err() {
@@ -1166,6 +1172,7 @@ mod tests {
         assert!(!token.is_empty())
     }
 
+    #[cfg(feature = "reqwest")]
     #[tokio::test]
     async fn test_mock() {
         let server = MockServer::new().await;
@@ -1259,6 +1266,7 @@ mod tests {
             .unwrap_err();
     }
 
+    #[cfg(feature = "reqwest")]
     #[tokio::test]
     async fn test_eks_pod_credential_provider() {
         use crate::client::mock_server::MockServer;
diff --git a/src/aws/mod.rs b/src/aws/mod.rs
index 765871a..42d84a4 100644
--- a/src/aws/mod.rs
+++ b/src/aws/mod.rs
@@ -31,8 +31,8 @@
 use async_trait::async_trait;
 use futures_util::stream::BoxStream;
 use futures_util::{StreamExt, TryStreamExt};
-use reqwest::header::{HeaderName, IF_MATCH, IF_NONE_MATCH};
-use reqwest::{Method, StatusCode};
+use http::header::{HeaderName, IF_MATCH, IF_NONE_MATCH};
+use http::{Method, StatusCode};
 use std::{sync::Arc, time::Duration};
 use url::Url;
 
@@ -58,14 +58,14 @@ mod client;
 mod credential;
 mod precondition;
 
-#[cfg(not(target_arch = "wasm32"))]
+#[cfg(all(feature = "reqwest", not(target_arch = "wasm32")))]
 mod resolve;
 
 pub use builder::{AmazonS3Builder, AmazonS3ConfigKey};
 pub use checksum::Checksum;
 pub use precondition::{S3ConditionalPut, S3CopyIfNotExists};
 
-#[cfg(not(target_arch = "wasm32"))]
+#[cfg(all(feature = "reqwest", not(target_arch = "wasm32")))]
 pub use resolve::resolve_bucket_region;
 
 /// This struct is used to maintain the URI path encoding
@@ -118,7 +118,7 @@ impl Signer for AmazonS3 {
     /// ```
     /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
     /// # use object_store::{aws::AmazonS3Builder, path::Path, signer::Signer};
-    /// # use reqwest::Method;
+    /// # use http::Method;
     /// # use std::time::Duration;
     /// #
     /// let region = "us-east-1";
@@ -534,6 +534,7 @@ mod tests {
     use super::*;
     use crate::ClientOptions;
     use crate::ObjectStoreExt;
+    #[cfg(feature = "reqwest")]
     use crate::client::SpawnedReqwestConnector;
     use crate::client::get::GetClient;
     use crate::client::retry::RetryContext;
@@ -946,6 +947,7 @@ mod tests {
 
     /// Integration test that ensures I/O is done on an alternate threadpool
     /// when using the `SpawnedReqwestConnector`.
+    #[cfg(feature = "reqwest")]
     #[test]
     fn s3_alternate_threadpool_spawned_request_connector() {
         maybe_skip_integration!();
diff --git a/src/aws/precondition.rs b/src/aws/precondition.rs
index b4ae938..51b8089 100644
--- a/src/aws/precondition.rs
+++ b/src/aws/precondition.rs
@@ -44,7 +44,7 @@ pub enum S3CopyIfNotExists {
     /// other than 412.
     ///
     /// Encoded as `header-with-status:<HEADER_NAME>:<HEADER_VALUE>:<STATUS>` 
ignoring whitespace
-    HeaderWithStatus(String, String, reqwest::StatusCode),
+    HeaderWithStatus(String, String, http::StatusCode),
     /// Native Amazon S3 supports copy if not exists through a multipart upload
     /// where the upload copies an existing object and is completed only if the
     /// new object does not already exist.
@@ -180,7 +180,7 @@ mod tests {
         let expected = Some(S3CopyIfNotExists::HeaderWithStatus(
             "key".to_owned(),
             "value".to_owned(),
-            reqwest::StatusCode::FORBIDDEN,
+            http::StatusCode::FORBIDDEN,
         ));
 
         assert_eq!(expected, S3CopyIfNotExists::from_str(input));
@@ -212,7 +212,7 @@ mod tests {
         let expected = Some(S3CopyIfNotExists::HeaderWithStatus(
             "key".to_owned(),
             "value".to_owned(),
-            reqwest::StatusCode::FORBIDDEN,
+            http::StatusCode::FORBIDDEN,
         ));
 
         const INPUTS: &[&str] = &[
diff --git a/src/aws/resolve.rs b/src/aws/resolve.rs
index 66d1511..a09ea53 100644
--- a/src/aws/resolve.rs
+++ b/src/aws/resolve.rs
@@ -47,7 +47,7 @@ impl From<Error> for crate::Error {
 ///
 /// [HeadBucket API]: 
https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadBucket.html
 pub async fn resolve_bucket_region(bucket: &str, client_options: 
&ClientOptions) -> Result<String> {
-    use reqwest::StatusCode;
+    use http::StatusCode;
 
     let endpoint = format!("https://{bucket}.s3.amazonaws.com";);
 
diff --git a/src/azure/client.rs b/src/azure/client.rs
index fd3e923..5a3fcbc 100644
--- a/src/azure/client.rs
+++ b/src/azure/client.rs
@@ -1385,6 +1385,7 @@ mod tests {
             quick_xml::de::from_str(S).unwrap();
     }
 
+    #[cfg(feature = "reqwest")]
     #[tokio::test]
     async fn test_build_bulk_delete_body() {
         let credential_provider = Arc::new(StaticCredentialProvider::new(
diff --git a/src/azure/credential.rs b/src/azure/credential.rs
index 1fbb101..4e84fb8 100644
--- a/src/azure/credential.rs
+++ b/src/azure/credential.rs
@@ -1075,6 +1075,7 @@ mod tests {
     use crate::client::mock_server::MockServer;
     use crate::{ObjectStoreExt, Path};
 
+    #[cfg(feature = "reqwest")]
     #[tokio::test]
     async fn test_managed_identity() {
         let server = MockServer::new().await;
@@ -1133,6 +1134,7 @@ mod tests {
         );
     }
 
+    #[cfg(feature = "reqwest")]
     #[tokio::test]
     async fn test_workload_identity() {
         let server = MockServer::new().await;
@@ -1185,6 +1187,7 @@ mod tests {
         );
     }
 
+    #[cfg(feature = "reqwest")]
     #[tokio::test]
     async fn test_no_credentials() {
         let server = MockServer::new().await;
@@ -1218,6 +1221,7 @@ mod tests {
         }
     }
 
+    #[cfg(feature = "reqwest")]
     #[tokio::test]
     async fn test_fabric_refresh_expired_token() {
         let server = MockServer::new().await;
diff --git a/src/azure/mod.rs b/src/azure/mod.rs
index 6d7b642..6c4968d 100644
--- a/src/azure/mod.rs
+++ b/src/azure/mod.rs
@@ -33,7 +33,7 @@ use crate::{
 };
 use async_trait::async_trait;
 use futures_util::stream::{BoxStream, StreamExt, TryStreamExt};
-use reqwest::Method;
+use http::Method;
 use std::fmt::Debug;
 use std::sync::Arc;
 use std::time::Duration;
@@ -197,7 +197,7 @@ impl Signer for MicrosoftAzure {
     /// ```
     /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
     /// # use object_store::{azure::MicrosoftAzureBuilder, path::Path, 
signer::Signer};
-    /// # use reqwest::Method;
+    /// # use http::Method;
     /// # use std::time::Duration;
     /// #
     /// let azure = MicrosoftAzureBuilder::new()
diff --git a/src/client/builder.rs b/src/client/builder.rs
index f74c5ec..fd0a719 100644
--- a/src/client/builder.rs
+++ b/src/client/builder.rs
@@ -63,7 +63,7 @@ impl HttpRequestBuilder {
         }
     }
 
-    #[cfg(any(feature = "aws", feature = "azure"))]
+    #[cfg(any(feature = "aws-base", feature = "azure-base"))]
     pub(crate) fn from_parts(client: HttpClient, request: HttpRequest) -> Self 
{
         Self {
             client,
@@ -116,7 +116,7 @@ impl HttpRequestBuilder {
         self
     }
 
-    #[cfg(feature = "aws")]
+    #[cfg(feature = "aws-base")]
     pub(crate) fn headers(mut self, headers: http::HeaderMap) -> Self {
         use http::header::{Entry, OccupiedEntry};
 
@@ -151,7 +151,7 @@ impl HttpRequestBuilder {
         self
     }
 
-    #[cfg(feature = "gcp")]
+    #[cfg(feature = "gcp-base")]
     pub(crate) fn bearer_auth(mut self, token: &str) -> Self {
         let value = HeaderValue::try_from(format!("Bearer {token}"));
         match (value, &mut self.request) {
@@ -165,7 +165,7 @@ impl HttpRequestBuilder {
         self
     }
 
-    #[cfg(feature = "gcp")]
+    #[cfg(feature = "gcp-base")]
     pub(crate) fn json<S: serde::Serialize>(mut self, s: S) -> Self {
         match (serde_json::to_vec(&s), &mut self.request) {
             (Ok(json), Ok(request)) => {
@@ -177,7 +177,12 @@ impl HttpRequestBuilder {
         self
     }
 
-    #[cfg(any(test, feature = "aws", feature = "gcp", feature = "azure"))]
+    #[cfg(any(
+        test,
+        feature = "aws-base",
+        feature = "gcp-base",
+        feature = "azure-base"
+    ))]
     pub(crate) fn query<T: serde::Serialize + ?Sized>(mut self, query: &T) -> 
Self {
         let mut error = None;
         if let Ok(ref mut req) = self.request {
@@ -205,7 +210,7 @@ impl HttpRequestBuilder {
         self
     }
 
-    #[cfg(any(feature = "gcp", feature = "azure"))]
+    #[cfg(any(feature = "gcp-base", feature = "azure-base"))]
     pub(crate) fn form<T: serde::Serialize>(mut self, form: T) -> Self {
         let mut error = None;
         if let Ok(ref mut req) = self.request {
@@ -226,7 +231,7 @@ impl HttpRequestBuilder {
         self
     }
 
-    #[cfg(any(feature = "aws", feature = "gcp", feature = "azure"))]
+    #[cfg(any(feature = "aws-base", feature = "gcp-base", feature = 
"azure-base"))]
     pub(crate) fn body(mut self, b: impl Into<HttpRequestBody>) -> Self {
         if let Ok(r) = &mut self.request {
             *r.body_mut() = b.into();
@@ -239,7 +244,7 @@ impl HttpRequestBuilder {
     }
 }
 
-#[cfg(any(test, feature = "azure"))]
+#[cfg(any(test, feature = "azure-base"))]
 pub(crate) fn add_query_pairs<I, K, V>(uri: &mut Uri, query_pairs: I)
 where
     I: IntoIterator,
@@ -300,6 +305,7 @@ mod tests {
         assert_eq!(uri.to_string(), "https://[email protected]/?foo=1";);
     }
 
+    #[cfg(feature = "reqwest")]
     #[test]
     fn test_request_builder_query() {
         let client = HttpClient::new(reqwest::Client::new());
diff --git a/src/client/get.rs b/src/client/get.rs
index a9de711..c7ec36d 100644
--- a/src/client/get.rs
+++ b/src/client/get.rs
@@ -28,12 +28,12 @@ use bytes::Bytes;
 use futures_util::StreamExt;
 use futures_util::stream::BoxStream;
 use http::StatusCode;
+use http::header::ToStrError;
 use http::header::{
     CACHE_CONTROL, CONTENT_DISPOSITION, CONTENT_ENCODING, CONTENT_LANGUAGE, 
CONTENT_RANGE,
     CONTENT_TYPE,
 };
 use http_body_util::BodyExt;
-use reqwest::header::ToStrError;
 use std::ops::Range;
 use std::sync::Arc;
 use tracing::info;
@@ -523,7 +523,7 @@ mod tests {
         );
     }
 }
-#[cfg(all(test, feature = "http", not(target_arch = "wasm32")))]
+#[cfg(all(test, feature = "http-base", not(target_arch = "wasm32")))]
 mod http_tests {
     use crate::client::mock_server::MockServer;
     use crate::client::{HttpError, HttpErrorKind, HttpResponseBody};
@@ -590,6 +590,7 @@ mod http_tests {
         }
     }
 
+    #[cfg(feature = "reqwest")]
     #[tokio::test]
     async fn test_stream_retry() {
         let mock = MockServer::new().await;
@@ -815,6 +816,7 @@ mod http_tests {
         );
     }
 
+    #[cfg(feature = "reqwest")]
     #[tokio::test]
     async fn test_retry_validate_content_range() {
         let mock = MockServer::new().await;
diff --git a/src/client/header.rs b/src/client/header.rs
index d0dd27a..58cc3a8 100644
--- a/src/client/header.rs
+++ b/src/client/header.rs
@@ -49,7 +49,7 @@ pub(crate) enum Error {
     MissingEtag,
 
     #[error("Received header containing non-ASCII data")]
-    BadHeader { source: reqwest::header::ToStrError },
+    BadHeader { source: http::header::ToStrError },
 
     #[error("Last-Modified Header missing from response")]
     MissingLastModified,
@@ -73,7 +73,7 @@ pub(crate) enum Error {
 /// Extracts a PutResult from the provided response
 ///
 /// Propagates the extensions of the response into the [`crate::PutResult`]
-#[cfg(any(feature = "aws", feature = "gcp", feature = "azure"))]
+#[cfg(any(feature = "aws-base", feature = "gcp-base", feature = "azure-base"))]
 pub(crate) fn get_put_result(
     response: crate::client::HttpResponse,
     version: &str,
@@ -89,7 +89,7 @@ pub(crate) fn get_put_result(
 }
 
 /// Extracts a optional version from the provided [`HeaderMap`]
-#[cfg(any(feature = "aws", feature = "gcp", feature = "azure"))]
+#[cfg(any(feature = "aws-base", feature = "gcp-base", feature = "azure-base"))]
 pub(crate) fn get_version(headers: &HeaderMap, version: &str) -> 
Result<Option<String>, Error> {
     Ok(match headers.get(version) {
         Some(x) => Some(
diff --git a/src/client/http/body.rs b/src/client/http/body.rs
index e22ccea..554b0cf 100644
--- a/src/client/http/body.rs
+++ b/src/client/http/body.rs
@@ -39,7 +39,7 @@ impl HttpRequestBody {
         Self(Inner::Bytes(Bytes::new()))
     }
 
-    #[cfg(not(target_arch = "wasm32"))]
+    #[cfg(all(feature = "reqwest", not(target_arch = "wasm32")))]
     pub(crate) fn into_reqwest(self) -> reqwest::Body {
         match self.0 {
             Inner::Bytes(b) => b.into(),
@@ -49,7 +49,7 @@ impl HttpRequestBody {
         }
     }
 
-    #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
+    #[cfg(all(feature = "reqwest", target_arch = "wasm32", target_os = 
"unknown"))]
     pub(crate) fn into_reqwest(self) -> reqwest::Body {
         match self.0 {
             Inner::Bytes(b) => b.into(),
@@ -196,7 +196,7 @@ impl HttpResponseBody {
         String::from_utf8(b.into()).map_err(|e| 
HttpError::new(HttpErrorKind::Decode, e))
     }
 
-    #[cfg(any(feature = "aws", feature = "gcp", feature = "azure"))]
+    #[cfg(any(feature = "aws-base", feature = "gcp-base", feature = 
"azure-base"))]
     pub(crate) async fn json<B: serde::de::DeserializeOwned>(self) -> 
Result<B, HttpError> {
         let b = self.bytes().await?;
         serde_json::from_slice(&b).map_err(|e| 
HttpError::new(HttpErrorKind::Decode, e))
diff --git a/src/client/http/connection.rs b/src/client/http/connection.rs
index 69c8436..c7cd2c1 100644
--- a/src/client/http/connection.rs
+++ b/src/client/http/connection.rs
@@ -16,13 +16,17 @@
 // under the License.
 
 use crate::ClientOptions;
+#[cfg(feature = "reqwest")]
+use crate::client::HttpResponseBody;
 use crate::client::builder::{HttpRequestBuilder, RequestBuilderError};
-use crate::client::{HttpRequest, HttpResponse, HttpResponseBody};
+use crate::client::{HttpRequest, HttpResponse};
 use async_trait::async_trait;
 use http::{Method, Uri};
+#[cfg(feature = "reqwest")]
 use http_body_util::BodyExt;
 use std::error::Error;
 use std::sync::Arc;
+#[cfg(all(feature = "reqwest", not(target_arch = "wasm32")))]
 use tokio::runtime::Handle;
 
 /// An HTTP protocol error
@@ -83,6 +87,7 @@ impl HttpError {
         }
     }
 
+    #[cfg(feature = "reqwest")]
     pub(crate) fn reqwest(e: reqwest::Error) -> Self {
         #[cfg(not(target_arch = "wasm32"))]
         let is_connect = || e.is_connect();
@@ -208,7 +213,7 @@ impl HttpClient {
 }
 
 #[async_trait]
-#[cfg(not(target_arch = "wasm32"))]
+#[cfg(all(feature = "reqwest", not(target_arch = "wasm32")))]
 impl HttpService for reqwest::Client {
     async fn call(&self, req: HttpRequest) -> Result<HttpResponse, HttpError> {
         let (parts, body) = req.into_parts();
@@ -228,7 +233,7 @@ impl HttpService for reqwest::Client {
 }
 
 #[async_trait]
-#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
+#[cfg(all(feature = "reqwest", target_arch = "wasm32", target_os = "unknown"))]
 impl HttpService for reqwest::Client {
     async fn call(&self, req: HttpRequest) -> Result<HttpResponse, HttpError> {
         use futures_channel::{mpsc, oneshot};
@@ -288,10 +293,16 @@ pub trait HttpConnector: std::fmt::Debug + Send + Sync + 
'static {
 /// [`HttpConnector`] using [`reqwest::Client`]
 #[derive(Debug, Default)]
 #[allow(missing_copy_implementations)]
-#[cfg(not(all(target_arch = "wasm32", target_os = "wasi")))]
+#[cfg(all(
+    feature = "reqwest",
+    not(all(target_arch = "wasm32", target_os = "wasi"))
+))]
 pub struct ReqwestConnector {}
 
-#[cfg(not(all(target_arch = "wasm32", target_os = "wasi")))]
+#[cfg(all(
+    feature = "reqwest",
+    not(all(target_arch = "wasm32", target_os = "wasi"))
+))]
 impl HttpConnector for ReqwestConnector {
     fn connect(&self, options: &ClientOptions) -> crate::Result<HttpClient> {
         let client = options.client()?;
@@ -336,12 +347,12 @@ impl HttpConnector for ReqwestConnector {
 /// ```
 #[derive(Debug)]
 #[allow(missing_copy_implementations)]
-#[cfg(not(target_arch = "wasm32"))]
+#[cfg(all(feature = "reqwest", not(target_arch = "wasm32")))]
 pub struct SpawnedReqwestConnector {
     runtime: Handle,
 }
 
-#[cfg(not(target_arch = "wasm32"))]
+#[cfg(all(feature = "reqwest", not(target_arch = "wasm32")))]
 impl SpawnedReqwestConnector {
     /// Create a new [`SpawnedReqwestConnector`] with the provided [`Handle`] 
to
     /// a tokio [`Runtime`]
@@ -352,7 +363,7 @@ impl SpawnedReqwestConnector {
     }
 }
 
-#[cfg(not(target_arch = "wasm32"))]
+#[cfg(all(feature = "reqwest", not(target_arch = "wasm32")))]
 impl HttpConnector for SpawnedReqwestConnector {
     fn connect(&self, options: &ClientOptions) -> crate::Result<HttpClient> {
         let spawn_service = super::SpawnService::new(options.client()?, 
self.runtime.clone());
@@ -360,21 +371,39 @@ impl HttpConnector for SpawnedReqwestConnector {
     }
 }
 
-#[cfg(all(target_arch = "wasm32", target_os = "wasi"))]
+#[cfg(all(feature = "reqwest", target_arch = "wasm32", target_os = "wasi"))]
 pub(crate) fn http_connector(
     custom: Option<Arc<dyn HttpConnector>>,
 ) -> crate::Result<Arc<dyn HttpConnector>> {
     match custom {
         Some(x) => Ok(x),
         None => Err(crate::Error::NotSupported {
-            source: "WASI architectures must provide an HTTPConnector"
+            source: "reqwest is not supported on the WASI architecture; \
+                supply a custom HttpConnector via `.with_http_connector(...)`"
                 .to_string()
                 .into(),
         }),
     }
 }
 
-#[cfg(not(all(target_arch = "wasm32", target_os = "wasi")))]
+#[cfg(all(not(feature = "reqwest"), target_arch = "wasm32", target_os = 
"wasi"))]
+pub(crate) fn http_connector(
+    custom: Option<Arc<dyn HttpConnector>>,
+) -> crate::Result<Arc<dyn HttpConnector>> {
+    match custom {
+        Some(x) => Ok(x),
+        None => Err(crate::Error::NotSupported {
+            source: "WASI architectures must provide an HttpConnector"
+                .to_string()
+                .into(),
+        }),
+    }
+}
+
+#[cfg(all(
+    feature = "reqwest",
+    not(all(target_arch = "wasm32", target_os = "wasi"))
+))]
 pub(crate) fn http_connector(
     custom: Option<Arc<dyn HttpConnector>>,
 ) -> crate::Result<Arc<dyn HttpConnector>> {
@@ -383,3 +412,21 @@ pub(crate) fn http_connector(
         None => Ok(Arc::new(ReqwestConnector {})),
     }
 }
+
+#[cfg(all(
+    not(feature = "reqwest"),
+    not(all(target_arch = "wasm32", target_os = "wasi"))
+))]
+pub(crate) fn http_connector(
+    custom: Option<Arc<dyn HttpConnector>>,
+) -> crate::Result<Arc<dyn HttpConnector>> {
+    match custom {
+        Some(x) => Ok(x),
+        None => Err(crate::Error::NotSupported {
+            source: "no built-in HTTP transport: enable the `reqwest` feature \
+                or supply a custom HttpConnector via 
`.with_http_connector(...)`"
+                .to_string()
+                .into(),
+        }),
+    }
+}
diff --git a/src/client/http/spawn.rs b/src/client/http/spawn.rs
index 80f3a87..2750e83 100644
--- a/src/client/http/spawn.rs
+++ b/src/client/http/spawn.rs
@@ -125,7 +125,7 @@ impl Body for SpawnBody {
     }
 }
 
-#[cfg(not(target_arch = "wasm32"))]
+#[cfg(all(feature = "reqwest", not(target_arch = "wasm32")))]
 #[cfg(test)]
 mod tests {
     use super::*;
diff --git a/src/client/mod.rs b/src/client/mod.rs
index 5dceaf4..896fcb2 100644
--- a/src/client/mod.rs
+++ b/src/client/mod.rs
@@ -21,7 +21,7 @@
 
 pub(crate) mod backoff;
 
-#[cfg(not(target_arch = "wasm32"))]
+#[cfg(all(feature = "reqwest", not(target_arch = "wasm32")))]
 mod dns;
 
 #[cfg(not(target_arch = "wasm32"))]
@@ -30,44 +30,45 @@ pub(crate) mod mock_server;
 
 pub(crate) mod retry;
 
-#[cfg(any(feature = "aws", feature = "gcp", feature = "azure"))]
+#[cfg(any(feature = "aws-base", feature = "gcp-base", feature = "azure-base"))]
 pub(crate) mod pagination;
 
 pub(crate) mod get;
 
-#[cfg(any(feature = "aws", feature = "gcp", feature = "azure"))]
+#[cfg(any(feature = "aws-base", feature = "gcp-base", feature = "azure-base"))]
 pub(crate) mod list;
 
-#[cfg(any(feature = "aws", feature = "gcp", feature = "azure"))]
+#[cfg(any(feature = "aws-base", feature = "gcp-base", feature = "azure-base"))]
 pub(crate) mod token;
 
 pub(crate) mod header;
 
-#[cfg(any(feature = "aws", feature = "gcp"))]
+#[cfg(any(feature = "aws-base", feature = "gcp-base"))]
 pub(crate) mod s3;
 
 pub(crate) mod builder;
 mod http;
 
-#[cfg(any(feature = "aws", feature = "gcp", feature = "azure"))]
+#[cfg(any(feature = "aws-base", feature = "gcp-base", feature = "azure-base"))]
 pub(crate) mod parts;
 pub use http::*;
 
+use ::http::header::{HeaderMap, HeaderValue};
 use async_trait::async_trait;
-use reqwest::header::{HeaderMap, HeaderValue};
 use serde::{Deserialize, Serialize};
 use std::collections::HashMap;
 use std::str::FromStr;
 use std::sync::Arc;
 use std::time::Duration;
 
-#[cfg(not(target_arch = "wasm32"))]
+#[cfg(all(feature = "reqwest", not(target_arch = "wasm32")))]
 use reqwest::{NoProxy, Proxy};
 
 use crate::config::{ConfigValue, fmt_duration};
 use crate::path::Path;
 use crate::{GetOptions, Result};
 
+#[cfg(feature = "reqwest")]
 fn map_client_error(e: reqwest::Error) -> super::Error {
     super::Error::Generic {
         store: "HTTP client",
@@ -75,6 +76,7 @@ fn map_client_error(e: reqwest::Error) -> super::Error {
     }
 }
 
+#[cfg(feature = "reqwest")]
 static DEFAULT_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", 
env!("CARGO_PKG_VERSION"),);
 
 /// Configuration keys for [`ClientOptions`]
@@ -272,10 +274,10 @@ impl FromStr for ClientConfigKey {
 /// This is used to configure the client to trust a specific certificate. See
 /// [Self::from_pem] for an example
 #[derive(Debug, Clone)]
-#[cfg(not(target_arch = "wasm32"))]
+#[cfg(all(feature = "reqwest", not(target_arch = "wasm32")))]
 pub struct Certificate(reqwest::tls::Certificate);
 
-#[cfg(not(target_arch = "wasm32"))]
+#[cfg(all(feature = "reqwest", not(target_arch = "wasm32")))]
 impl Certificate {
     /// Create a `Certificate` from a PEM encoded certificate.
     ///
@@ -322,7 +324,7 @@ impl Certificate {
 #[derive(Debug, Clone)]
 pub struct ClientOptions {
     user_agent: Option<ConfigValue<HeaderValue>>,
-    #[cfg(not(target_arch = "wasm32"))]
+    #[cfg(all(feature = "reqwest", not(target_arch = "wasm32")))]
     root_certificates: Vec<Certificate>,
     content_type_map: HashMap<String, String>,
     default_content_type: Option<String>,
@@ -357,7 +359,7 @@ impl Default for ClientOptions {
         // we opt for a slightly higher default timeout of 30 seconds
         Self {
             user_agent: None,
-            #[cfg(not(target_arch = "wasm32"))]
+            #[cfg(all(feature = "reqwest", not(target_arch = "wasm32")))]
             root_certificates: Default::default(),
             content_type_map: Default::default(),
             default_content_type: None,
@@ -493,7 +495,7 @@ impl ClientOptions {
     ///
     /// This can be used to connect to a server that has a self-signed
     /// certificate for example.
-    #[cfg(not(target_arch = "wasm32"))]
+    #[cfg(all(feature = "reqwest", not(target_arch = "wasm32")))]
     pub fn with_root_certificate(mut self, certificate: Certificate) -> Self {
         self.root_certificates.push(certificate);
         self
@@ -787,14 +789,14 @@ impl ClientOptions {
     /// In particular:
     /// * Allows HTTP as metadata endpoints do not use TLS
     /// * Configures a low connection timeout to provide quick feedback if not 
present
-    #[cfg(any(feature = "aws", feature = "gcp", feature = "azure"))]
+    #[cfg(any(feature = "aws-base", feature = "gcp-base", feature = 
"azure-base"))]
     pub(crate) fn metadata_options(&self) -> Self {
         self.clone()
             .with_allow_http(true)
             .with_connect_timeout(Duration::from_secs(1))
     }
 
-    #[cfg(not(target_arch = "wasm32"))]
+    #[cfg(all(feature = "reqwest", not(target_arch = "wasm32")))]
     pub(crate) fn client(&self) -> Result<reqwest::Client> {
         let mut builder = reqwest::ClientBuilder::new();
 
@@ -894,7 +896,7 @@ impl ClientOptions {
             .map_err(map_client_error)
     }
 
-    #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
+    #[cfg(all(feature = "reqwest", target_arch = "wasm32", target_os = 
"unknown"))]
     pub(crate) fn client(&self) -> Result<reqwest::Client> {
         let mut builder = reqwest::ClientBuilder::new();
 
@@ -994,7 +996,7 @@ where
     }
 }
 
-#[cfg(any(feature = "aws", feature = "azure", feature = "gcp"))]
+#[cfg(any(feature = "aws-base", feature = "azure-base", feature = "gcp-base"))]
 mod cloud {
     use super::*;
     use crate::RetryConfig;
@@ -1020,7 +1022,7 @@ mod cloud {
         }
 
         /// Override the minimum remaining TTL for a cached token to be used
-        #[cfg(any(feature = "aws", feature = "gcp"))]
+        #[cfg(any(feature = "aws-base", feature = "gcp-base"))]
         pub(crate) fn with_min_ttl(mut self, min_ttl: Duration) -> Self {
             self.cache = self.cache.with_min_ttl(min_ttl);
             self
@@ -1051,7 +1053,7 @@ mod cloud {
 }
 
 use crate::client::builder::HttpRequestBuilder;
-#[cfg(any(feature = "aws", feature = "azure", feature = "gcp"))]
+#[cfg(any(feature = "aws-base", feature = "azure-base", feature = "gcp-base"))]
 pub(crate) use cloud::*;
 
 #[cfg(test)]
diff --git a/src/client/retry.rs b/src/client/retry.rs
index 45b73eb..c79012a 100644
--- a/src/client/retry.rs
+++ b/src/client/retry.rs
@@ -22,9 +22,9 @@ use crate::client::backoff::{Backoff, BackoffConfig};
 use crate::client::builder::HttpRequestBuilder;
 use crate::client::{HttpClient, HttpError, HttpErrorKind, HttpRequest, 
HttpResponse};
 use futures_util::future::BoxFuture;
+use http::StatusCode;
+use http::header::LOCATION;
 use http::{Method, Uri};
-use reqwest::StatusCode;
-use reqwest::header::LOCATION;
 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
 use std::time::{Duration, Instant};
 use tracing::info;
@@ -280,7 +280,7 @@ impl RetryableRequestBuilder {
     }
 
     /// Set whether this request should be retried on a 409 Conflict response.
-    #[cfg(feature = "aws")]
+    #[cfg(feature = "aws-base")]
     pub(crate) fn retry_on_conflict(mut self, retry_on_conflict: bool) -> Self 
{
         self.request.retry_on_conflict = retry_on_conflict;
         self
@@ -512,13 +512,15 @@ mod tests {
     use crate::client::mock_server::MockServer;
     use crate::client::retry::{RequestError, RetryContext, RetryExt, 
body_contains_error};
     use crate::client::{HttpClient, HttpError, HttpErrorKind, HttpResponse};
+    use http::Method;
     use http::StatusCode;
     use hyper::Response;
     use hyper::header::LOCATION;
     use hyper::server::conn::http1;
     use hyper::service::service_fn;
     use hyper_util::rt::TokioIo;
-    use reqwest::{Client, Method};
+    #[cfg(feature = "reqwest")]
+    use reqwest::Client;
     use std::convert::Infallible;
     use std::error::Error;
     use std::time::Duration;
@@ -539,6 +541,7 @@ mod tests {
         assert!(!body_contains_error(success_response));
     }
 
+    #[cfg(feature = "reqwest")]
     #[tokio::test]
     async fn test_retry() {
         let mock = MockServer::new().await;
@@ -846,6 +849,7 @@ mod tests {
         mock.shutdown().await
     }
 
+    #[cfg(feature = "reqwest")]
     #[tokio::test]
     async fn test_503_error_body_captured() {
         let mock = MockServer::new().await;
@@ -880,6 +884,7 @@ mod tests {
         mock.shutdown().await
     }
 
+    #[cfg(feature = "reqwest")]
     #[tokio::test]
     #[expect(
         deprecated,
diff --git a/src/client/s3.rs b/src/client/s3.rs
index 2c8fb63..1bf7636 100644
--- a/src/client/s3.rs
+++ b/src/client/s3.rs
@@ -93,7 +93,7 @@ pub(crate) struct InitiateMultipartUploadResult {
     pub upload_id: String,
 }
 
-#[cfg(feature = "aws")]
+#[cfg(feature = "aws-base")]
 #[derive(Debug, Deserialize)]
 #[serde(rename_all = "PascalCase")]
 pub(crate) struct CopyPartResult {
diff --git a/src/client/token.rs b/src/client/token.rs
index 5b680bd..147c77a 100644
--- a/src/client/token.rs
+++ b/src/client/token.rs
@@ -58,7 +58,7 @@ impl<T> Default for TokenCache<T> {
 
 impl<T: Clone + Send + Sync> TokenCache<T> {
     /// Override the minimum remaining TTL for a cached token to be used
-    #[cfg(any(feature = "aws", feature = "gcp"))]
+    #[cfg(any(feature = "aws-base", feature = "gcp-base"))]
     pub(crate) fn with_min_ttl(self, min_ttl: Duration) -> Self {
         Self { min_ttl, ..self }
     }
diff --git a/src/config.rs b/src/config.rs
index 29a389d..3707a36 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -18,8 +18,8 @@ use std::fmt::{Debug, Display, Formatter};
 use std::str::FromStr;
 use std::time::Duration;
 
+use http::header::HeaderValue;
 use humantime::{format_duration, parse_duration};
-use reqwest::header::HeaderValue;
 
 use crate::{Error, Result};
 
diff --git a/src/gcp/builder.rs b/src/gcp/builder.rs
index 82752b0..55cfe63 100644
--- a/src/gcp/builder.rs
+++ b/src/gcp/builder.rs
@@ -762,6 +762,7 @@ mod tests {
         }
     }
 
+    #[cfg(feature = "reqwest")]
     #[tokio::test]
     async fn gcs_test_proxy_url() {
         let mut tfile = NamedTempFile::new().unwrap();
@@ -798,6 +799,7 @@ mod tests {
         builder.parse_url("mailto://bucket/path";).unwrap_err();
     }
 
+    #[cfg(feature = "reqwest")]
     #[test]
     fn gcs_test_service_account_key_only() {
         let _ = GoogleCloudStorageBuilder::new()
@@ -807,6 +809,7 @@ mod tests {
             .unwrap();
     }
 
+    #[cfg(feature = "reqwest")]
     #[test]
     fn gcs_test_with_base_url() {
         let no_base_url = GoogleCloudStorageBuilder::new()
@@ -904,6 +907,7 @@ mod tests {
         }
     }
 
+    #[cfg(feature = "reqwest")]
     #[test]
     fn gcs_test_explicit_creds_skip_invalid_adc() {
         // Create a valid service account key file
@@ -932,6 +936,7 @@ mod tests {
         );
     }
 
+    #[cfg(feature = "reqwest")]
     #[test]
     fn gcs_test_explicit_creds_with_service_account_key_skip_invalid_adc() {
         // Create invalid ADC file with unsupported credential type
@@ -955,6 +960,7 @@ mod tests {
         );
     }
 
+    #[cfg(feature = "reqwest")]
     #[test]
     fn gcs_test_adc_error_propagated_without_explicit_creds() {
         // Create invalid ADC file with unsupported credential type
@@ -982,6 +988,7 @@ mod tests {
         );
     }
 
+    #[cfg(feature = "reqwest")]
     #[test]
     fn gcs_test_with_credentials_skip_invalid_adc() {
         use crate::StaticCredentialProvider;
diff --git a/src/gcp/mod.rs b/src/gcp/mod.rs
index 7dc22ad..3402057 100644
--- a/src/gcp/mod.rs
+++ b/src/gcp/mod.rs
@@ -347,6 +347,7 @@ mod test {
         response_extensions(&integration, test_multipart).await;
     }
 
+    #[cfg(feature = "reqwest")]
     #[tokio::test]
     #[ignore]
     async fn gcs_test_sign() {
diff --git a/src/http/client.rs b/src/http/client.rs
index 4375a79..580b725 100644
--- a/src/http/client.rs
+++ b/src/http/client.rs
@@ -30,8 +30,8 @@ use http::header::{
     CACHE_CONTROL, CONTENT_DISPOSITION, CONTENT_ENCODING, CONTENT_LANGUAGE, 
CONTENT_LENGTH,
     CONTENT_TYPE,
 };
+use http::{Method, StatusCode};
 use percent_encoding::percent_decode_str;
-use reqwest::{Method, StatusCode};
 use serde::Deserialize;
 use url::Url;
 
diff --git a/src/integration.rs b/src/integration.rs
index 8b435d3..bcd4dca 100644
--- a/src/integration.rs
+++ b/src/integration.rs
@@ -1438,7 +1438,7 @@ pub async fn list_with_offset_exclusivity(storage: 
&DynObjectStore) {
 }
 
 #[cfg(all(
-    feature = "cloud",
+    feature = "reqwest",
     not(all(target_arch = "wasm32", target_os = "wasi"))
 ))]
 mod marker {
@@ -1521,7 +1521,7 @@ mod marker {
 }
 
 #[cfg(all(
-    feature = "cloud",
+    feature = "reqwest",
     not(all(target_arch = "wasm32", target_os = "wasi"))
 ))]
 pub use marker::{MarkerHttpConnector, response_extensions};
diff --git a/src/lib.rs b/src/lib.rs
index cb286f0..48711bb 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -80,19 +80,19 @@
     doc = "* Local filesystem: [`LocalFileSystem`](local::LocalFileSystem)"
 )]
 #![cfg_attr(
-    feature = "gcp",
+    feature = "gcp-base",
     doc = "* [`gcp`]: [Google Cloud 
Storage](https://cloud.google.com/storage/) support. See 
[`GoogleCloudStorageBuilder`](gcp::GoogleCloudStorageBuilder)"
 )]
 #![cfg_attr(
-    feature = "aws",
+    feature = "aws-base",
     doc = "* [`aws`]: [Amazon S3](https://aws.amazon.com/s3/). See 
[`AmazonS3Builder`](aws::AmazonS3Builder)"
 )]
 #![cfg_attr(
-    feature = "azure",
+    feature = "azure-base",
     doc = "* [`azure`]: [Azure Blob 
Storage](https://azure.microsoft.com/en-gb/services/storage/blobs/). See 
[`MicrosoftAzureBuilder`](azure::MicrosoftAzureBuilder)"
 )]
 #![cfg_attr(
-    feature = "http",
+    feature = "http-base",
     doc = "* [`http`]: [HTTP/WebDAV 
Storage](https://datatracker.ietf.org/doc/html/rfc2518). See 
[`HttpBuilder`](http::HttpBuilder)"
 )]
 //!
@@ -537,18 +537,18 @@
 //!
 //! [`HttpConnector`]: client::HttpConnector
 
-#[cfg(feature = "aws")]
+#[cfg(feature = "aws-base")]
 pub mod aws;
-#[cfg(feature = "azure")]
+#[cfg(feature = "azure-base")]
 pub mod azure;
 #[cfg(feature = "tokio")]
 pub mod buffered;
 #[cfg(not(target_arch = "wasm32"))]
 pub mod chunked;
 pub mod delimited;
-#[cfg(feature = "gcp")]
+#[cfg(feature = "gcp-base")]
 pub mod gcp;
-#[cfg(feature = "http")]
+#[cfg(feature = "http-base")]
 pub mod http;
 #[cfg(feature = "tokio")]
 pub mod limit;
@@ -558,24 +558,28 @@ pub mod memory;
 pub mod path;
 pub mod prefix;
 pub mod registry;
-#[cfg(feature = "cloud")]
+#[cfg(feature = "cloud-base")]
 pub mod signer;
 #[cfg(feature = "tokio")]
 pub mod throttle;
 
-#[cfg(feature = "cloud")]
+#[cfg(feature = "cloud-base")]
 pub mod client;
 
-#[cfg(feature = "cloud")]
+#[cfg(feature = "cloud-base")]
 pub use client::{
     ClientConfigKey, ClientOptions, CredentialProvider, 
StaticCredentialProvider,
     backoff::BackoffConfig, retry::RetryConfig,
 };
 
-#[cfg(all(feature = "cloud", not(target_arch = "wasm32")))]
+#[cfg(all(
+    feature = "cloud-base",
+    feature = "reqwest",
+    not(target_arch = "wasm32")
+))]
 pub use client::Certificate;
 
-#[cfg(feature = "cloud")]
+#[cfg(feature = "cloud-base")]
 mod config;
 
 mod tags;
@@ -2213,12 +2217,12 @@ mod tests {
         store.list(Some(&path))
     }
 
-    #[cfg(any(feature = "azure", feature = "aws"))]
+    #[cfg(any(feature = "azure-base", feature = "aws-base"))]
     pub(crate) async fn signing<T>(integration: &T)
     where
         T: ObjectStore + signer::Signer,
     {
-        use reqwest::Method;
+        use ::http::Method;
         use std::time::Duration;
 
         let data = Bytes::from("hello world");
@@ -2236,7 +2240,7 @@ mod tests {
         assert_eq!(data, loaded);
     }
 
-    #[cfg(any(feature = "aws", feature = "azure"))]
+    #[cfg(any(feature = "aws-base", feature = "azure-base"))]
     pub(crate) async fn tagging<F, Fut>(storage: Arc<dyn ObjectStore>, 
validate: bool, get_tags: F)
     where
         F: Fn(Path) -> Fut + Send + Sync,
@@ -2409,7 +2413,7 @@ mod tests {
     }
 
     #[test]
-    #[cfg(feature = "http")]
+    #[cfg(feature = "http-base")]
     fn test_reexported_types() {
         // Test HeaderMap
         let mut headers = HeaderMap::new();
diff --git a/src/parse.rs b/src/parse.rs
index d316b2f..2265547 100644
--- a/src/parse.rs
+++ b/src/parse.rs
@@ -139,7 +139,7 @@ impl ObjectStoreScheme {
     }
 }
 
-#[cfg(feature = "cloud")]
+#[cfg(feature = "cloud-base")]
 macro_rules! builder_opts {
     ($builder:ty, $url:expr, $options:expr) => {{
         let builder = $options.into_iter().fold(
@@ -201,29 +201,29 @@ where
         #[cfg(all(feature = "fs", not(target_arch = "wasm32")))]
         ObjectStoreScheme::Local => Box::new(LocalFileSystem::new()) as _,
         ObjectStoreScheme::Memory => Box::new(InMemory::new()) as _,
-        #[cfg(feature = "aws")]
+        #[cfg(feature = "aws-base")]
         ObjectStoreScheme::AmazonS3 => {
             builder_opts!(crate::aws::AmazonS3Builder, url, _options)
         }
-        #[cfg(feature = "gcp")]
+        #[cfg(feature = "gcp-base")]
         ObjectStoreScheme::GoogleCloudStorage => {
             builder_opts!(crate::gcp::GoogleCloudStorageBuilder, url, _options)
         }
-        #[cfg(feature = "azure")]
+        #[cfg(feature = "azure-base")]
         ObjectStoreScheme::MicrosoftAzure => {
             builder_opts!(crate::azure::MicrosoftAzureBuilder, url, _options)
         }
-        #[cfg(feature = "http")]
+        #[cfg(feature = "http-base")]
         ObjectStoreScheme::Http => {
             let url = &url[..url::Position::BeforePath];
             builder_opts!(crate::http::HttpBuilder, url, _options)
         }
         #[cfg(not(all(
             feature = "fs",
-            feature = "aws",
-            feature = "azure",
-            feature = "gcp",
-            feature = "http",
+            feature = "aws-base",
+            feature = "azure-base",
+            feature = "gcp-base",
+            feature = "http-base",
             not(target_arch = "wasm32")
         )))]
         s => {
@@ -441,7 +441,11 @@ mod tests {
     }
 
     #[tokio::test]
-    #[cfg(all(feature = "http", not(target_arch = "wasm32")))]
+    #[cfg(all(
+        feature = "reqwest",
+        feature = "http-base",
+        not(target_arch = "wasm32")
+    ))]
     async fn test_url_http() {
         use crate::{ObjectStoreExt, client::mock_server::MockServer};
         use http::{Response, header::USER_AGENT};
diff --git a/src/prefix.rs b/src/prefix.rs
index 9b3cf19..2b94b48 100644
--- a/src/prefix.rs
+++ b/src/prefix.rs
@@ -22,7 +22,7 @@ use std::ops::Range;
 
 use crate::multipart::{MultipartStore, PartId};
 use crate::path::Path;
-#[cfg(feature = "cloud")]
+#[cfg(feature = "cloud-base")]
 use crate::signer::Signer;
 use crate::{
     CopyOptions, GetOptions, GetResult, ListResult, MultipartId, 
MultipartUpload, ObjectMeta,
@@ -237,7 +237,7 @@ impl<T: MultipartStore> MultipartStore for PrefixStore<T> {
     }
 }
 
-#[cfg(feature = "cloud")]
+#[cfg(feature = "cloud-base")]
 #[async_trait::async_trait]
 impl<T: Signer> Signer for PrefixStore<T> {
     async fn signed_url(
@@ -397,7 +397,7 @@ mod tests {
         multipart_race_condition(&store, true).await;
     }
 
-    #[cfg(feature = "cloud")]
+    #[cfg(feature = "cloud-base")]
     #[tokio::test]
     async fn signer() {
         #[derive(Debug)]
diff --git a/src/signer.rs b/src/signer.rs
index ab3c7f9..47f3baa 100644
--- a/src/signer.rs
+++ b/src/signer.rs
@@ -19,7 +19,7 @@
 
 use crate::{Result, path::Path};
 use async_trait::async_trait;
-use reqwest::Method;
+use http::Method;
 use std::{fmt, time::Duration};
 use url::Url;
 
diff --git a/src/util.rs b/src/util.rs
index 162c077..21c9f7a 100644
--- a/src/util.rs
+++ b/src/util.rs
@@ -25,11 +25,11 @@ use super::Result;
 use bytes::Bytes;
 use futures_util::{Stream, TryStreamExt, stream::StreamExt};
 
-#[cfg(any(feature = "azure", feature = "http"))]
+#[cfg(any(feature = "azure-base", feature = "http-base"))]
 pub(crate) static RFC1123_FMT: &str = "%a, %d %h %Y %T GMT";
 
 // deserialize dates according to rfc1123
-#[cfg(any(feature = "azure", feature = "http"))]
+#[cfg(any(feature = "azure-base", feature = "http-base"))]
 pub(crate) fn deserialize_rfc1123<'de, D>(
     deserializer: D,
 ) -> Result<chrono::DateTime<chrono::Utc>, D::Error>
@@ -42,7 +42,7 @@ where
     Ok(chrono::TimeZone::from_utc_datetime(&chrono::Utc, &naive))
 }
 
-#[cfg(any(feature = "aws", feature = "azure"))]
+#[cfg(any(feature = "aws-base", feature = "azure-base"))]
 pub(crate) fn hmac_sha256(secret: impl AsRef<[u8]>, bytes: impl AsRef<[u8]>) 
-> ring::hmac::Tag {
     let key = ring::hmac::Key::new(ring::hmac::HMAC_SHA256, secret.as_ref());
     ring::hmac::sign(&key, bytes.as_ref())
@@ -300,7 +300,7 @@ impl<T: RangeBounds<u64>> From<T> for GetRange {
 //
 // Do not URI-encode any of the unreserved characters that RFC 3986 defines:
 // A-Z, a-z, 0-9, hyphen ( - ), underscore ( _ ), period ( . ), and tilde ( ~ 
).
-#[cfg(any(feature = "aws", feature = "gcp"))]
+#[cfg(any(feature = "aws-base", feature = "gcp-base"))]
 pub(crate) const STRICT_ENCODE_SET: percent_encoding::AsciiSet = 
percent_encoding::NON_ALPHANUMERIC
     .remove(b'-')
     .remove(b'.')
@@ -308,14 +308,14 @@ pub(crate) const STRICT_ENCODE_SET: 
percent_encoding::AsciiSet = percent_encodin
     .remove(b'~');
 
 /// Computes the SHA256 digest of `body` returned as a hex encoded string
-#[cfg(any(feature = "aws", feature = "gcp"))]
+#[cfg(any(feature = "aws-base", feature = "gcp-base"))]
 pub(crate) fn hex_digest(bytes: &[u8]) -> String {
     let digest = ring::digest::digest(&ring::digest::SHA256, bytes);
     hex_encode(digest.as_ref())
 }
 
 /// Returns `bytes` as a lower-case hex encoded string
-#[cfg(any(feature = "aws", feature = "gcp"))]
+#[cfg(any(feature = "aws-base", feature = "gcp-base"))]
 pub(crate) fn hex_encode(bytes: &[u8]) -> String {
     use std::fmt::Write;
     let mut out = String::with_capacity(bytes.len() * 2);
diff --git a/tests/http.rs b/tests/http.rs
index f23ef74..d1aa930 100644
--- a/tests/http.rs
+++ b/tests/http.rs
@@ -17,16 +17,16 @@
 
 //! Tests the HTTP store implementation
 
-#[cfg(feature = "http")]
+#[cfg(feature = "http-base")]
 use object_store::{GetOptions, GetRange, ObjectStore, http::HttpBuilder, 
path::Path};
 
-#[cfg(all(feature = "http", target_arch = "wasm32", target_os = "unknown"))]
+#[cfg(all(feature = "http-base", target_arch = "wasm32", target_os = 
"unknown"))]
 use wasm_bindgen_test::*;
 
 /// Tests that even when reqwest has the `gzip` feature enabled, the HTTP store
 /// does not error on a missing `Content-Length` header.
 #[tokio::test]
-#[cfg(feature = "http")]
+#[cfg(all(feature = "http-base", feature = "reqwest"))]
 async fn test_http_store_gzip() {
     let http_store = HttpBuilder::new()
         
.with_url("https://raw.githubusercontent.com/apache/arrow-rs/refs/heads/main";)
@@ -42,7 +42,7 @@ async fn test_http_store_gzip() {
         .unwrap();
 }
 
-#[cfg(all(feature = "http", target_arch = "wasm32", target_os = "unknown"))]
+#[cfg(all(feature = "http-base", target_arch = "wasm32", target_os = 
"unknown"))]
 #[wasm_bindgen_test]
 async fn basic_wasm_get() {
     let http_store = HttpBuilder::new()

Reply via email to