Skip to content

Commit 52f5119

Browse files
authored
Add Extensions in *Result objects (#743)
* add extensions support to results * add extensions to MultiStatus * fmt * rustdocs * get rid of extensions in paginated list result * add extension result integration tests
1 parent 18ed86c commit 52f5119

18 files changed

Lines changed: 260 additions & 43 deletions

File tree

src/aws/client.rs

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ use crate::client::{GetOptionsExt, HttpClient, HttpError, HttpResponse};
3636
use crate::list::{PaginatedListOptions, PaginatedListResult};
3737
use crate::multipart::PartId;
3838
use crate::{
39-
Attribute, Attributes, ClientOptions, GetOptions, MultipartId, Path, PutMultipartOptions,
40-
PutPayload, PutResult, Result, RetryConfig, TagSet,
39+
Attribute, Attributes, ClientOptions, GetOptions, ListResult, MultipartId, Path,
40+
PutMultipartOptions, PutPayload, PutResult, Result, RetryConfig, TagSet,
4141
};
4242
use async_trait::async_trait;
4343
use base64::Engine;
@@ -470,8 +470,10 @@ impl Request<'_> {
470470

471471
pub(crate) async fn do_put(self) -> Result<PutResult> {
472472
let response = self.send().await?;
473-
Ok(get_put_result(response.headers(), VERSION_HEADER)
474-
.map_err(|source| Error::Metadata { source })?)
473+
Ok(
474+
get_put_result(response, VERSION_HEADER)
475+
.map_err(|source| Error::Metadata { source })?,
476+
)
475477
}
476478
}
477479

@@ -857,11 +859,12 @@ impl S3Client {
857859
path: location.as_ref().to_string(),
858860
})?;
859861

860-
let version = get_version(response.headers(), VERSION_HEADER)
862+
let (parts, body) = response.into_parts();
863+
864+
let version = get_version(&parts.headers, VERSION_HEADER)
861865
.map_err(|source| Error::Metadata { source })?;
862866

863-
let data = response
864-
.into_body()
867+
let data = body
865868
.bytes()
866869
.await
867870
.map_err(|source| Error::CompleteMultipartResponseBody { source })?;
@@ -872,6 +875,7 @@ impl S3Client {
872875
Ok(PutResult {
873876
e_tag: Some(response.e_tag),
874877
version,
878+
extensions: parts.extensions,
875879
})
876880
}
877881

