Skip to content
Merged
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
30 changes: 20 additions & 10 deletions src/aws/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ use crate::client::{GetOptionsExt, HttpClient, HttpError, HttpResponse};
use crate::list::{PaginatedListOptions, PaginatedListResult};
use crate::multipart::PartId;
use crate::{
Attribute, Attributes, ClientOptions, GetOptions, MultipartId, Path, PutMultipartOptions,
PutPayload, PutResult, Result, RetryConfig, TagSet,
Attribute, Attributes, ClientOptions, GetOptions, ListResult, MultipartId, Path,
PutMultipartOptions, PutPayload, PutResult, Result, RetryConfig, TagSet,
};
use async_trait::async_trait;
use base64::Engine;
Expand Down Expand Up @@ -470,8 +470,10 @@ impl Request<'_> {

pub(crate) async fn do_put(self) -> Result<PutResult> {
let response = self.send().await?;
Ok(get_put_result(response.headers(), VERSION_HEADER)
.map_err(|source| Error::Metadata { source })?)
Ok(
get_put_result(response, VERSION_HEADER)
.map_err(|source| Error::Metadata { source })?,
)
}
}

Expand Down Expand Up @@ -857,11 +859,12 @@ impl S3Client {
path: location.as_ref().to_string(),
})?;

let version = get_version(response.headers(), VERSION_HEADER)
let (parts, body) = response.into_parts();

let version = get_version(&parts.headers, VERSION_HEADER)
.map_err(|source| Error::Metadata { source })?;

let data = response
.into_body()
let data = body
.bytes()
.await
.map_err(|source| Error::CompleteMultipartResponseBody { source })?;
Expand All @@ -872,6 +875,7 @@ impl S3Client {
Ok(PutResult {
e_tag: Some(response.e_tag),
version,
extensions: parts.extensions,
})
}

