This is an automated email from the ASF dual-hosted git repository.
mneumann 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 719a29d fix: support `AWS_REQUEST_PAYER=requester` (#669)
719a29d is described below
commit 719a29d0f6eca84268c61b0969d8dbe952e75e92
Author: Len Strnad <[email protected]>
AuthorDate: Tue Mar 31 00:54:36 2026 -0600
fix: support `AWS_REQUEST_PAYER=requester` (#669)
---
src/aws/builder.rs | 102 +++++++++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 95 insertions(+), 7 deletions(-)
diff --git a/src/aws/builder.rs b/src/aws/builder.rs
index 1b9204c..85dbaae 100644
--- a/src/aws/builder.rs
+++ b/src/aws/builder.rs
@@ -188,7 +188,7 @@ pub struct AmazonS3Builder {
/// base64-encoded 256-bit customer encryption key for SSE-C.
encryption_customer_key_base64: Option<String>,
/// When set to true, charge requester for bucket operations
- request_payer: ConfigValue<bool>,
+ request_payer: ConfigValue<RequesterPayer>,
/// The [`HttpConnector`] to use
http_connector: Option<Arc<dyn HttpConnector>>,
}
@@ -574,7 +574,7 @@ impl AmazonS3Builder {
/// * `AWS_CONTAINER_CREDENTIALS_FULL_URI` ->
<https://docs.aws.amazon.com/sdkref/latest/guide/feature-container-credentials.html>
/// * `AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE` ->
<https://docs.aws.amazon.com/sdkref/latest/guide/feature-container-credentials.html>
/// * `AWS_ALLOW_HTTP` -> set to "true" to permit HTTP connections without
TLS
- /// * `AWS_REQUEST_PAYER` -> set to "true" to permit operations on
requester-pays buckets.
+ /// * `AWS_REQUEST_PAYER` -> set to "requester" or "true" to permit
operations on requester-pays buckets.
///
/// # Example
/// ```
@@ -678,9 +678,7 @@ impl AmazonS3Builder {
AmazonS3ConfigKey::ConditionalPut => {
self.conditional_put = ConfigValue::Deferred(value.into())
}
- AmazonS3ConfigKey::RequestPayer => {
- self.request_payer = ConfigValue::Deferred(value.into())
- }
+ AmazonS3ConfigKey::RequestPayer => self.request_payer.parse(value),
AmazonS3ConfigKey::Encryption(key) => match key {
S3EncryptionConfigKey::ServerSideEncryption => {
self.encryption_type =
Some(ConfigValue::Deferred(value.into()))
@@ -1061,7 +1059,7 @@ impl AmazonS3Builder {
///
///
<https://docs.aws.amazon.com/AmazonS3/latest/userguide/RequesterPaysBuckets.html>
pub fn with_request_payer(mut self, enabled: bool) -> Self {
- self.request_payer = ConfigValue::Parsed(enabled);
+ self.request_payer = ConfigValue::Parsed(enabled.into());
self
}
@@ -1247,7 +1245,7 @@ impl AmazonS3Builder {
copy_if_not_exists,
conditional_put: self.conditional_put.get()?,
encryption_headers,
- request_payer: self.request_payer.get()?,
+ request_payer: self.request_payer.get()?.into(),
};
let http_client = http.connect(&config.client_options)?;
@@ -1267,6 +1265,40 @@ fn parse_bucket_az(bucket: &str) -> Option<&str> {
Some(base.rsplit_once("--")?.1)
}
+/// Captures `AWS_REQUEST_PAYER`.
+///
+/// Parses either as `"requester"` (case-insensitive) meaning `true`, or as a
[`bool`] (i.e. `"true"`, `"1"`, `"no"`, etc.).
+#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
+struct RequesterPayer(bool);
+
+impl crate::config::Parse for RequesterPayer {
+ fn parse(v: &str) -> Result<Self> {
+ if v.eq_ignore_ascii_case("requester") {
+ Ok(Self(true))
+ } else {
+ Ok(Self(<bool as crate::config::Parse>::parse(v)?))
+ }
+ }
+}
+
+impl From<bool> for RequesterPayer {
+ fn from(value: bool) -> Self {
+ Self(value)
+ }
+}
+
+impl From<RequesterPayer> for bool {
+ fn from(value: RequesterPayer) -> Self {
+ value.0
+ }
+}
+
+impl std::fmt::Display for RequesterPayer {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ self.0.fmt(f)
+ }
+}
+
/// Encryption configuration options for S3.
///
/// These options are used to configure server-side encryption for S3 objects.
@@ -1783,6 +1815,62 @@ mod tests {
err,
"Generic Config error: \"md5\" is not a valid checksum algorithm"
);
+
+ let err = AmazonS3Builder::new()
+ .with_config(AmazonS3ConfigKey::RequestPayer, "requestr")
+ .with_bucket_name("bucket")
+ .with_region("region")
+ .build()
+ .unwrap_err()
+ .to_string();
+
+ assert_eq!(
+ err,
+ "Generic Config error: failed to parse \"requestr\" as boolean"
+ );
+ }
+
+ #[test]
+ fn test_request_payer_config() {
+ let s3 = AmazonS3Builder::new()
+ .with_config(AmazonS3ConfigKey::RequestPayer, "requester")
+ .with_bucket_name("bucket")
+ .with_region("region")
+ .build()
+ .unwrap();
+ assert!(s3.client.config.request_payer);
+
+ let s3 = AmazonS3Builder::new()
+ .with_config(AmazonS3ConfigKey::RequestPayer, "REQUESTER")
+ .with_bucket_name("bucket")
+ .with_region("region")
+ .build()
+ .unwrap();
+ assert!(s3.client.config.request_payer);
+
+ let s3 = AmazonS3Builder::new()
+ .with_config(AmazonS3ConfigKey::RequestPayer, "true")
+ .with_bucket_name("bucket")
+ .with_region("region")
+ .build()
+ .unwrap();
+ assert!(s3.client.config.request_payer);
+
+ let s3 = AmazonS3Builder::new()
+ .with_config(AmazonS3ConfigKey::RequestPayer, "false")
+ .with_bucket_name("bucket")
+ .with_region("region")
+ .build()
+ .unwrap();
+ assert!(!s3.client.config.request_payer);
+
+ let s3 = AmazonS3Builder::new()
+ .with_request_payer(true)
+ .with_bucket_name("bucket")
+ .with_region("region")
+ .build()
+ .unwrap();
+ assert!(s3.client.config.request_payer);
}
#[test]