@@ -993,8 +997,11 @@ impl ListClient for Arc<S3Client> {
993997
.with_aws_sigv4(credential.authorizer(), None)
994998
.send_retry(&self.config.retry_config)
995999
.await
996-
.map_err(|source| Error::ListRequest { source })?
997-
.into_body()
1000+
.map_err(|source| Error::ListRequest { source })?;
1001+
1002+
let (parts, body) = response.into_parts();
1003+
1004+
let response = body
9981005
.bytes()
9991006
.await
10001007
.map_err(|source| Error::ListResponseBody { source })?;
@@ -1004,8 +1011,11 @@ impl ListClient for Arc<S3Client> {
10041011

10051012
let token = response.next_continuation_token.take();
10061013

1014+
let mut result: ListResult = response.try_into()?;
1015+
result.extensions = parts.extensions;
1016+
10071017
Ok(PaginatedListResult {
1008-
result: response.try_into()?,
1018+
result,
10091019
page_token: token,
10101020
})
10111021
}

src/aws/mod.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -679,7 +679,10 @@ mod tests {
679679
#[tokio::test]
680680
async fn s3_test() {
681681
maybe_skip_integration!();
682-
let config = AmazonS3Builder::from_env();
682+
// tag the extensions of every HTTP response with a marker,
683+
// allowing response_extensions to verify their propagation
684+
let config =
685+
AmazonS3Builder::from_env().with_http_connector(MarkerHttpConnector::default());
683686

684687
let integration = config.build().unwrap();
685688
let config = &integration.client.config;
@@ -701,6 +704,7 @@ mod tests {
701704
s3_encryption(&integration).await;
702705
put_get_attributes(&integration).await;
703706
list_paginated(&integration, &integration).await;
707+
response_extensions(&integration, true).await;
704708

705709
// Object tagging is not supported by S3 Express One Zone
706710
if config.session_provider.is_none() {

src/azure/client.rs

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -564,8 +564,10 @@ impl AzureClient {
564564
};
565565

566566
let response = builder.header(&BLOB_TYPE, "BlockBlob").send().await?;
567-
Ok(get_put_result(response.headers(), VERSION_HEADER)
568-
.map_err(|source| Error::Metadata { source })?)
567+
Ok(
568+
get_put_result(response, VERSION_HEADER)
569+
.map_err(|source| Error::Metadata { source })?,
570+
)
569571
}
570572

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

620-
Ok(get_put_result(response.headers(), VERSION_HEADER)
621-
.map_err(|source| Error::Metadata { source })?)
622+
Ok(
623+
get_put_result(response, VERSION_HEADER)
624+
.map_err(|source| Error::Metadata { source })?,
625+
)
622626
}
623627

624628
fn build_bulk_delete_body(
@@ -983,8 +987,11 @@ impl ListClient for Arc<AzureClient> {
983987
.sensitive(sensitive)
984988
.send()
985989
.await
986-
.map_err(|source| Error::ListRequest { source })?
987-
.into_body()
990+
.map_err(|source| Error::ListRequest { source })?;
991+
992+
let (parts, body) = response.into_parts();
993+
994+
let response = body
988995
.bytes()
989996
.await
990997
.map_err(|source| Error::ListResponseBody { source })?;
@@ -1007,8 +1014,11 @@ impl ListClient for Arc<AzureClient> {
10071014
}
10081015
}
10091016

1017+
let mut result = to_list_result(response, prefix)?;
1018+
result.extensions = parts.extensions;
1019+
10101020
Ok(PaginatedListResult {
1011-
result: to_list_result(response, prefix)?,
1021+
result,
10121022
page_token: token,
10131023
})
10141024
}
@@ -1051,6 +1061,7 @@ fn to_list_result(value: ListResultInternal, prefix: Option<&str>) -> Result<Lis
10511061
Ok(ListResult {
10521062
common_prefixes,
10531063
objects,
1064+
extensions: Default::default(),
10541065
})
10551066
}
10561067

src/azure/mod.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,12 @@ mod tests {
341341
#[tokio::test]
342342
async fn azure_blob_test() {
343343
maybe_skip_integration!();
344-
let integration = MicrosoftAzureBuilder::from_env().build().unwrap();
344+
// tag the extensions of every HTTP response with a marker,
345+
// allowing response_extensions to verify their propagation
346+
let integration = MicrosoftAzureBuilder::from_env()
347+
.with_http_connector(MarkerHttpConnector::default())
348+
.build()
349+
.unwrap();
345350

346351
put_get_delete_list(&integration).await;
347352
list_with_offset_exclusivity(&integration).await;
@@ -358,6 +363,7 @@ mod tests {
358363
multipart_out_of_order(&integration).await;
359364
signing(&integration).await;
360365
list_paginated(&integration, &integration).await;
366+
response_extensions(&integration, true).await;
361367

362368
let validate = !integration.client.config().disable_tagging;
363369
tagging(

src/client/get.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ impl<T: GetClient> GetContext<T> {
192192
meta,
193193
range,
194194
attributes,
195+
extensions: parts.extensions,
195196
})
196197
}
197198

src/client/header.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,22 @@ pub(crate) enum Error {
7070
},
7171
}
7272

73-
/// Extracts a PutResult from the provided [`HeaderMap`]
73+
/// Extracts a PutResult from the provided response
74+
///
75+
/// Propagates the extensions of the response into the [`crate::PutResult`]
7476
#[cfg(any(feature = "aws", feature = "gcp", feature = "azure"))]
7577
pub(crate) fn get_put_result(
76-
headers: &HeaderMap,
78+
response: crate::client::HttpResponse,
7779
version: &str,
7880
) -> Result<crate::PutResult, Error> {
79-
let e_tag = Some(get_etag(headers)?);
80-
let version = get_version(headers, version)?;
81-
Ok(crate::PutResult { e_tag, version })
81+
let (parts, _) = response.into_parts();
82+
let e_tag = Some(get_etag(&parts.headers)?);
83+
let version = get_version(&parts.headers, version)?;
84+
Ok(crate::PutResult {
85+
e_tag,
86+
version,
87+
extensions: parts.extensions,
88+
})
8289
}
8390

8491
/// Extracts a optional version from the provided [`HeaderMap`]

src/client/list.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,16 +114,19 @@ impl<T: ListClient + Clone> ListClientExt for T {
114114

115115
let mut common_prefixes = BTreeSet::new();
116116
let mut objects = Vec::new();
117+
let mut extensions = http::Extensions::new();
117118

118119
while let Some(result) = stream.next().await {
119120
let response = result?;
120121
common_prefixes.extend(response.common_prefixes);
121122
objects.extend(response.objects);
123+
extensions.extend(response.extensions);
122124
}
123125

124126
Ok(ListResult {
125127
common_prefixes: common_prefixes.into_iter().collect(),
126128
objects,
129+
extensions,
127130
})
128131
}
129132
}

src/client/s3.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ impl TryFrom<ListResponse> for ListResult {
5252
Ok(Self {
5353
common_prefixes,
5454
objects,
55+
extensions: Default::default(),
5556
})
5657
}
5758
}

src/gcp/client.rs

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ use crate::multipart::PartId;
3232
use crate::path::Path;
3333
use crate::util::hex_encode;
3434
use crate::{
35-
Attribute, Attributes, ClientOptions, CopyMode, GetOptions, MultipartId, PutMode,
35+
Attribute, Attributes, ClientOptions, CopyMode, GetOptions, ListResult, MultipartId, PutMode,
3636
PutMultipartOptions, PutOptions, PutPayload, PutResult, Result, RetryConfig,
3737
};
3838
use async_trait::async_trait;
@@ -250,8 +250,10 @@ impl Request<'_> {
250250

251251
async fn do_put(self) -> Result<PutResult> {
252252
let response = self.send().await?;
253-
Ok(get_put_result(response.headers(), VERSION_HEADER)
254-
.map_err(|source| Error::Metadata { source })?)
253+
Ok(
254+
get_put_result(response, VERSION_HEADER)
255+
.map_err(|source| Error::Metadata { source })?,
256+
)
255257
}
256258
}
257259

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

