Skip to content

Commit d339aee

Browse files
awesterbalambkylebarronkevinjqliu
authored
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 #724 (comment) 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 <kevinjqliu@users.noreply.github.com> --------- Co-authored-by: Andrew Lamb <andrew@nerdnetworks.org> Co-authored-by: Kyle Barron <kyle@developmentseed.org> Co-authored-by: Kyle Barron <kylebarron2@gmail.com> Co-authored-by: Kevin Liu <kevinjqliu@users.noreply.github.com>
1 parent f554e60 commit d339aee

32 files changed

Lines changed: 268 additions & 117 deletions

.github/workflows/ci.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,26 @@ jobs:
5757
run: cargo clippy --features azure -- -D warnings
5858
- name: Run clippy with http feature
5959
run: cargo clippy --features http -- -D warnings
60+
- name: Run clippy with aws-base feature
61+
run: cargo clippy --no-default-features --features aws-base -- -D warnings
62+
- name: Run clippy with azure-base feature
63+
run: cargo clippy --no-default-features --features azure-base -- -D warnings
64+
- name: Run clippy with gcp-base feature
65+
run: cargo clippy --no-default-features --features gcp-base -- -D warnings
66+
- name: Run clippy with http-base feature
67+
run: cargo clippy --no-default-features --features http-base -- -D warnings
68+
- name: Verify base features do not enable reqwest
69+
run: |
70+
if cargo tree \
71+
--no-default-features \
72+
--features aws-base,azure-base,gcp-base,http-base \
73+
--edges normal \
74+
--prefix none \
75+
-f '{p}' \
76+
| grep -E '^reqwest v'; then
77+
echo "reqwest must not be enabled by *-base features"
78+
exit 1
79+
fi
6080
- name: Run clippy with integration feature
6181
run: cargo clippy --no-default-features --features integration -- -D warnings
6282
- name: Run clippy with all features
@@ -196,6 +216,8 @@ jobs:
196216
run: rustup target add wasm32-wasip1
197217
- name: Build wasm32-wasip1
198218
run: cargo build --all-features --target wasm32-wasip1
219+
- name: Build wasm32-wasip1 without reqwest
220+
run: cargo build --no-default-features --features aws-base --target wasm32-wasip1
199221
- name: Install wasm-pack
200222
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
201223
- uses: actions/setup-node@v6

Cargo.toml

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,35 @@ futures-channel = {version = "0.3", features = ["sink"]}
8282

8383
[features]
8484
default = ["fs"]
85-
cloud = ["serde", "serde_json", "quick-xml", "hyper", "reqwest", "reqwest/stream", "chrono/serde", "base64", "rand", "ring", "http-body-util", "form_urlencoded", "serde_urlencoded", "tokio"]
86-
azure = ["cloud", "httparse"]
85+
86+
# Shared cloud/provider implementation dependencies.
87+
# This intentionally does NOT include reqwest.
88+
cloud-base = ["serde", "serde_json", "quick-xml", "hyper", "chrono/serde", "base64", "rand", "ring", "http-body-util", "form_urlencoded", "serde_urlencoded", "tokio"]
89+
90+
# Built-in reqwest-based HTTP transport.
91+
reqwest = ["dep:reqwest", "reqwest/stream"]
92+
93+
# Compatibility/convenience alias.
94+
# Historically, cloud meant "common cloud support", including reqwest.
95+
# Keep that behavior for existing users.
96+
cloud = ["cloud-base", "reqwest"]
97+
98+
# Provider base features.
99+
# These compile provider logic without forcing reqwest.
100+
azure-base = ["cloud-base", "httparse"]
101+
gcp-base = ["cloud-base", "rustls-pki-types"]
102+
aws-base = ["cloud-base", "crc-fast", "md-5"]
103+
http-base = ["cloud-base"]
104+
105+
# Batteries-included provider features.
106+
# These preserve existing behavior: enabling aws/azure/gcp/http gives
107+
# the default reqwest transport.
108+
azure = ["azure-base", "reqwest"]
109+
gcp = ["gcp-base", "reqwest"]
110+
aws = ["aws-base", "reqwest"]
111+
http = ["http-base", "reqwest"]
112+
87113
fs = ["walkdir", "tokio", "nix", "windows-sys"]
88-
gcp = ["cloud", "rustls-pki-types"]
89-
aws = ["cloud", "crc-fast", "md-5"]
90-
http = ["cloud"]
91114
tls-webpki-roots = ["reqwest?/rustls-tls-webpki-roots"]
92115
integration = ["rand", "tokio"]
93116
tokio = ["dep:tokio", "dep:tracing"]

