Skip to content

Commit

Permalink
Refactor S3's extended request ID implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
jdisanti committed Dec 22, 2022
1 parent 409b7c0 commit 3ba8529
Show file tree
Hide file tree
Showing 13 changed files with 451 additions and 338 deletions.
3 changes: 1 addition & 2 deletions aws/rust-runtime/aws-http/src/request_id.rs
Expand Up @@ -80,8 +80,7 @@ fn extract_request_id(headers: &HeaderMap<HeaderValue>) -> Option<&str> {
headers
.get("x-amzn-requestid")
.or_else(|| headers.get("x-amz-request-id"))
.map(|value| std::str::from_utf8(value.as_bytes()).ok())
.flatten()
.and_then(|value| value.to_str().ok())
}

#[cfg(test)]
Expand Down
4 changes: 2 additions & 2 deletions aws/rust-runtime/aws-inlineable/src/lib.rs
Expand Up @@ -24,8 +24,8 @@ pub mod no_credentials;
/// Support types required for adding presigning to an operation in a generated service.
pub mod presigning;

/// Special logic for handling S3's error responses.
pub mod s3_errors;
/// Special logic for extracting request IDs from S3's responses.
pub mod s3_request_id;

/// Glacier-specific checksumming behavior
pub mod glacier_checksums;
Expand Down
70 changes: 0 additions & 70 deletions aws/rust-runtime/aws-inlineable/src/s3_errors.rs

This file was deleted.

169 changes: 169 additions & 0 deletions aws/rust-runtime/aws-inlineable/src/s3_request_id.rs
@@ -0,0 +1,169 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

use aws_smithy_client::SdkError;
use aws_smithy_http::http::HttpHeaders;
use aws_smithy_http::operation;
use aws_smithy_types::error::{Builder as GenericErrorBuilder, Error as GenericError};
use http::{HeaderMap, HeaderValue};

const EXTENDED_REQUEST_ID: &str = "s3_extended_request_id";

/// Trait to retrieve the S3-specific extended request ID
///
/// Read more at <https://aws.amazon.com/premiumsupport/knowledge-center/s3-request-id-values/>.
pub trait RequestIdExt {
/// Returns the S3 Extended Request ID necessary when contacting AWS Support.
fn extended_request_id(&self) -> Option<&str>;
}

impl<E, R> RequestIdExt for SdkError<E, R>
where
R: HttpHeaders,
{
fn extended_request_id(&self) -> Option<&str> {
match self {
Self::ResponseError(err) => extract_extended_request_id(err.raw().http_headers()),
Self::ServiceError(err) => extract_extended_request_id(err.raw().http_headers()),
_ => None,
}
}
}

impl RequestIdExt for GenericError {
fn extended_request_id(&self) -> Option<&str> {
self.extra(EXTENDED_REQUEST_ID)
}
}

impl RequestIdExt for operation::Response {
fn extended_request_id(&self) -> Option<&str> {
extract_extended_request_id(self.http().headers())
}
}

impl<B> RequestIdExt for http::Response<B> {
fn extended_request_id(&self) -> Option<&str> {
extract_extended_request_id(self.headers())
}
}

impl<O, E> RequestIdExt for Result<O, E>
where
O: RequestIdExt,
E: RequestIdExt,
{
fn extended_request_id(&self) -> Option<&str> {
match self {
Ok(ok) => ok.extended_request_id(),
Err(err) => err.extended_request_id(),
}
}
}

/// Applies the extended request ID to a generic error builder
#[doc(hidden)]
pub fn apply_extended_request_id(
builder: GenericErrorBuilder,
headers: &HeaderMap<HeaderValue>,
) -> GenericErrorBuilder {
if let Some(extended_request_id) = extract_extended_request_id(headers) {
builder.custom(EXTENDED_REQUEST_ID, extended_request_id)
} else {
builder
}
}

/// Extracts the S3 Extended Request ID from HTTP response headers
fn extract_extended_request_id(headers: &HeaderMap<HeaderValue>) -> Option<&str> {
headers
.get("x-amz-id-2")
.and_then(|value| value.to_str().ok())
}