544-
let version = get_version(response.headers(), VERSION_HEADER)
546+
let (parts, body) = response.into_parts();
547+
548+
let version = get_version(&parts.headers, VERSION_HEADER)
545549
.map_err(|source| Error::Metadata { source })?;
546550

547-
let data = response
548-
.into_body()
551+
let data = body
549552
.bytes()
550553
.await
551554
.map_err(|source| Error::CompleteMultipartResponseBody { source })?;
@@ -556,6 +559,7 @@ impl GoogleCloudStorageClient {
556559
Ok(PutResult {
557560
e_tag: Some(response.e_tag),
558561
version,
562+
extensions: parts.extensions,
559563
})
560564
}
561565

@@ -705,8 +709,11 @@ impl ListClient for Arc<GoogleCloudStorageClient> {
705709
.with_bearer_auth(credential.as_deref())
706710
.send_retry(&self.config.retry_config)
707711
.await
708-
.map_err(|source| Error::ListRequest { source })?
709-
.into_body()
712+
.map_err(|source| Error::ListRequest { source })?;
713+
714+
let (parts, body) = response.into_parts();
715+
716+
let response = body
710717
.bytes()
711718
.await
712719
.map_err(|source| Error::ListResponseBody { source })?;
@@ -715,8 +722,12 @@ impl ListClient for Arc<GoogleCloudStorageClient> {
715722
.map_err(|source| Error::InvalidListResponse { source })?;
716723

717724
let token = response.next_continuation_token.take();
725+
726+
let mut result: ListResult = response.try_into()?;
727+
result.extensions = parts.extensions;
728+
718729
Ok(PaginatedListResult {
719-
result: response.try_into()?,
730+
result,
720731
page_token: token,
721732
})
722733
}

src/gcp/mod.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,12 @@ mod test {
311311
#[tokio::test]
312312
async fn gcs_test() {
313313
maybe_skip_integration!();
314-
let integration = GoogleCloudStorageBuilder::from_env().build().unwrap();
314+
// tag the extensions of every HTTP response with a marker,
315+
// allowing response_extensions to verify their propagation
316+
let integration = GoogleCloudStorageBuilder::from_env()
317+
.with_http_connector(MarkerHttpConnector::default())
318+
.build()
319+
.unwrap();
315320

316321
put_get_delete_list(&integration).await;
317322
list_with_offset_exclusivity(&integration).await;
@@ -336,6 +341,10 @@ mod test {
336341
// Fake GCS server doesn't currently support attributes
337342
put_get_attributes(&integration).await;
338343
}
344+
345+
// Fake GCS server does not yet implement XML Multipart uploads
346+
let test_multipart = integration.client.config().base_url == DEFAULT_GCS_BASE_URL;
347+
response_extensions(&integration, test_multipart).await;
339348
}
340349

341350
#[tokio::test]

0 commit comments

Comments
 (0)