Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 67 additions & 3 deletions src/aws/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ use crate::aws::{
};
use crate::client::{HttpConnector, TokenCredentialProvider, http_connector};
use crate::config::ConfigValue;
use crate::{ClientConfigKey, ClientOptions, Result, RetryConfig, StaticCredentialProvider};
use crate::{
Capabilities, ClientConfigKey, ClientOptions, Result, RetryConfig, StaticCredentialProvider,
};
use base64::Engine;
use base64::prelude::BASE64_STANDARD;
use itertools::Itertools;
Expand Down Expand Up @@ -191,6 +193,8 @@ pub struct AmazonS3Builder {
request_payer: ConfigValue<RequesterPayer>,
/// The [`HttpConnector`] to use
http_connector: Option<Arc<dyn HttpConnector>>,
/// Capabilities to advertise for this store instance
capabilities: Option<ConfigValue<Capabilities>>,
}

/// Configuration keys for [`AmazonS3Builder`]
Expand Down Expand Up @@ -448,6 +452,9 @@ pub enum AmazonS3ConfigKey {

/// Encryption options
Encryption(S3EncryptionConfigKey),

/// Override the capabilities advertised by this store.
Capabilities,
}

impl AsRef<str> for AmazonS3ConfigKey {
Expand Down Expand Up @@ -481,6 +488,7 @@ impl AsRef<str> for AmazonS3ConfigKey {
Self::RequestPayer => "aws_request_payer",
Self::Client(opt) => opt.as_ref(),
Self::Encryption(opt) => opt.as_ref(),
Self::Capabilities => "aws_capabilities",
}
}
}
Expand Down Expand Up @@ -540,6 +548,7 @@ impl FromStr for AmazonS3ConfigKey {
"aws_sse_customer_key_base64" | "sse_customer_key_base64" => Ok(Self::Encryption(
S3EncryptionConfigKey::CustomerEncryptionKey,
)),
"aws_capabilities" => Ok(Self::Capabilities),
_ => match s.strip_prefix("aws_").unwrap_or(s).parse() {
Ok(key) => Ok(Self::Client(key)),
Err(_) => Err(Error::UnknownConfigurationKey { key: s.into() }.into()),
Expand Down Expand Up @@ -691,6 +700,9 @@ impl AmazonS3Builder {
self.encryption_customer_key_base64 = Some(value.into())
}
},
AmazonS3ConfigKey::Capabilities => {
self.capabilities = Some(ConfigValue::Deferred(value.into()))
}
};
self
}
Expand Down Expand Up @@ -746,6 +758,7 @@ impl AmazonS3Builder {
AmazonS3ConfigKey::ConditionalPut => Some(self.conditional_put.to_string()),
AmazonS3ConfigKey::DisableTagging => Some(self.disable_tagging.to_string()),
AmazonS3ConfigKey::RequestPayer => Some(self.request_payer.to_string()),
AmazonS3ConfigKey::Capabilities => self.capabilities.as_ref().map(ToString::to_string),
AmazonS3ConfigKey::Encryption(key) => match key {
S3EncryptionConfigKey::ServerSideEncryption => {
self.encryption_type.as_ref().map(ToString::to_string)
Expand Down Expand Up @@ -1071,6 +1084,17 @@ impl AmazonS3Builder {
self
}

/// Override the [`Capabilities`] advertised by this store.
///
/// By default the store reports `ordered_listing: true` because S3
/// `ListObjectsV2` returns results in lexicographic order. Use this
/// method if you are connecting to an S3-compatible endpoint whose
/// behaviour differs from the standard S3 API.
pub fn with_capabilities(mut self, capabilities: Capabilities) -> Self {
self.capabilities = Some(ConfigValue::Parsed(capabilities));
self
}

/// Create a [`AmazonS3`] instance from the provided values,
/// consuming `self`.
pub fn build(mut self) -> Result<AmazonS3> {
Expand Down Expand Up @@ -1251,7 +1275,10 @@ impl AmazonS3Builder {
let http_client = http.connect(&config.client_options)?;
let client = Arc::new(S3Client::new(config, http_client));

Ok(AmazonS3 { client })
Ok(AmazonS3 {
client,
capabilities: self.capabilities.map(|x| x.get()).transpose()?,
})
}
}

Expand Down Expand Up @@ -1500,6 +1527,7 @@ impl From<S3EncryptionHeaders> for HeaderMap {
#[cfg(test)]
mod tests {
use super::*;
use crate::Capability;
use std::collections::HashMap;

#[test]
Expand All @@ -1517,6 +1545,7 @@ mod tests {
("aws_session_token", aws_session_token.clone()),
("aws_unsigned_payload", "true".to_string()),
("aws_checksum_algorithm", "sha256".to_string()),
("aws_capabilities", "ordered-listing".to_string()),
]);

let builder = options
Expand All @@ -1536,6 +1565,14 @@ mod tests {
Checksum::SHA256
);
assert!(builder.unsigned_payload.get().unwrap());
assert!(
builder
.capabilities
.unwrap()
.get()
.unwrap()
.has(Capability::OrderedListing)
);
}

#[test]
Expand Down Expand Up @@ -1590,7 +1627,8 @@ mod tests {
.with_config(
"aws_sse_customer_key_base64".parse().unwrap(),
"some_customer_key",
);
)
.with_config(AmazonS3ConfigKey::Capabilities, "ordered-listing");

assert_eq!(
builder
Expand Down Expand Up @@ -1650,6 +1688,12 @@ mod tests {
.unwrap(),
"some_customer_key"
);
assert_eq!(
builder
.get_config_value(&"aws_capabilities".parse().unwrap())
.unwrap(),
"ordered-listing"
);
}

#[test]
Expand Down Expand Up @@ -1873,6 +1917,26 @@ mod tests {
assert!(s3.client.config.request_payer);
}

#[test]
fn test_parse_capabilities() {
// Default: ordered listing disabled
let s3 = AmazonS3Builder::new()
.with_bucket_name("bucket")
.with_region("region")
.build()
.unwrap();
assert!(!s3.capabilities.is_some());

// Explicit override via with_capabilities: no capabilities
let s3 = AmazonS3Builder::new()
.with_capabilities(Capabilities::new([Capability::OrderedListing]))
.with_bucket_name("bucket")
.with_region("region")
.build()
.unwrap();
assert!(s3.capabilities.unwrap().has(Capability::OrderedListing));
}

#[test]
fn test_parse_bucket_az() {
let cases = [
Expand Down
18 changes: 15 additions & 3 deletions src/aws/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ use crate::multipart::{MultipartStore, PartId};
use crate::signer::Signer;
use crate::util::STRICT_ENCODE_SET;
use crate::{
CopyMode, CopyOptions, Error, GetOptions, GetResult, ListResult, MultipartId, MultipartUpload,
ObjectMeta, ObjectStore, Path, PutMode, PutMultipartOptions, PutOptions, PutPayload, PutResult,
Result, UploadPart,
Capabilities, CopyMode, CopyOptions, Error, GetOptions, GetResult, ListResult, MultipartId,
MultipartUpload, ObjectMeta, ObjectStore, Path, PutMode, PutMultipartOptions, PutOptions,
PutPayload, PutResult, Result, UploadPart,
};

static TAGS_HEADER: HeaderName = HeaderName::from_static("x-amz-tagging");
Expand Down Expand Up @@ -79,10 +79,15 @@ use crate::client::parts::Parts;
use crate::list::{PaginatedListOptions, PaginatedListResult, PaginatedListStore};
pub use credential::{AwsAuthorizer, AwsCredential};

fn get_default_capabilities() -> Capabilities {
return Capabilities::new([]);
}

/// Interface for [Amazon S3](https://aws.amazon.com/s3/).
#[derive(Debug, Clone)]
pub struct AmazonS3 {
client: Arc<S3Client>,
capabilities: Option<Capabilities>,
}

impl std::fmt::Display for AmazonS3 {
Expand Down Expand Up @@ -394,6 +399,12 @@ impl ObjectStore for AmazonS3 {
}
}
}

fn capabilities(&self) -> Capabilities {
self.capabilities
.clone()
.unwrap_or_else(get_default_capabilities)
}
}

#[derive(Debug)]
Expand Down Expand Up @@ -688,6 +699,7 @@ mod tests {
tagging(
Arc::new(AmazonS3 {
client: Arc::clone(&integration.client),
capabilities: None,
}),
!config.disable_tagging,
|p| {
Expand Down
22 changes: 20 additions & 2 deletions src/azure/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ use crate::azure::credential::{
use crate::azure::{AzureCredential, AzureCredentialProvider, MicrosoftAzure, STORE};
use crate::client::{HttpConnector, TokenCredentialProvider, http_connector};
use crate::config::ConfigValue;
use crate::{ClientConfigKey, ClientOptions, Result, RetryConfig, StaticCredentialProvider};
use crate::{
Capabilities, ClientConfigKey, ClientOptions, Result, RetryConfig, StaticCredentialProvider,
};
use percent_encoding::percent_decode_str;
use serde::{Deserialize, Serialize};
use std::str::FromStr;
Expand Down Expand Up @@ -180,6 +182,8 @@ pub struct MicrosoftAzureBuilder {
fabric_cluster_identifier: Option<String>,
/// The [`HttpConnector`] to use
http_connector: Option<Arc<dyn HttpConnector>>,
/// Capabilities to advertise for this store instance
capabilities: Option<Capabilities>,
}

/// Configuration keys for [`MicrosoftAzureBuilder`]
Expand Down Expand Up @@ -906,6 +910,17 @@ impl MicrosoftAzureBuilder {
self
}

/// Override the [`Capabilities`] advertised by this store.
///
/// By default the store reports `ordered_listing: true` because Azure Blob
/// Storage returns list results in lexicographic order. Use this method if
/// you are connecting to an endpoint whose behaviour differs from the
/// standard Azure Blob Storage API.
pub fn with_capabilities(mut self, capabilities: Capabilities) -> Self {
self.capabilities = Some(capabilities);
self
}

/// Configure a connection to container with given name on Microsoft Azure Blob store.
pub fn build(mut self) -> Result<MicrosoftAzure> {
if let Some(url) = self.url.take() {
Expand Down Expand Up @@ -1054,7 +1069,10 @@ impl MicrosoftAzureBuilder {
let http_client = http.connect(&config.client_options)?;
let client = Arc::new(AzureClient::new(config, http_client));

Ok(MicrosoftAzure { client })
Ok(MicrosoftAzure {
client,
capabilities: self.capabilities,
})
}
}

Expand Down
18 changes: 15 additions & 3 deletions src/azure/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@
//! Unused blocks will automatically be dropped after 7 days.
//!
use crate::{
CopyMode, CopyOptions, GetOptions, GetResult, ListResult, MultipartId, MultipartUpload,
ObjectMeta, ObjectStore, PutMultipartOptions, PutOptions, PutPayload, PutResult, Result,
UploadPart,
Capabilities, Capability, CopyMode, CopyOptions, GetOptions, GetResult, ListResult,
MultipartId, MultipartUpload, ObjectMeta, ObjectStore, PutMultipartOptions, PutOptions,
PutPayload, PutResult, Result, UploadPart,
multipart::{MultipartStore, PartId},
path::Path,
signer::Signer,
Expand Down Expand Up @@ -58,10 +58,15 @@ pub use credential::AzureCredential;

const STORE: &str = "MicrosoftAzure";

fn get_default_capabilities() -> Capabilities {
Capabilities::new([Capability::OrderedListing])
}

/// Interface for [Microsoft Azure Blob Storage](https://azure.microsoft.com/en-us/services/storage/blobs/).
#[derive(Debug)]
pub struct MicrosoftAzure {
client: Arc<AzureClient>,
capabilities: Option<Capabilities>,
}

impl MicrosoftAzure {
Expand Down Expand Up @@ -180,6 +185,12 @@ impl ObjectStore for MicrosoftAzure {
CopyMode::Create => self.client.copy_request(from, to, false).await,
}
}

fn capabilities(&self) -> Capabilities {
self.capabilities
.clone()
.unwrap_or_else(get_default_capabilities)
}
}

#[async_trait]
Expand Down Expand Up @@ -363,6 +374,7 @@ mod tests {
tagging(
Arc::new(MicrosoftAzure {
client: Arc::clone(&integration.client),
capabilities: None,
}),
validate,
|p| {
Expand Down
Loading