#[cfg(test)]
mod test {
use super::*;
use aws_smithy_client::SdkError;
use aws_smithy_http::body::SdkBody;
use http::Response;

#[test]
fn handle_missing_header() {
let resp = http::Response::builder().status(400).body("").unwrap();
let mut builder = aws_smithy_types::Error::builder().message("123");
builder = apply_extended_request_id(builder, resp.headers());
assert_eq!(builder.build().extended_request_id(), None);
}

#[test]
fn test_extended_request_id_sdk_error() {
let without_extended_request_id =
|| operation::Response::new(Response::builder().body(SdkBody::empty()).unwrap());
let with_extended_request_id = || {
operation::Response::new(
Response::builder()
.header("x-amz-id-2", HeaderValue::from_static("some-request-id"))
.body(SdkBody::empty())
.unwrap(),
)
};
assert_eq!(
None,
SdkError::<(), _>::response_error("test", without_extended_request_id())
.extended_request_id()
);
assert_eq!(
Some("some-request-id"),
SdkError::<(), _>::response_error("test", with_extended_request_id())
.extended_request_id()
);
assert_eq!(
None,
SdkError::service_error((), without_extended_request_id()).extended_request_id()
);
assert_eq!(
Some("some-request-id"),
SdkError::service_error((), with_extended_request_id()).extended_request_id()
);
}

#[test]
fn test_extract_extended_request_id() {
let mut headers = HeaderMap::new();
assert_eq!(None, extract_extended_request_id(&headers));

headers.append("x-amz-id-2", HeaderValue::from_static("some-request-id"));
assert_eq!(
Some("some-request-id"),
extract_extended_request_id(&headers)
);
}

#[test]
fn test_apply_extended_request_id() {
let mut headers = HeaderMap::new();
assert_eq!(
GenericError::builder().build(),
apply_extended_request_id(GenericError::builder(), &headers).build(),
);

headers.append("x-amz-id-2", HeaderValue::from_static("some-request-id"));
assert_eq!(
GenericError::builder()
.custom(EXTENDED_REQUEST_ID, "some-request-id")
.build(),
apply_extended_request_id(GenericError::builder(), &headers).build(),
);
}

#[test]
fn test_generic_error_extended_request_id_impl() {
let err = GenericError::builder()
.custom(EXTENDED_REQUEST_ID, "some-request-id")
.build();
assert_eq!(Some("some-request-id"), err.extended_request_id());
}
}
Expand Up @@ -11,48 +11,57 @@ import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegen
import software.amazon.smithy.rust.codegen.client.smithy.customize.CombinedClientCodegenDecorator
import software.amazon.smithy.rustsdk.customize.DisabledAuthDecorator
import software.amazon.smithy.rustsdk.customize.apigateway.ApiGatewayDecorator
import software.amazon.smithy.rustsdk.customize.applyDecorators
import software.amazon.smithy.rustsdk.customize.ec2.Ec2Decorator
import software.amazon.smithy.rustsdk.customize.glacier.GlacierDecorator
import software.amazon.smithy.rustsdk.customize.onlyApplyTo
import software.amazon.smithy.rustsdk.customize.route53.Route53Decorator
import software.amazon.smithy.rustsdk.customize.s3.S3Decorator
import software.amazon.smithy.rustsdk.customize.s3.S3ExtendedRequestIdDecorator
import software.amazon.smithy.rustsdk.customize.s3control.S3ControlDecorator
import software.amazon.smithy.rustsdk.customize.sts.STSDecorator

val DECORATORS: List<ClientCodegenDecorator> = listOf(
// General AWS Decorators
CredentialsProviderDecorator(),
RegionDecorator(),
AwsEndpointDecorator(),
UserAgentDecorator(),
SigV4SigningDecorator(),
HttpRequestChecksumDecorator(),
HttpResponseChecksumDecorator(),
RetryClassifierDecorator(),
IntegrationTestDecorator(),
AwsFluentClientDecorator(),
CrateLicenseDecorator(),
SdkConfigDecorator(),
ServiceConfigDecorator(),
AwsPresigningDecorator(),
AwsReadmeDecorator(),
HttpConnectorDecorator(),
AwsEndpointsStdLib(),
AwsRequestIdDecorator(),
DisabledAuthDecorator(),
listOf(
CredentialsProviderDecorator(),
RegionDecorator(),
AwsEndpointDecorator(),
UserAgentDecorator(),
SigV4SigningDecorator(),
HttpRequestChecksumDecorator(),
HttpResponseChecksumDecorator(),
RetryClassifierDecorator(),
IntegrationTestDecorator(),
AwsFluentClientDecorator(),
CrateLicenseDecorator(),
SdkConfigDecorator(),
ServiceConfigDecorator(),
AwsPresigningDecorator(),
AwsReadmeDecorator(),
HttpConnectorDecorator(),
AwsEndpointsStdLib(),
AwsRequestIdDecorator(),
DisabledAuthDecorator(),
),

// Service specific decorators
ApiGatewayDecorator().onlyApplyTo("com.amazonaws.apigateway#BackplaneControlService"),
Ec2Decorator().onlyApplyTo("com.amazonaws.ec2#AmazonEC2"),
GlacierDecorator().onlyApplyTo("com.amazonaws.glacier#Glacier"),
Route53Decorator().onlyApplyTo("com.amazonaws.route53#AWSDnsV20130401"),
S3Decorator().onlyApplyTo("com.amazonaws.s3#AmazonS3"),
"com.amazonaws.s3#AmazonS3".applyDecorators(
S3Decorator(),
S3ExtendedRequestIdDecorator(),
),
S3ControlDecorator().onlyApplyTo("com.amazonaws.s3control#AWSS3ControlServiceV20180820"),
STSDecorator().onlyApplyTo("com.amazonaws.sts#AWSSecurityTokenServiceV20110615"),

// Only build docs-rs for linux to reduce load on docs.rs
DocsRsMetadataDecorator(DocsRsMetadataSettings(targets = listOf("x86_64-unknown-linux-gnu"), allFeatures = true)),
)
listOf(
DocsRsMetadataDecorator(DocsRsMetadataSettings(targets = listOf("x86_64-unknown-linux-gnu"), allFeatures = true)),
),
).flatten()

class AwsCodegenDecorator : CombinedClientCodegenDecorator(DECORATORS) {
override val name: String = "AwsSdkCodegenDecorator"
Expand Down

0 comments on commit 3ba8529

Please sign in to comment.