diff --git a/aws/rust-runtime/aws-inlineable/Cargo.toml b/aws/rust-runtime/aws-inlineable/Cargo.toml index f471975d829e21ddfbaddf3e0565da8fb7ebaed5..8836e8c558e9fbcb2d556cf9261b26e2d575bca9 100644 --- a/aws/rust-runtime/aws-inlineable/Cargo.toml +++ b/aws/rust-runtime/aws-inlineable/Cargo.toml @@ -15,6 +15,8 @@ repository = "https://github.com/awslabs/smithy-rs" aws-credential-types = { path = "../aws-credential-types" } aws-endpoint = { path = "../aws-endpoint" } aws-http = { path = "../aws-http" } +aws-runtime = { path = "../aws-runtime" } +aws-sigv4 = { path = "../aws-sigv4" } aws-sig-auth = { path = "../aws-sig-auth" } aws-smithy-checksums = { path = "../../../rust-runtime/aws-smithy-checksums" } aws-smithy-client = { path = "../../../rust-runtime/aws-smithy-client" } @@ -39,6 +41,7 @@ tracing = "0.1" aws-credential-types = { path = "../aws-credential-types", features = ["test-util"] } aws-smithy-client = { path = "../../../rust-runtime/aws-smithy-client", features = ["test-util"] } aws-smithy-http = { path = "../../../rust-runtime/aws-smithy-http", features = ["rt-tokio"] } +aws-smithy-runtime = { path = "../../../rust-runtime/aws-smithy-runtime" } tempfile = "3.2.0" tracing-subscriber = { version = "0.3.15", features = ["env-filter"] } diff --git a/aws/rust-runtime/aws-inlineable/src/glacier_checksums.rs b/aws/rust-runtime/aws-inlineable/src/glacier_checksums.rs index c60e4d02fe86b78b78c52472eb2cbb45fb554ca9..d22d28ed92f941f40980680100b3287267d512b1 100644 --- a/aws/rust-runtime/aws-inlineable/src/glacier_checksums.rs +++ b/aws/rust-runtime/aws-inlineable/src/glacier_checksums.rs @@ -3,6 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +// TODO(enableNewSmithyRuntime): Delete this file when cleaning up middleware + use aws_sig_auth::signer::SignableBody; use aws_smithy_http::body::SdkBody; use aws_smithy_http::byte_stream::{self, ByteStream}; diff --git a/aws/rust-runtime/aws-inlineable/src/glacier_interceptors.rs b/aws/rust-runtime/aws-inlineable/src/glacier_interceptors.rs new file mode 100644 index 0000000000000000000000000000000000000000..3dddba4381842915db333892c83a77deed2bb785 --- /dev/null +++ b/aws/rust-runtime/aws-inlineable/src/glacier_interceptors.rs @@ -0,0 +1,388 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +// This code is referenced in generated code, so the compiler doesn't realize it is used. +#![allow(dead_code)] + +use aws_runtime::auth::sigv4::SigV4OperationSigningConfig; +use aws_sigv4::http_request::SignableBody; +use aws_smithy_http::body::SdkBody; +use aws_smithy_http::byte_stream; +use aws_smithy_runtime_api::client::interceptors::{ + BeforeSerializationInterceptorContextMut, BeforeTransmitInterceptorContextMut, BoxError, + Interceptor, +}; +use aws_smithy_runtime_api::client::orchestrator::LoadedRequestBody; +use aws_smithy_runtime_api::config_bag::ConfigBag; +use bytes::Bytes; +use http::header::{HeaderName, HeaderValue}; +use http::Request; +use ring::digest::{Context, Digest, SHA256}; +use std::fmt; +use std::marker::PhantomData; + +/// The default account ID when none is set on an input +const DEFAULT_ACCOUNT_ID: &str = "-"; + +const TREE_HASH_HEADER: &str = "x-amz-sha256-tree-hash"; +const X_AMZ_CONTENT_SHA256: &str = "x-amz-content-sha256"; +const API_VERSION_HEADER: &str = "x-amz-glacier-version"; + +/// Adds an account ID autofill method to generated input structs +/// +/// Some Glacier operations have an account ID field that needs to get defaulted to `-` if not set. +/// This trait is implemented via codegen customization for those operation inputs so that +/// the [`GlacierAccountIdAutofillInterceptor`] can do this defaulting. +pub(crate) trait GlacierAccountId: fmt::Debug { + /// Returns a mutable reference to the account ID field + fn account_id_mut(&mut self) -> &mut Option; + + /// Autofills the account ID with the default if not set + fn autofill_account_id(&mut self) { + let account_id = self.account_id_mut(); + if account_id.as_deref().unwrap_or_default().is_empty() { + *account_id = Some(DEFAULT_ACCOUNT_ID.into()); + } + } +} + +/// Autofills account ID input fields with a default if no value is set +#[derive(Debug)] +pub(crate) struct GlacierAccountIdAutofillInterceptor { + _phantom: PhantomData, +} + +impl GlacierAccountIdAutofillInterceptor { + /// Constructs a new [`GlacierAccountIdAutofillInterceptor`] + pub(crate) fn new() -> Self { + Self { + _phantom: Default::default(), + } + } +} + +impl Interceptor + for GlacierAccountIdAutofillInterceptor +{ + fn modify_before_serialization( + &self, + context: &mut BeforeSerializationInterceptorContextMut<'_>, + _cfg: &mut ConfigBag, + ) -> Result<(), BoxError> { + let erased_input = context.input_mut(); + let input: &mut I = erased_input + .downcast_mut() + .expect("typechecked at registration"); + input.autofill_account_id(); + Ok(()) + } +} + +/// Attaches the `x-amz-glacier-version` header to the request +#[derive(Debug)] +pub(crate) struct GlacierApiVersionInterceptor { + api_version: &'static str, +} + +impl GlacierApiVersionInterceptor { + /// Constructs a new [`GlacierApiVersionInterceptor`] with the given API version. + pub(crate) fn new(api_version: &'static str) -> Self { + Self { api_version } + } +} + +impl Interceptor for GlacierApiVersionInterceptor { + fn modify_before_signing( + &self, + context: &mut BeforeTransmitInterceptorContextMut<'_>, + _cfg: &mut ConfigBag, + ) -> Result<(), BoxError> { + context.request_mut().headers_mut().insert( + API_VERSION_HEADER, + HeaderValue::from_static(self.api_version), + ); + Ok(()) + } +} + +/// Adds a glacier tree hash checksum to the HTTP Request +#[derive(Debug, Default)] +pub(crate) struct GlacierTreeHashHeaderInterceptor; + +impl Interceptor for GlacierTreeHashHeaderInterceptor { + fn modify_before_serialization( + &self, + _context: &mut BeforeSerializationInterceptorContextMut<'_>, + cfg: &mut ConfigBag, + ) -> Result<(), BoxError> { + // Request the request body to be loaded into memory immediately after serialization + // so that it can be checksummed before signing and transmit + cfg.put(LoadedRequestBody::Requested); + Ok(()) + } + + fn modify_before_retry_loop( + &self, + context: &mut BeforeTransmitInterceptorContextMut<'_>, + cfg: &mut ConfigBag, + ) -> Result<(), BoxError> { + let maybe_loaded_body = cfg.get::(); + if let Some(LoadedRequestBody::Loaded(body)) = maybe_loaded_body { + let content_sha256 = add_checksum_treehash(context.request_mut(), body)?; + + // Override the signing payload with this precomputed hash + let mut signing_config = cfg + .get::() + .ok_or("SigV4OperationSigningConfig not found")? + .clone(); + signing_config.signing_options.payload_override = + Some(SignableBody::Precomputed(content_sha256)); + cfg.put(signing_config); + } else { + return Err( + "the request body wasn't loaded into memory before the retry loop, \ + so the Glacier tree hash header can't be computed" + .into(), + ); + } + Ok(()) + } +} + +/// Adds a glacier tree hash checksum to the HTTP Request +/// +/// This handles two cases: +/// 1. A body which is retryable: the body will be streamed through a digest calculator, limiting memory usage. +/// 2. A body which is not retryable: the body will be converted into `Bytes`, then streamed through a digest calculator. +/// +/// The actual checksum algorithm will first compute a SHA256 checksum for each 1MB chunk. Then, a tree +/// will be assembled, recursively pairing neighboring chunks and computing their combined checksum. The 1 leftover +/// chunk (if it exists) is paired at the end. +/// +/// See for more information. +fn add_checksum_treehash( + request: &mut Request, + body: &Bytes, +) -> Result { + let (full_body, hashes) = compute_hashes(body, MEGABYTE)?; + let tree_hash = hex::encode(compute_hash_tree(hashes)); + let complete_hash = hex::encode(full_body); + if !request.headers().contains_key(TREE_HASH_HEADER) { + request.headers_mut().insert( + HeaderName::from_static(TREE_HASH_HEADER), + tree_hash.parse().expect("hash must be valid header"), + ); + } + if !request.headers().contains_key(X_AMZ_CONTENT_SHA256) { + request.headers_mut().insert( + HeaderName::from_static(X_AMZ_CONTENT_SHA256), + complete_hash.parse().expect("hash must be valid header"), + ); + } + Ok(complete_hash) +} + +const MEGABYTE: usize = 1024 * 1024; +fn compute_hashes( + body: &Bytes, + chunk_size: usize, +) -> Result<(Digest, Vec), byte_stream::error::Error> { + let mut hashes = Vec::new(); + let mut full_body = Context::new(&SHA256); + for chunk in body.chunks(chunk_size) { + let mut local = Context::new(&SHA256); + local.update(chunk); + hashes.push(local.finish()); + + full_body.update(chunk); + } + if hashes.is_empty() { + hashes.push(Context::new(&SHA256).finish()) + } + Ok((full_body.finish(), hashes)) +} + +/// Compute the glacier tree hash for a vector of hashes. +/// +/// Adjacent hashes are combined into a single hash. This process occurs recursively until only 1 hash remains. +/// +/// See for more information. +fn compute_hash_tree(mut hashes: Vec) -> Digest { + assert!( + !hashes.is_empty(), + "even an empty file will produce a digest. this function assumes that hashes is non-empty" + ); + while hashes.len() > 1 { + let next = hashes.chunks(2).into_iter().map(|chunk| match *chunk { + [left, right] => { + let mut ctx = Context::new(&SHA256); + ctx.update(left.as_ref()); + ctx.update(right.as_ref()); + ctx.finish() + } + [last] => last, + _ => unreachable!(), + }); + hashes = next.collect(); + } + hashes[0] +} + +#[cfg(test)] +mod account_id_autofill_tests { + use super::*; + use aws_smithy_runtime_api::client::interceptors::InterceptorContext; + use aws_smithy_runtime_api::type_erasure::TypedBox; + + #[test] + fn autofill_account_id() { + #[derive(Debug)] + struct SomeInput { + account_id: Option, + } + impl GlacierAccountId for SomeInput { + fn account_id_mut(&mut self) -> &mut Option { + &mut self.account_id + } + } + + let mut cfg = ConfigBag::base(); + let mut context = + InterceptorContext::new(TypedBox::new(SomeInput { account_id: None }).erase()); + let mut context = BeforeSerializationInterceptorContextMut::from(&mut context); + let interceptor = GlacierAccountIdAutofillInterceptor::::new(); + interceptor + .modify_before_serialization(&mut context, &mut cfg) + .expect("success"); + assert_eq!( + DEFAULT_ACCOUNT_ID, + context + .input() + .downcast_ref::() + .unwrap() + .account_id + .as_ref() + .expect("it is set now") + ); + } +} + +#[cfg(test)] +mod api_version_tests { + use super::*; + use aws_smithy_runtime_api::client::interceptors::InterceptorContext; + use aws_smithy_runtime_api::type_erasure::TypedBox; + + #[test] + fn api_version_interceptor() { + let mut cfg = ConfigBag::base(); + let mut context = InterceptorContext::new(TypedBox::new("dontcare").erase()); + context.set_request(http::Request::builder().body(SdkBody::empty()).unwrap()); + let mut context = BeforeTransmitInterceptorContextMut::from(&mut context); + + let interceptor = GlacierApiVersionInterceptor::new("some-version"); + interceptor + .modify_before_signing(&mut context, &mut cfg) + .expect("success"); + + assert_eq!( + "some-version", + context + .request() + .headers() + .get(API_VERSION_HEADER) + .expect("header set") + ); + } +} + +#[cfg(test)] +mod treehash_checksum_tests { + use super::*; + + #[test] + fn compute_digests() { + { + let body = Bytes::from_static(b"1234"); + let hashes = compute_hashes(&body, 1).expect("succeeds").1; + assert_eq!(hashes.len(), 4); + } + { + let body = Bytes::from_static(b"1234"); + let hashes = compute_hashes(&body, 2).expect("succeeds").1; + assert_eq!(hashes.len(), 2); + } + { + let body = Bytes::from_static(b"12345"); + let hashes = compute_hashes(&body, 3).expect("succeeds").1; + assert_eq!(hashes.len(), 2); + } + { + let body = Bytes::from_static(b"11221122"); + let hashes = compute_hashes(&body, 2).expect("succeeds").1; + assert_eq!(hashes[0].as_ref(), hashes[2].as_ref()); + } + } + + #[test] + fn empty_body_computes_digest() { + let body = Bytes::from_static(b""); + let (_, hashes) = compute_hashes(&body, 2).expect("succeeds"); + assert_eq!(hashes.len(), 1); + } + + #[test] + fn compute_tree_digest() { + macro_rules! hash { + ($($inp:expr),*) => { + { + let mut ctx = ring::digest::Context::new(&ring::digest::SHA256); + $( + ctx.update($inp.as_ref()); + )* + ctx.finish() + } + } + } + let body = Bytes::from_static(b"1234567891011"); + let (complete, hashes) = compute_hashes(&body, 3).expect("succeeds"); + assert_eq!(hashes.len(), 5); + assert_eq!(complete.as_ref(), hash!("1234567891011").as_ref()); + let final_digest = compute_hash_tree(hashes); + let expected_digest = hash!( + hash!( + hash!(hash!("123"), hash!("456")), + hash!(hash!("789"), hash!("101")) + ), + hash!("1") + ); + assert_eq!(expected_digest.as_ref(), final_digest.as_ref()); + } + + #[test] + fn hash_value_test() { + // the test data consists of an 11 byte sequence, repeated. Since the sequence length is + // relatively prime with 1 megabyte, we can ensure that chunks will all have different hashes. + let base_seq = b"01245678912"; + let total_size = MEGABYTE * 101 + 500; + let mut test_data = vec![]; + while test_data.len() < total_size { + test_data.extend_from_slice(base_seq) + } + let test_data = Bytes::from(test_data); + + let mut http_req = http::Request::builder() + .uri("http://example.com/hello") + .body(SdkBody::taken()) // the body isn't used by add_checksum_treehash + .unwrap(); + + add_checksum_treehash(&mut http_req, &test_data).expect("should succeed"); + // hash value verified with AWS CLI + assert_eq!( + http_req.headers().get(TREE_HASH_HEADER).unwrap(), + "3d417484359fc9f5a3bafd576dc47b8b2de2bf2d4fdac5aa2aff768f2210d386" + ); + } +} diff --git a/aws/rust-runtime/aws-inlineable/src/lib.rs b/aws/rust-runtime/aws-inlineable/src/lib.rs index c2414b6b5fc087818a5b2a999a68670a97d6af6e..c4b7d2368469cd27a1ccd0794ffbf99e4731af35 100644 --- a/aws/rust-runtime/aws-inlineable/src/lib.rs +++ b/aws/rust-runtime/aws-inlineable/src/lib.rs @@ -34,6 +34,9 @@ pub mod s3_request_id; /// Glacier-specific checksumming behavior pub mod glacier_checksums; +/// Glacier-specific behavior +pub mod glacier_interceptors; + /// Default middleware stack for AWS services pub mod middleware; diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/ServiceSpecificDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/ServiceSpecificDecorator.kt index f540152a25c977a22334b3c143b97899a0618176..cc9c34797d2ec826e4d0168345ae768834855657 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/ServiceSpecificDecorator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/ServiceSpecificDecorator.kt @@ -14,6 +14,7 @@ import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientProtocolMap import software.amazon.smithy.rust.codegen.client.smithy.endpoint.EndpointCustomization +import software.amazon.smithy.rust.codegen.client.smithy.generators.OperationRuntimePluginCustomization import software.amazon.smithy.rust.codegen.client.smithy.generators.ServiceRuntimePluginCustomization import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ConfigCustomization import software.amazon.smithy.rust.codegen.client.smithy.generators.error.ErrorCustomization @@ -146,4 +147,12 @@ class ServiceSpecificDecorator( ): ProtocolTestGenerator = baseGenerator.maybeApply(codegenContext.serviceShape) { delegateTo.protocolTestGenerator(codegenContext, baseGenerator) } + + override fun operationRuntimePluginCustomizations( + codegenContext: ClientCodegenContext, + operation: OperationShape, + baseCustomizations: List, + ): List = baseCustomizations.maybeApply(codegenContext.serviceShape) { + delegateTo.operationRuntimePluginCustomizations(codegenContext, operation, baseCustomizations) + } } diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/glacier/AccountIdAutofill.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/glacier/AccountIdAutofill.kt index 63ef01d1b182b4f6c433973826f8cff7dabcbc48..19484b07b716d4dfe26b14021a2717e1fc43dc35 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/glacier/AccountIdAutofill.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/glacier/AccountIdAutofill.kt @@ -14,6 +14,8 @@ import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationCustom import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationSection import software.amazon.smithy.rust.codegen.core.util.inputShape +// TODO(enableNewSmithyRuntime): Delete this file when cleaning up middleware. + class AccountIdAutofill : OperationCustomization() { override fun mutSelf(): Boolean = true override fun consumesSelf(): Boolean = false diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/glacier/ApiVersionHeader.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/glacier/ApiVersionHeader.kt index 8772de0ce9112d41de6df658504181dcb38251d3..91dece494e05e438ecc678b7b7bd0844dc619ba1 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/glacier/ApiVersionHeader.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/glacier/ApiVersionHeader.kt @@ -13,6 +13,8 @@ import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationCustom import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationSection import software.amazon.smithy.rust.codegen.core.util.dq +// TODO(enableNewSmithyRuntime): Delete this file when cleaning up middleware. + class ApiVersionHeader( /** * ApiVersion diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/glacier/GlacierDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/glacier/GlacierDecorator.kt index 5ba71e2f20cb250e13542f0ea92fc02b5c2cfaa2..0110331b489ff334c1d8d7a1a44e5ed57010349c 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/glacier/GlacierDecorator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/glacier/GlacierDecorator.kt @@ -6,21 +6,174 @@ package software.amazon.smithy.rustsdk.customize.glacier import software.amazon.smithy.model.shapes.OperationShape +import software.amazon.smithy.model.shapes.ShapeId +import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator +import software.amazon.smithy.rust.codegen.client.smithy.generators.OperationRuntimePluginCustomization +import software.amazon.smithy.rust.codegen.client.smithy.generators.OperationRuntimePluginSection +import software.amazon.smithy.rust.codegen.client.smithy.generators.ServiceRuntimePluginCustomization +import software.amazon.smithy.rust.codegen.client.smithy.generators.ServiceRuntimePluginSection +import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency +import software.amazon.smithy.rust.codegen.core.rustlang.Writable +import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate +import software.amazon.smithy.rust.codegen.core.rustlang.writable +import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig +import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationCustomization +import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureCustomization +import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureSection +import software.amazon.smithy.rust.codegen.core.smithy.traits.SyntheticInputTrait +import software.amazon.smithy.rust.codegen.core.util.dq +import software.amazon.smithy.rust.codegen.core.util.hasTrait +import software.amazon.smithy.rust.codegen.core.util.letIf +import software.amazon.smithy.rustsdk.AwsCargoDependency +import software.amazon.smithy.rustsdk.InlineAwsDependency +/** + * Glacier has three required customizations: + * + * 1. Add the `x-amz-glacier-version` header to all requests + * 2. For operations with an `account_id` field, autofill that field with `-` if it is not set by the customer. + * This is required because the account ID is sent across as part of the URL, and `-` has been pre-established + * "no account ID" in the Glacier service. + * 3. The `UploadArchive` and `UploadMultipartPart` operations require tree hash headers to be + * calculated and added to the request. + * + * This decorator wires up these three customizations. + */ class GlacierDecorator : ClientCodegenDecorator { override val name: String = "Glacier" override val order: Byte = 0 + // TODO(enableNewSmithyRuntime): Delete the operation customizations when cleaning up middleware override fun operationCustomizations( codegenContext: ClientCodegenContext, operation: OperationShape, baseCustomizations: List, - ): List = baseCustomizations + listOfNotNull( - ApiVersionHeader(codegenContext.serviceShape.version), - TreeHashHeader.forOperation(operation, codegenContext.runtimeConfig), - AccountIdAutofill.forOperation(operation, codegenContext.model), - ) + ): List = baseCustomizations.letIf(codegenContext.smithyRuntimeMode.generateMiddleware) { + it + listOfNotNull( + ApiVersionHeader(codegenContext.serviceShape.version), + TreeHashHeader.forOperation(operation, codegenContext.runtimeConfig), + AccountIdAutofill.forOperation(operation, codegenContext.model), + ) + } + + override fun structureCustomizations( + codegenContext: ClientCodegenContext, + baseCustomizations: List, + ): List = baseCustomizations.letIf(codegenContext.smithyRuntimeMode.generateOrchestrator) { + it + listOf(GlacierAccountIdCustomization(codegenContext)) + } + + override fun serviceRuntimePluginCustomizations( + codegenContext: ClientCodegenContext, + baseCustomizations: List, + ): List = + baseCustomizations.letIf(codegenContext.smithyRuntimeMode.generateOrchestrator) { + it + listOf(GlacierApiVersionCustomization(codegenContext)) + } + + override fun operationRuntimePluginCustomizations( + codegenContext: ClientCodegenContext, + operation: OperationShape, + baseCustomizations: List, + ): List = + baseCustomizations.letIf(codegenContext.smithyRuntimeMode.generateOrchestrator) { + it + listOf(GlacierOperationInterceptorsCustomization(codegenContext)) + } +} + +/** Implements the `GlacierAccountId` trait for inputs that have an `account_id` field */ +private class GlacierAccountIdCustomization(private val codegenContext: ClientCodegenContext) : + StructureCustomization() { + override fun section(section: StructureSection): Writable = writable { + if (section is StructureSection.AdditionalTraitImpls && section.shape.inputWithAccountId()) { + val inlineModule = inlineModule(codegenContext.runtimeConfig) + rustTemplate( + """ + impl #{GlacierAccountId} for ${section.structName} { + fn account_id_mut(&mut self) -> &mut Option { + &mut self.account_id + } + } + """, + "GlacierAccountId" to inlineModule.resolve("GlacierAccountId"), + ) + } + } } + +/** Adds the `x-amz-glacier-version` header to all requests */ +private class GlacierApiVersionCustomization(private val codegenContext: ClientCodegenContext) : + ServiceRuntimePluginCustomization() { + override fun section(section: ServiceRuntimePluginSection): Writable = writable { + if (section is ServiceRuntimePluginSection.AdditionalConfig) { + val apiVersion = codegenContext.serviceShape.version + section.registerInterceptor(codegenContext.runtimeConfig, this) { + rustTemplate( + "#{Interceptor}::new(${apiVersion.dq()})", + "Interceptor" to inlineModule(codegenContext.runtimeConfig).resolve("GlacierApiVersionInterceptor"), + ) + } + } + } +} + +/** + * Adds two interceptors: + * 1. `GlacierAccountIdAutofillInterceptor`: Uses the `GlacierAccountId` trait to correctly autofill the account ID field + * 2. `GlacierSetPrecomputedSignableBodyInterceptor`: Reuses the tree hash computation during signing rather than allowing + * the `aws-sigv4` module to recalculate the payload hash. + */ +private class GlacierOperationInterceptorsCustomization(private val codegenContext: ClientCodegenContext) : + OperationRuntimePluginCustomization() { + override fun section(section: OperationRuntimePluginSection): Writable = writable { + if (section is OperationRuntimePluginSection.AdditionalConfig) { + val inputShape = codegenContext.model.expectShape(section.operationShape.inputShape) as StructureShape + val inlineModule = inlineModule(codegenContext.runtimeConfig) + if (inputShape.inputWithAccountId()) { + section.registerInterceptor(codegenContext.runtimeConfig, this) { + rustTemplate( + "#{Interceptor}::<#{Input}>::new()", + "Interceptor" to inlineModule.resolve("GlacierAccountIdAutofillInterceptor"), + "Input" to codegenContext.symbolProvider.toSymbol(inputShape), + ) + } + } + if (section.operationShape.requiresTreeHashHeader()) { + section.registerInterceptor(codegenContext.runtimeConfig, this) { + rustTemplate( + "#{Interceptor}::default()", + "Interceptor" to inlineModule.resolve("GlacierTreeHashHeaderInterceptor"), + ) + } + } + } + } +} + +/** True when the operation requires tree hash headers */ +private fun OperationShape.requiresTreeHashHeader(): Boolean = + id == ShapeId.from("com.amazonaws.glacier#UploadArchive") || + id == ShapeId.from("com.amazonaws.glacier#UploadMultipartPart") + +private fun StructureShape.inputWithAccountId(): Boolean = + hasTrait() && members().any { it.memberName.lowercase() == "accountid" } + +private fun inlineModule(runtimeConfig: RuntimeConfig) = RuntimeType.forInlineDependency( + InlineAwsDependency.forRustFile( + "glacier_interceptors", + additionalDependency = glacierInterceptorDependencies(runtimeConfig).toTypedArray(), + ), +) + +private fun glacierInterceptorDependencies(runtimeConfig: RuntimeConfig) = listOf( + AwsCargoDependency.awsRuntime(runtimeConfig), + AwsCargoDependency.awsSigv4(runtimeConfig), + CargoDependency.Bytes, + CargoDependency.Hex, + CargoDependency.Ring, + CargoDependency.smithyHttp(runtimeConfig), + CargoDependency.smithyRuntimeApi(runtimeConfig), +) diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/glacier/TreeHashHeader.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/glacier/TreeHashHeader.kt index c97357a2fd90f6e68dc6fca7aaf13c2f5ca561ba..7a972b93aa76391556242336a9cc9816ab3368e5 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/glacier/TreeHashHeader.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/glacier/TreeHashHeader.kt @@ -18,6 +18,8 @@ import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationSectio import software.amazon.smithy.rust.codegen.core.smithy.generators.operationBuildError import software.amazon.smithy.rustsdk.InlineAwsDependency +// TODO(enableNewSmithyRuntime): Delete this file when cleaning up middleware. + val TreeHashDependencies = listOf( CargoDependency.Ring, CargoDependency.TokioStream, diff --git a/aws/sdk/integration-tests/glacier/tests/custom-headers.rs b/aws/sdk/integration-tests/glacier/tests/custom-headers.rs index 00163b0a81d8b8937559304d5235d385e77dc346..941ed0a999a3420303983252fd8b069719963114 100644 --- a/aws/sdk/integration-tests/glacier/tests/custom-headers.rs +++ b/aws/sdk/integration-tests/glacier/tests/custom-headers.rs @@ -39,3 +39,49 @@ async fn set_correct_headers() { ], )); } + +#[tokio::test] +async fn autofill_account_id() { + let (conn, handler) = capture_request(None); + let conf = aws_sdk_glacier::Config::builder() + .region(Region::new("us-east-1")) + .credentials_provider(Credentials::for_tests()) + .http_connector(conn) + .build(); + + let client = aws_sdk_glacier::Client::from_conf(conf); + let _resp = client + .abort_multipart_upload() + .vault_name("vault") + .upload_id("some/upload/id") + .send() + .await; + let req = handler.expect_request(); + assert_eq!( + "/-/vaults/vault/multipart-uploads/some%2Fupload%2Fid", + req.uri().path() + ); +} + +#[tokio::test] +async fn api_version_set() { + let (conn, handler) = capture_request(None); + let conf = aws_sdk_glacier::Config::builder() + .region(Region::new("us-east-1")) + .credentials_provider(Credentials::for_tests()) + .http_connector(conn) + .build(); + + let client = aws_sdk_glacier::Client::from_conf(conf); + let _resp = client + .abort_multipart_upload() + .vault_name("vault") + .upload_id("some/upload/id") + .send() + .await; + let req = handler.expect_request(); + assert_ok(validate_headers( + req.headers(), + [("x-amz-glacier-version", "2012-06-01")], + )); +} diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ClientProtocolGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ClientProtocolGenerator.kt index 02f281dfd4dc7d6ed3a20df480a0eb78fccbf519..1c8a69bece26164b2d0b5f22c5dd9a35739ab8d7 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ClientProtocolGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ClientProtocolGenerator.kt @@ -74,7 +74,12 @@ open class ClientProtocolGenerator( } } - renderOperationStruct(operationWriter, operationShape, operationCustomizations, codegenDecorator) + renderOperationStruct( + operationWriter, + operationShape, + operationCustomizations, + codegenDecorator, + ) } private fun renderOperationStruct( @@ -183,7 +188,7 @@ open class ClientProtocolGenerator( ResponseDeserializerGenerator(codegenContext, protocol) .render(operationWriter, operationShape, operationCustomizations) RequestSerializerGenerator(codegenContext, protocol, bodyGenerator) - .render(operationWriter, operationShape, operationCustomizations) + .render(operationWriter, operationShape) EndpointParamsInterceptorGenerator(codegenContext) .render(operationWriter, operationShape) diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/RequestSerializerGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/RequestSerializerGenerator.kt index 74cac8c60e75cda1818f810cb88c0501c5df9e6d..22870147b5f2bdee7c81a25ae9232f95530ecfaa 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/RequestSerializerGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/RequestSerializerGenerator.kt @@ -17,7 +17,6 @@ import software.amazon.smithy.rust.codegen.core.rustlang.rust import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate import software.amazon.smithy.rust.codegen.core.rustlang.writable import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType -import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationCustomization import software.amazon.smithy.rust.codegen.core.smithy.generators.protocol.ProtocolPayloadGenerator import software.amazon.smithy.rust.codegen.core.smithy.protocols.HttpLocation import software.amazon.smithy.rust.codegen.core.smithy.protocols.Protocol @@ -52,7 +51,7 @@ class RequestSerializerGenerator( } } - fun render(writer: RustWriter, operationShape: OperationShape, customizations: List) { + fun render(writer: RustWriter, operationShape: OperationShape) { val inputShape = operationShape.inputShape(codegenContext.model) val operationName = symbolProvider.toSymbol(operationShape).name val inputSymbol = symbolProvider.toSymbol(inputShape) diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ResponseDeserializerGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ResponseDeserializerGenerator.kt index f3bfe104b5d2126d3fdb15db07bc160cade6cbe3..d66f02c3da11cc6fcd55dfe85ea0ac96e3fbcf7a 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ResponseDeserializerGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ResponseDeserializerGenerator.kt @@ -99,7 +99,7 @@ class ResponseDeserializerGenerator( if !response.status().is_success() && response.status().as_u16() != $successCode { return None; } - Some(#{type_erase_result}(#{parse_streaming_response}(response)).into()) + Some(#{type_erase_result}(#{parse_streaming_response}(response))) } """, *codegenScope, @@ -118,7 +118,7 @@ class ResponseDeserializerGenerator( """ // For streaming operations, we only hit this case if its an error let body = response.body().bytes().expect("body loaded"); - #{type_erase_result}(#{parse_error}(response.status().as_u16(), response.headers(), body)).into() + #{type_erase_result}(#{parse_error}(response.status().as_u16(), response.headers(), body)) """, *codegenScope, "parse_error" to parserGenerator.parseErrorFn(operationShape, customizations), diff --git a/rust-runtime/aws-smithy-runtime-api/Cargo.toml b/rust-runtime/aws-smithy-runtime-api/Cargo.toml index 8fe513faf18aeadbd984febd32bb8145442772fb..726b923482cb3e9e070589eca16fe7561cad142b 100644 --- a/rust-runtime/aws-smithy-runtime-api/Cargo.toml +++ b/rust-runtime/aws-smithy-runtime-api/Cargo.toml @@ -17,6 +17,7 @@ http-auth = ["dep:zeroize"] aws-smithy-async = { path = "../aws-smithy-async" } aws-smithy-http = { path = "../aws-smithy-http" } aws-smithy-types = { path = "../aws-smithy-types" } +bytes = "1" http = "0.2.3" tokio = { version = "1.25", features = ["sync"] } tracing = "0.1" diff --git a/rust-runtime/aws-smithy-runtime-api/external-types.toml b/rust-runtime/aws-smithy-runtime-api/external-types.toml index ab041b9f7ec1fe5833ec1c09f99d86af288e21ad..91e6e35f0c3a6e6a7c1f1f02f5dd84434b547d11 100644 --- a/rust-runtime/aws-smithy-runtime-api/external-types.toml +++ b/rust-runtime/aws-smithy-runtime-api/external-types.toml @@ -3,6 +3,8 @@ allowed_external_types = [ "aws_smithy_types::*", "aws_smithy_http::*", + "bytes::bytes::Bytes", + "http::request::Request", "http::response::Response", "http::uri::Uri", diff --git a/rust-runtime/aws-smithy-runtime-api/src/client/orchestrator.rs b/rust-runtime/aws-smithy-runtime-api/src/client/orchestrator.rs index 314fd572492178ada618c0b1e242315f4c9d782c..3b9aaff2f3bbe7149be94f16a71a8c0e33329181 100644 --- a/rust-runtime/aws-smithy-runtime-api/src/client/orchestrator.rs +++ b/rust-runtime/aws-smithy-runtime-api/src/client/orchestrator.rs @@ -17,6 +17,7 @@ use aws_smithy_async::future::now_or_later::NowOrLater; use aws_smithy_async::rt::sleep::AsyncSleep; use aws_smithy_http::body::SdkBody; use aws_smithy_types::endpoint::Endpoint; +use bytes::Bytes; use std::fmt; use std::future::Future as StdFuture; use std::pin::Pin; @@ -100,6 +101,26 @@ impl RequestTime { } } +/// Informs the orchestrator on whether or not the request body needs to be loaded into memory before transmit. +/// +/// This enum gets placed into the `ConfigBag` to change the orchestrator behavior. +/// Immediately after serialization (before the `read_after_serialization` interceptor hook), +/// if it was set to `Requested` in the config bag, it will be replaced back into the config bag as +/// `Loaded` with the request body contents for use in later interceptors. +/// +/// This all happens before the attempt loop, so the loaded request body will remain available +/// for interceptors that run in any subsequent retry attempts. +#[non_exhaustive] +#[derive(Clone, Debug)] +pub enum LoadedRequestBody { + /// Don't attempt to load the request body into memory. + NotNeeded, + /// Attempt to load the request body into memory. + Requested, + /// The request body is already loaded. + Loaded(Bytes), +} + pub trait ConfigBagAccessors { fn auth_option_resolver_params(&self) -> &AuthOptionResolverParams; fn set_auth_option_resolver_params( @@ -145,8 +166,13 @@ pub trait ConfigBagAccessors { fn sleep_impl(&self) -> Option>; fn set_sleep_impl(&mut self, async_sleep: Option>); + + fn loaded_request_body(&self) -> &LoadedRequestBody; + fn set_loaded_request_body(&mut self, loaded_request_body: LoadedRequestBody); } +const NOT_NEEDED: LoadedRequestBody = LoadedRequestBody::NotNeeded; + impl ConfigBagAccessors for ConfigBag { fn auth_option_resolver_params(&self) -> &AuthOptionResolverParams { self.get::() @@ -281,4 +307,12 @@ impl ConfigBagAccessors for ConfigBag { self.unset::>(); } } + + fn loaded_request_body(&self) -> &LoadedRequestBody { + self.get::().unwrap_or(&NOT_NEEDED) + } + + fn set_loaded_request_body(&mut self, loaded_request_body: LoadedRequestBody) { + self.put::(loaded_request_body); + } } diff --git a/rust-runtime/aws-smithy-runtime-api/src/client/orchestrator/error.rs b/rust-runtime/aws-smithy-runtime-api/src/client/orchestrator/error.rs index 36b12d9d29e6ca35f8b6513f7df6d23631020784..2cc44a26477e7c62503935748dd807d45c61cc44 100644 --- a/rust-runtime/aws-smithy-runtime-api/src/client/orchestrator/error.rs +++ b/rust-runtime/aws-smithy-runtime-api/src/client/orchestrator/error.rs @@ -115,3 +115,12 @@ impl From for OrchestratorError { Self::operation(err) } } + +impl From for OrchestratorError +where + E: Debug + std::error::Error + 'static, +{ + fn from(err: aws_smithy_http::byte_stream::error::Error) -> Self { + Self::other(err.into()) + } +} diff --git a/rust-runtime/aws-smithy-runtime/src/client/orchestrator.rs b/rust-runtime/aws-smithy-runtime/src/client/orchestrator.rs index 72cd3cbef5cea0b2c79d7dc2cf6bcd8e59671e41..51cd3ee7106f0a83aeee0ed3dbc30cf1f56c8745 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/orchestrator.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/orchestrator.rs @@ -7,13 +7,18 @@ use self::auth::orchestrate_auth; use crate::client::orchestrator::endpoints::orchestrate_endpoint; use crate::client::orchestrator::http::read_body; use crate::client::timeout::{MaybeTimeout, ProvideMaybeTimeoutConfig, TimeoutKind}; +use aws_smithy_http::body::SdkBody; +use aws_smithy_http::byte_stream::ByteStream; use aws_smithy_http::result::SdkError; use aws_smithy_runtime_api::client::interceptors::context::{Error, Input, Output}; use aws_smithy_runtime_api::client::interceptors::{InterceptorContext, Interceptors}; -use aws_smithy_runtime_api::client::orchestrator::{BoxError, ConfigBagAccessors, HttpResponse}; +use aws_smithy_runtime_api::client::orchestrator::{ + BoxError, ConfigBagAccessors, HttpResponse, LoadedRequestBody, +}; use aws_smithy_runtime_api::client::retries::ShouldAttempt; use aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugins; use aws_smithy_runtime_api::config_bag::ConfigBag; +use std::mem; use tracing::{debug_span, Instrument}; mod auth; @@ -22,14 +27,18 @@ pub mod endpoints; mod http; pub mod interceptors; +macro_rules! halt { + ([$ctx:ident] => $err:expr) => {{ + $ctx.fail($err.into()); + return; + }}; +} + macro_rules! halt_on_err { ([$ctx:ident] => $expr:expr) => { match $expr { Ok(ok) => ok, - Err(err) => { - $ctx.fail(err); - return; - } + Err(err) => halt!([$ctx] => err), } }; } @@ -37,7 +46,7 @@ macro_rules! halt_on_err { macro_rules! continue_on_err { ([$ctx:ident] => $expr:expr) => { if let Err(err) = $expr { - $ctx.fail(err); + $ctx.fail(err.into()); } }; } @@ -80,33 +89,41 @@ fn apply_configuration( runtime_plugins: &RuntimePlugins, ) -> Result<(), BoxError> { runtime_plugins.apply_client_configuration(cfg, interceptors.client_interceptors_mut())?; - continue_on_err!([ctx] =>interceptors.client_read_before_execution(ctx, cfg).map_err(Into::into)); + continue_on_err!([ctx] =>interceptors.client_read_before_execution(ctx, cfg)); runtime_plugins .apply_operation_configuration(cfg, interceptors.operation_interceptors_mut())?; - continue_on_err!([ctx] => interceptors.operation_read_before_execution(ctx, cfg).map_err(Into::into)); + continue_on_err!([ctx] => interceptors.operation_read_before_execution(ctx, cfg)); Ok(()) } async fn try_op(ctx: &mut InterceptorContext, cfg: &mut ConfigBag, interceptors: &Interceptors) { // Before serialization - halt_on_err!([ctx] => interceptors.read_before_serialization(ctx, cfg).map_err(Into::into)); - halt_on_err!([ctx] => interceptors.modify_before_serialization(ctx, cfg).map_err(Into::into)); + halt_on_err!([ctx] => interceptors.read_before_serialization(ctx, cfg)); + halt_on_err!([ctx] => interceptors.modify_before_serialization(ctx, cfg)); // Serialization ctx.enter_serialization_phase(); { let request_serializer = cfg.request_serializer(); let input = ctx.take_input().expect("input set at this point"); - let request = - halt_on_err!([ctx] => request_serializer.serialize_input(input).map_err(Into::into)); + let request = halt_on_err!([ctx] => request_serializer.serialize_input(input)); ctx.set_request(request); } + // Load the request body into memory if configured to do so + if let LoadedRequestBody::Requested = cfg.loaded_request_body() { + let mut body = SdkBody::taken(); + mem::swap(&mut body, ctx.request_mut().body_mut()); + let loaded_body = halt_on_err!([ctx] => ByteStream::new(body).collect().await).into_bytes(); + *ctx.request_mut().body_mut() = SdkBody::from(loaded_body.clone()); + cfg.set_loaded_request_body(LoadedRequestBody::Loaded(loaded_body)); + } + // Before transmit ctx.enter_before_transmit_phase(); - halt_on_err!([ctx] => interceptors.read_after_serialization(ctx, cfg).map_err(Into::into)); - halt_on_err!([ctx] => interceptors.modify_before_retry_loop(ctx, cfg).map_err(Into::into)); + halt_on_err!([ctx] => interceptors.read_after_serialization(ctx, cfg)); + halt_on_err!([ctx] => interceptors.modify_before_retry_loop(ctx, cfg)); let retry_strategy = cfg.retry_strategy(); match retry_strategy.should_attempt_initial_request(cfg) { @@ -114,11 +131,11 @@ async fn try_op(ctx: &mut InterceptorContext, cfg: &mut ConfigBag, interceptors: Ok(ShouldAttempt::Yes) => { /* Keep going */ } // No, this request shouldn't be sent Ok(ShouldAttempt::No) => { - let err: Box = "The retry strategy indicates that an initial request shouldn't be made, but it did specify why.".into(); - halt_on_err!([ctx] => Err(err.into())); + let err: BoxError = "The retry strategy indicates that an initial request shouldn't be made, but it did specify why.".into(); + halt!([ctx] => err); } // No, we shouldn't make a request because... - Err(err) => halt_on_err!([ctx] => Err(err.into())), + Err(err) => halt!([ctx] => err), Ok(ShouldAttempt::YesAfterDelay(_)) => { unreachable!("Delaying the initial request is currently unsupported. If this feature is important to you, please file an issue in GitHub.") } @@ -135,7 +152,7 @@ async fn try_op(ctx: &mut InterceptorContext, cfg: &mut ConfigBag, interceptors: .await .expect("These are infallible; The retry strategy will decide whether to stop or not."); let retry_strategy = cfg.retry_strategy(); - let should_attempt = halt_on_err!([ctx] => retry_strategy.should_attempt_retry(ctx, cfg).map_err(Into::into)); + let should_attempt = halt_on_err!([ctx] => retry_strategy.should_attempt_retry(ctx, cfg)); match should_attempt { // Yes, let's retry the request ShouldAttempt::Yes => continue, @@ -156,30 +173,30 @@ async fn try_attempt( cfg: &mut ConfigBag, interceptors: &Interceptors, ) { - halt_on_err!([ctx] => interceptors.read_before_attempt(ctx, cfg).map_err(Into::into)); - halt_on_err!([ctx] => orchestrate_endpoint(ctx, cfg).map_err(Into::into)); - halt_on_err!([ctx] => interceptors.modify_before_signing(ctx, cfg).map_err(Into::into)); - halt_on_err!([ctx] => interceptors.read_before_signing(ctx, cfg).map_err(Into::into)); + halt_on_err!([ctx] => interceptors.read_before_attempt(ctx, cfg)); + halt_on_err!([ctx] => orchestrate_endpoint(ctx, cfg)); + halt_on_err!([ctx] => interceptors.modify_before_signing(ctx, cfg)); + halt_on_err!([ctx] => interceptors.read_before_signing(ctx, cfg)); - halt_on_err!([ctx] => orchestrate_auth(ctx, cfg).await.map_err(Into::into)); + halt_on_err!([ctx] => orchestrate_auth(ctx, cfg).await); - halt_on_err!([ctx] => interceptors.read_after_signing(ctx, cfg).map_err(Into::into)); - halt_on_err!([ctx] => interceptors.modify_before_transmit(ctx, cfg).map_err(Into::into)); - halt_on_err!([ctx] => interceptors.read_before_transmit(ctx, cfg).map_err(Into::into)); + halt_on_err!([ctx] => interceptors.read_after_signing(ctx, cfg)); + halt_on_err!([ctx] => interceptors.modify_before_transmit(ctx, cfg)); + halt_on_err!([ctx] => interceptors.read_before_transmit(ctx, cfg)); // The connection consumes the request but we need to keep a copy of it // within the interceptor context, so we clone it here. ctx.enter_transmit_phase(); let call_result = halt_on_err!([ctx] => { let request = ctx.take_request(); - cfg.connection().call(request).await.map_err(Into::into) + cfg.connection().call(request).await }); ctx.set_response(call_result); ctx.enter_before_deserialization_phase(); - halt_on_err!([ctx] => interceptors.read_after_transmit(ctx, cfg).map_err(Into::into)); - halt_on_err!([ctx] => interceptors.modify_before_deserialization(ctx, cfg).map_err(Into::into)); - halt_on_err!([ctx] => interceptors.read_before_deserialization(ctx, cfg).map_err(Into::into)); + halt_on_err!([ctx] => interceptors.read_after_transmit(ctx, cfg)); + halt_on_err!([ctx] => interceptors.modify_before_deserialization(ctx, cfg)); + halt_on_err!([ctx] => interceptors.read_before_deserialization(ctx, cfg)); ctx.enter_deserialization_phase(); let output_or_error = async { @@ -198,7 +215,7 @@ async fn try_attempt( ctx.set_output_or_error(output_or_error); ctx.enter_after_deserialization_phase(); - halt_on_err!([ctx] => interceptors.read_after_deserialization(ctx, cfg).map_err(Into::into)); + halt_on_err!([ctx] => interceptors.read_after_deserialization(ctx, cfg)); } async fn finally_attempt( @@ -206,8 +223,8 @@ async fn finally_attempt( cfg: &mut ConfigBag, interceptors: &Interceptors, ) { - continue_on_err!([ctx] => interceptors.modify_before_attempt_completion(ctx, cfg).map_err(Into::into)); - continue_on_err!([ctx] => interceptors.read_after_attempt(ctx, cfg).map_err(Into::into)); + continue_on_err!([ctx] => interceptors.modify_before_attempt_completion(ctx, cfg)); + continue_on_err!([ctx] => interceptors.read_after_attempt(ctx, cfg)); } async fn finally_op( @@ -215,8 +232,8 @@ async fn finally_op( cfg: &mut ConfigBag, interceptors: &Interceptors, ) { - continue_on_err!([ctx] => interceptors.modify_before_completion(ctx, cfg).map_err(Into::into)); - continue_on_err!([ctx] => interceptors.read_after_execution(ctx, cfg).map_err(Into::into)); + continue_on_err!([ctx] => interceptors.modify_before_completion(ctx, cfg)); + continue_on_err!([ctx] => interceptors.read_after_execution(ctx, cfg)); } #[cfg(all(test, feature = "test-util", feature = "anonymous-auth"))] diff --git a/tools/ci-scripts/check-aws-sdk-orchestrator-impl b/tools/ci-scripts/check-aws-sdk-orchestrator-impl index 8cd7d32e452d83685f967644fca7f750b6377d16..483e3075bfe03cb14e42cbb5e257e7c2bac1ff35 100755 --- a/tools/ci-scripts/check-aws-sdk-orchestrator-impl +++ b/tools/ci-scripts/check-aws-sdk-orchestrator-impl @@ -23,7 +23,6 @@ services_that_fail_compile=(\ # TODO(enableNewSmithyRuntime): Move these into `services_that_pass_tests` as more progress is made services_that_compile=(\ "dynamodb"\ - "glacier"\ "route53"\ "sts"\ ) @@ -32,6 +31,7 @@ services_that_pass_tests=(\ "config"\ "ec2"\ "ecs"\ + "glacier"\ "iam"\ "kms"\ "lambda"\