src/aws/builder.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ use crate::config::ConfigValue;
2929
use crate::{ClientConfigKey, ClientOptions, Result, RetryConfig, StaticCredentialProvider};
3030
use base64::Engine;
3131
use base64::prelude::BASE64_STANDARD;
32+
use http::header::{HeaderMap, HeaderValue};
3233
use itertools::Itertools;
3334
use md5::{Digest, Md5};
34-
use reqwest::header::{HeaderMap, HeaderValue};
3535
use serde::{Deserialize, Serialize};
3636
use std::str::FromStr;
3737
use std::sync::Arc;
@@ -1573,6 +1573,7 @@ mod tests {
15731573
assert!(builder.unsigned_payload.get().unwrap());
15741574
}
15751575

1576+
#[cfg(feature = "reqwest")]
15761577
#[test]
15771578
fn s3_test_endpoint_url_s3_config() {
15781579
// Verify aws_endpoint_url_s3 parses to S3Endpoint config key
@@ -1687,6 +1688,7 @@ mod tests {
16871688
);
16881689
}
16891690

1691+
#[cfg(feature = "reqwest")]
16901692
#[test]
16911693
fn s3_default_region() {
16921694
let builder = AmazonS3Builder::new()
@@ -1696,6 +1698,7 @@ mod tests {
16961698
assert_eq!(builder.client.config.region, "us-east-1");
16971699
}
16981700

1701+
#[cfg(feature = "reqwest")]
16991702
#[test]
17001703
fn s3_test_bucket_endpoint() {
17011704
let builder = AmazonS3Builder::new()
@@ -1795,6 +1798,7 @@ mod tests {
17951798
}
17961799
}
17971800

1801+
#[cfg(feature = "reqwest")]
17981802
#[tokio::test]
17991803
async fn s3_test_proxy_url() {
18001804
let s3 = AmazonS3Builder::new()
@@ -1823,6 +1827,7 @@ mod tests {
18231827
assert_eq!("Generic HTTP client error: builder error", err);
18241828
}
18251829

1830+
#[cfg(feature = "reqwest")]
18261831
#[test]
18271832
fn test_invalid_config() {
18281833
let err = AmazonS3Builder::new()
@@ -1865,6 +1870,7 @@ mod tests {
18651870
);
18661871
}
18671872

1873+
#[cfg(feature = "reqwest")]
18681874
#[test]
18691875
fn test_request_payer_config() {
18701876
let s3 = AmazonS3Builder::new()
@@ -1938,6 +1944,7 @@ mod tests {
19381944
}
19391945
}
19401946

1947+
#[cfg(feature = "reqwest")]
19411948
#[test]
19421949
fn test_builder_eks_with_config() {
19431950
let builder = AmazonS3Builder::new()
@@ -1960,6 +1967,7 @@ mod tests {
19601967
);
19611968
}
19621969

1970+
#[cfg(feature = "reqwest")]
19631971
#[test]
19641972
fn test_builder_web_identity_with_config() {
19651973
let builder = AmazonS3Builder::new()

src/aws/client.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1041,6 +1041,7 @@ mod tests {
10411041
use hyper::Request;
10421042
use hyper::body::Incoming;
10431043

1044+
#[cfg(feature = "reqwest")]
10441045
#[tokio::test]
10451046
async fn test_create_multipart_has_content_length() {
10461047
let mock = MockServer::new().await;
@@ -1138,6 +1139,7 @@ mod tests {
11381139
}
11391140
}
11401141

1142+
#[cfg(feature = "reqwest")]
11411143
#[tokio::test]
11421144
async fn test_default_headers_signed_request() {
11431145
let mock = MockServer::new().await;
@@ -1162,6 +1164,7 @@ mod tests {
11621164
mock.shutdown().await;
11631165
}
11641166

1167+
#[cfg(feature = "reqwest")]
11651168
#[tokio::test]
11661169
async fn test_default_headers_signed_bulk_delete() {
11671170
let mock = MockServer::new().await;
@@ -1338,6 +1341,7 @@ mod tests {
13381341
mock.shutdown().await;
13391342
}
13401343

1344+
#[cfg(feature = "reqwest")]
13411345
#[tokio::test]
13421346
async fn test_default_headers_signed_get_request() {
13431347
let mock = MockServer::new().await;
@@ -1360,6 +1364,7 @@ mod tests {
13601364
mock.shutdown().await;
13611365
}
13621366

1367+
#[cfg(feature = "reqwest")]
13631368
#[tokio::test]
13641369
async fn test_default_headers_signed_complete_multipart() {
13651370
let mock = MockServer::new().await;

src/aws/credential.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -865,11 +865,13 @@ mod tests {
865865
use crate::aws::{AmazonS3Builder, AmazonS3ConfigKey};
866866
use crate::client::HttpClient;
867867
use crate::client::mock_server::MockServer;
868-
use http::Response;
869-
use reqwest::{Client, Method};
868+
use http::{Method, Response};
869+
#[cfg(feature = "reqwest")]
870+
use reqwest::Client;
870871
use std::env;
871872

872873
// Test generated using https://docs.aws.amazon.com/general/latest/gr/sigv4-signed-request-examples.html
874+
#[cfg(feature = "reqwest")]
873875
#[test]
874876
fn test_sign_with_signed_payload() {
875877
let client = HttpClient::new(Client::new());
@@ -914,6 +916,7 @@ mod tests {
914916
)
915917
}
916918

919+
#[cfg(feature = "reqwest")]
917920
#[test]
918921
fn test_sign_with_signed_payload_request_payer() {
919922
let client = HttpClient::new(Client::new());
@@ -958,6 +961,7 @@ mod tests {
958961
)
959962
}
960963

964+
#[cfg(feature = "reqwest")]
961965
#[test]
962966
fn test_sign_with_unsigned_payload() {
963967
let client = HttpClient::new(Client::new());
@@ -1085,6 +1089,7 @@ mod tests {
10851089
);
10861090
}
10871091

1092+
#[cfg(feature = "reqwest")]
10881093
#[test]
10891094
fn test_sign_port() {
10901095
let client = HttpClient::new(Client::new());
@@ -1128,6 +1133,7 @@ mod tests {
11281133
)
11291134
}
11301135

1136+
#[cfg(feature = "reqwest")]
11311137
#[tokio::test]
11321138
async fn test_instance_metadata() {
11331139
if env::var("TEST_INTEGRATION").is_err() {
@@ -1166,6 +1172,7 @@ mod tests {
11661172
assert!(!token.is_empty())
11671173
}
11681174

1175+
#[cfg(feature = "reqwest")]
11691176
#[tokio::test]
11701177
async fn test_mock() {
11711178
let server = MockServer::new().await;
@@ -1259,6 +1266,7 @@ mod tests {
12591266
.unwrap_err();
12601267
}
12611268

1269+
#[cfg(feature = "reqwest")]
12621270
#[tokio::test]
12631271
async fn test_eks_pod_credential_provider() {
12641272
use crate::client::mock_server::MockServer;

src/aws/mod.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@
3131
use async_trait::async_trait;
3232
use futures_util::stream::BoxStream;
3333
use futures_util::{StreamExt, TryStreamExt};
34-
use reqwest::header::{HeaderName, IF_MATCH, IF_NONE_MATCH};
35-
use reqwest::{Method, StatusCode};
34+
use http::header::{HeaderName, IF_MATCH, IF_NONE_MATCH};
35+
use http::{Method, StatusCode};
3636
use std::{sync::Arc, time::Duration};
3737
use url::Url;
3838

@@ -58,14 +58,14 @@ mod client;
5858
mod credential;
5959
mod precondition;
6060

61-
#[cfg(not(target_arch = "wasm32"))]
61+
#[cfg(all(feature = "reqwest", not(target_arch = "wasm32")))]
6262
mod resolve;
6363

6464
pub use builder::{AmazonS3Builder, AmazonS3ConfigKey};
6565
pub use checksum::Checksum;
6666
pub use precondition::{S3ConditionalPut, S3CopyIfNotExists};
6767

68-
#[cfg(not(target_arch = "wasm32"))]
68+
#[cfg(all(feature = "reqwest", not(target_arch = "wasm32")))]
6969
pub use resolve::resolve_bucket_region;
7070

7171
/// This struct is used to maintain the URI path encoding
@@ -118,7 +118,7 @@ impl Signer for AmazonS3 {
118118
/// ```
119119
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
120120
/// # use object_store::{aws::AmazonS3Builder, path::Path, signer::Signer};
121-
/// # use reqwest::Method;
121+
/// # use http::Method;
122122
/// # use std::time::Duration;
123123
/// #
124124
/// let region = "us-east-1";
@@ -534,6 +534,7 @@ mod tests {
534534
use super::*;
535535
use crate::ClientOptions;
536536
use crate::ObjectStoreExt;
537+
#[cfg(feature = "reqwest")]
537538
use crate::client::SpawnedReqwestConnector;
538539
use crate::client::get::GetClient;
539540
use crate::client::retry::RetryContext;
@@ -946,6 +947,7 @@ mod tests {
946947

947948
/// Integration test that ensures I/O is done on an alternate threadpool
948949
/// when using the `SpawnedReqwestConnector`.
950+
#[cfg(feature = "reqwest")]
949951
#[test]
950952
fn s3_alternate_threadpool_spawned_request_connector() {
951953
maybe_skip_integration!();

src/aws/precondition.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ pub enum S3CopyIfNotExists {
4444
/// other than 412.
4545
///
4646
/// Encoded as `header-with-status:<HEADER_NAME>:<HEADER_VALUE>:<STATUS>` ignoring whitespace
47-
HeaderWithStatus(String, String, reqwest::StatusCode),
47+
HeaderWithStatus(String, String, http::StatusCode),
4848
/// Native Amazon S3 supports copy if not exists through a multipart upload
4949
/// where the upload copies an existing object and is completed only if the
5050
/// new object does not already exist.
@@ -180,7 +180,7 @@ mod tests {
180180
let expected = Some(S3CopyIfNotExists::HeaderWithStatus(
181181
"key".to_owned(),
182182
"value".to_owned(),
183-
reqwest::StatusCode::FORBIDDEN,
183+
http::StatusCode::FORBIDDEN,
184184
));
185185

186186
assert_eq!(expected, S3CopyIfNotExists::from_str(input));
@@ -212,7 +212,7 @@ mod tests {
212212
let expected = Some(S3CopyIfNotExists::HeaderWithStatus(
213213
"key".to_owned(),
214214
"value".to_owned(),
215-
reqwest::StatusCode::FORBIDDEN,
215+
http::StatusCode::FORBIDDEN,
216216
));
217217

218218
const INPUTS: &[&str] = &[

src/aws/resolve.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ impl From<Error> for crate::Error {
4747
///
4848
/// [HeadBucket API]: https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadBucket.html
4949
pub async fn resolve_bucket_region(bucket: &str, client_options: &ClientOptions) -> Result<String> {
50-
use reqwest::StatusCode;
50+
use http::StatusCode;
5151

5252
let endpoint = format!("https://{bucket}.s3.amazonaws.com");
5353

src/azure/client.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1385,6 +1385,7 @@ mod tests {
13851385
quick_xml::de::from_str(S).unwrap();
13861386
}
13871387

1388+
#[cfg(feature = "reqwest")]
13881389
#[tokio::test]
13891390
async fn test_build_bulk_delete_body() {
13901391
let credential_provider = Arc::new(StaticCredentialProvider::new(

src/azure/credential.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1075,6 +1075,7 @@ mod tests {
10751075
use crate::client::mock_server::MockServer;
10761076
use crate::{ObjectStoreExt, Path};
10771077

1078+
#[cfg(feature = "reqwest")]
10781079
#[tokio::test]
10791080
async fn test_managed_identity() {
10801081
let server = MockServer::new().await;
@@ -1133,6 +1134,7 @@ mod tests {
11331134
);
11341135
}
11351136

1137+
#[cfg(feature = "reqwest")]
11361138
#[tokio::test]
11371139
async fn test_workload_identity() {
11381140
let server = MockServer::new().await;
@@ -1185,6 +1187,7 @@ mod tests {
11851187
);
11861188
}
11871189

1190+
#[cfg(feature = "reqwest")]
11881191
#[tokio::test]
11891192
async fn test_no_credentials() {
11901193
let server = MockServer::new().await;
@@ -1218,6 +1221,7 @@ mod tests {
12181221
}
12191222
}
12201223

1224+
#[cfg(feature = "reqwest")]
12211225
#[tokio::test]
12221226
async fn test_fabric_refresh_expired_token() {
12231227
let server = MockServer::new().await;

0 commit comments

Comments
 (0)