Expand Down Expand Up @@ -993,8 +997,11 @@ impl ListClient for Arc<S3Client> {
.with_aws_sigv4(credential.authorizer(), None)
.send_retry(&self.config.retry_config)
.await
.map_err(|source| Error::ListRequest { source })?
.into_body()
.map_err(|source| Error::ListRequest { source })?;

let (parts, body) = response.into_parts();

let response = body
.bytes()
.await
.map_err(|source| Error::ListResponseBody { source })?;
Expand All @@ -1004,8 +1011,11 @@ impl ListClient for Arc<S3Client> {

let token = response.next_continuation_token.take();

let mut result: ListResult = response.try_into()?;
result.extensions = parts.extensions;

Ok(PaginatedListResult {
result: response.try_into()?,
result,
page_token: token,
})
}
Expand Down
6 changes: 5 additions & 1 deletion src/aws/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -679,7 +679,10 @@ mod tests {
#[tokio::test]
async fn s3_test() {
maybe_skip_integration!();
let config = AmazonS3Builder::from_env();
// tag the extensions of every HTTP response with a marker,
// allowing response_extensions to verify their propagation
let config =
AmazonS3Builder::from_env().with_http_connector(MarkerHttpConnector::default());

let integration = config.build().unwrap();
let config = &integration.client.config;
Expand All @@ -701,6 +704,7 @@ mod tests {
s3_encryption(&integration).await;
put_get_attributes(&integration).await;
list_paginated(&integration, &integration).await;
response_extensions(&integration, true).await;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


// Object tagging is not supported by S3 Express One Zone
if config.session_provider.is_none() {
Expand Down
25 changes: 18 additions & 7 deletions src/azure/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -564,8 +564,10 @@ impl AzureClient {
};

let response = builder.header(&BLOB_TYPE, "BlockBlob").send().await?;
Ok(get_put_result(response.headers(), VERSION_HEADER)
.map_err(|source| Error::Metadata { source })?)
Ok(
get_put_result(response, VERSION_HEADER)
.map_err(|source| Error::Metadata { source })?,
)
}

/// PUT a block <https://learn.microsoft.com/en-us/rest/api/storageservices/put-block>
Expand Down Expand Up @@ -617,8 +619,10 @@ impl AzureClient {
.send()
.await?;

Ok(get_put_result(response.headers(), VERSION_HEADER)
.map_err(|source| Error::Metadata { source })?)
Ok(
get_put_result(response, VERSION_HEADER)
.map_err(|source| Error::Metadata { source })?,
)
}

fn build_bulk_delete_body(
Expand Down Expand Up @@ -983,8 +987,11 @@ impl ListClient for Arc<AzureClient> {
.sensitive(sensitive)
.send()
.await
.map_err(|source| Error::ListRequest { source })?
.into_body()
.map_err(|source| Error::ListRequest { source })?;

let (parts, body) = response.into_parts();

let response = body
.bytes()
.await
.map_err(|source| Error::ListResponseBody { source })?;
Expand All @@ -1007,8 +1014,11 @@ impl ListClient for Arc<AzureClient> {
}
}

let mut result = to_list_result(response, prefix)?;
result.extensions = parts.extensions;

Ok(PaginatedListResult {
result: to_list_result(response, prefix)?,
result,
page_token: token,
})
}
Expand Down Expand Up @@ -1051,6 +1061,7 @@ fn to_list_result(value: ListResultInternal, prefix: Option<&str>) -> Result<Lis
Ok(ListResult {
common_prefixes,
objects,
extensions: Default::default(),
})
}

Expand Down
8 changes: 7 additions & 1 deletion src/azure/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,12 @@ mod tests {
#[tokio::test]
async fn azure_blob_test() {
maybe_skip_integration!();
let integration = MicrosoftAzureBuilder::from_env().build().unwrap();
// tag the extensions of every HTTP response with a marker,
// allowing response_extensions to verify their propagation
let integration = MicrosoftAzureBuilder::from_env()
.with_http_connector(MarkerHttpConnector::default())
.build()
.unwrap();

put_get_delete_list(&integration).await;
list_with_offset_exclusivity(&integration).await;
Expand All @@ -358,6 +363,7 @@ mod tests {
multipart_out_of_order(&integration).await;
signing(&integration).await;
list_paginated(&integration, &integration).await;
response_extensions(&integration, true).await;

let validate = !integration.client.config().disable_tagging;
tagging(
Expand Down
1 change: 1 addition & 0 deletions src/client/get.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ impl<T: GetClient> GetContext<T> {
meta,
range,
attributes,
extensions: parts.extensions,
})
}

Expand Down
17 changes: 12 additions & 5 deletions src/client/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,22 @@ pub(crate) enum Error {
},
}

/// Extracts a PutResult from the provided [`HeaderMap`]
/// 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"))]
pub(crate) fn get_put_result(
headers: &HeaderMap,
response: crate::client::HttpResponse,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Different ways to tackle this. LMK if you prefer another approach. Fable initially did a mut here which was kinda gross.

version: &str,
) -> Result<crate::PutResult, Error> {
let e_tag = Some(get_etag(headers)?);
let version = get_version(headers, version)?;
Ok(crate::PutResult { e_tag, version })
let (parts, _) = response.into_parts();
let e_tag = Some(get_etag(&parts.headers)?);
let version = get_version(&parts.headers, version)?;
Ok(crate::PutResult {
e_tag,
version,
extensions: parts.extensions,
})
}

/// Extracts a optional version from the provided [`HeaderMap`]
Expand Down
3 changes: 3 additions & 0 deletions src/client/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,16 +114,19 @@ impl<T: ListClient + Clone> ListClientExt for T {

let mut common_prefixes = BTreeSet::new();
let mut objects = Vec::new();
let mut extensions = http::Extensions::new();

while let Some(result) = stream.next().await {
let response = result?;
common_prefixes.extend(response.common_prefixes);
objects.extend(response.objects);
extensions.extend(response.extensions);
}

Ok(ListResult {
common_prefixes: common_prefixes.into_iter().collect(),
objects,
extensions,
})
}
}
1 change: 1 addition & 0 deletions src/client/s3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ impl TryFrom<ListResponse> for ListResult {
Ok(Self {
common_prefixes,
objects,
extensions: Default::default(),
})
}
}
Expand Down
29 changes: 20 additions & 9 deletions src/gcp/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use crate::multipart::PartId;
use crate::path::Path;
use crate::util::hex_encode;
use crate::{
Attribute, Attributes, ClientOptions, CopyMode, GetOptions, MultipartId, PutMode,
Attribute, Attributes, ClientOptions, CopyMode, GetOptions, ListResult, MultipartId, PutMode,
PutMultipartOptions, PutOptions, PutPayload, PutResult, Result, RetryConfig,
};
use async_trait::async_trait;
Expand Down Expand Up @@ -250,8 +250,10 @@ impl Request<'_> {

async fn do_put(self) -> Result<PutResult> {
let response = self.send().await?;
Ok(get_put_result(response.headers(), VERSION_HEADER)
.map_err(|source| Error::Metadata { source })?)
Ok(
get_put_result(response, VERSION_HEADER)
.map_err(|source| Error::Metadata { source })?,
)
}
}

Expand Down Expand Up @@ -541,11 +543,12 @@ impl GoogleCloudStorageClient {
.await
.map_err(|source| Error::CompleteMultipartRequest { source })?;

let version = get_version(response.headers(), VERSION_HEADER)
let (parts, body) = response.into_parts();

let version = get_version(&parts.headers, VERSION_HEADER)
.map_err(|source| Error::Metadata { source })?;

let data = response
.into_body()
let data = body
.bytes()
.await
.map_err(|source| Error::CompleteMultipartResponseBody { source })?;
Expand All @@ -556,6 +559,7 @@ impl GoogleCloudStorageClient {
Ok(PutResult {
e_tag: Some(response.e_tag),
version,
extensions: parts.extensions,
})
}

Expand Down Expand Up @@ -705,8 +709,11 @@ impl ListClient for Arc<GoogleCloudStorageClient> {
.with_bearer_auth(credential.as_deref())
.send_retry(&self.config.retry_config)
.await
.map_err(|source| Error::ListRequest { source })?
.into_body()
.map_err(|source| Error::ListRequest { source })?;

let (parts, body) = response.into_parts();

let response = body
.bytes()
.await
.map_err(|source| Error::ListResponseBody { source })?;
Expand All @@ -715,8 +722,12 @@ impl ListClient for Arc<GoogleCloudStorageClient> {
.map_err(|source| Error::InvalidListResponse { source })?;

let token = response.next_continuation_token.take();

let mut result: ListResult = response.try_into()?;
result.extensions = parts.extensions;

Ok(PaginatedListResult {
result: response.try_into()?,
result,
page_token: token,
})
}
Expand Down
11 changes: 10 additions & 1 deletion src/gcp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,12 @@ mod test {
#[tokio::test]
async fn gcs_test() {
maybe_skip_integration!();
let integration = GoogleCloudStorageBuilder::from_env().build().unwrap();
// tag the extensions of every HTTP response with a marker,
// allowing response_extensions to verify their propagation
let integration = GoogleCloudStorageBuilder::from_env()
.with_http_connector(MarkerHttpConnector::default())
.build()
.unwrap();

put_get_delete_list(&integration).await;
list_with_offset_exclusivity(&integration).await;
Expand All @@ -336,6 +341,10 @@ mod test {
// Fake GCS server doesn't currently support attributes
put_get_attributes(&integration).await;
}

// Fake GCS server does not yet implement XML Multipart uploads
let test_multipart = integration.client.config().base_url == DEFAULT_GCS_BASE_URL;
response_extensions(&integration, test_multipart).await;
}

#[tokio::test]
Expand Down
21 changes: 14 additions & 7 deletions src/http/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,12 +256,15 @@ impl Client {
.send()
.await;

let response = match result {
Ok(result) => result
.into_body()
.bytes()
.await
.map_err(|source| Error::Reqwest { source })?,
let (response, extensions) = match result {
Ok(result) => {
let (parts, body) = result.into_parts();
let body = body
.bytes()
.await
.map_err(|source| Error::Reqwest { source })?;
(body, parts.extensions)
}
Err(e) if matches!(e.status(), Some(StatusCode::NOT_FOUND)) => {
return match depth {
"0" => {
Expand All @@ -286,8 +289,9 @@ impl Client {
}
};

let status = quick_xml::de::from_reader(response.reader())
let mut status: MultiStatus = quick_xml::de::from_reader(response.reader())
.map_err(|source| Error::InvalidPropFind { source })?;
status.extensions = extensions;

Ok(status)
}
Expand Down Expand Up @@ -415,6 +419,9 @@ impl GetClient for Client {
#[derive(Deserialize, Default)]
pub(crate) struct MultiStatus {
pub response: Vec<MultiStatusResponse>,
/// The extensions of the HTTP response this was parsed from
#[serde(skip)]
pub extensions: ::http::Extensions,
Comment thread
criccomini marked this conversation as resolved.
}

#[derive(Deserialize)]
Expand Down
Loading
Loading