diff --git a/aws/rust-runtime/aws-inlineable/src/http_request_checksum.rs b/aws/rust-runtime/aws-inlineable/src/http_request_checksum.rs index b97b2d2bd0a98525c5bde0e91cbde80c0fbf0355..702fbe56c2d6b1c6a15f06ef28fe7502ec1ae347 100644 --- a/aws/rust-runtime/aws-inlineable/src/http_request_checksum.rs +++ b/aws/rust-runtime/aws-inlineable/src/http_request_checksum.rs @@ -77,7 +77,7 @@ impl RequestChecksumInterceptor { impl Interceptor for RequestChecksumInterceptor where - AP: Fn(&Input) -> Result, BoxError>, + AP: Fn(&Input) -> Result, BoxError> + Send + Sync, { fn read_before_serialization( &self, diff --git a/aws/rust-runtime/aws-inlineable/src/http_response_checksum.rs b/aws/rust-runtime/aws-inlineable/src/http_response_checksum.rs index 9a54d0019194d2bc0982eed894ea86f2ce521b07..2a98830d8e9d725c6c508a1f717d93002b1b6f3e 100644 --- a/aws/rust-runtime/aws-inlineable/src/http_response_checksum.rs +++ b/aws/rust-runtime/aws-inlineable/src/http_response_checksum.rs @@ -54,7 +54,7 @@ impl ResponseChecksumInterceptor { impl Interceptor for ResponseChecksumInterceptor where - VE: Fn(&Input) -> bool, + VE: Fn(&Input) -> bool + Send + Sync, { fn read_before_serialization( &self, diff --git a/aws/rust-runtime/aws-inlineable/src/route53_resource_id_preprocessor.rs b/aws/rust-runtime/aws-inlineable/src/route53_resource_id_preprocessor.rs index d8fa3f4c97222c9267d4d2d1777c9e72be2a4c7e..00f158317f9eac2ae799f868e164d13b63e563d1 100644 --- a/aws/rust-runtime/aws-inlineable/src/route53_resource_id_preprocessor.rs +++ b/aws/rust-runtime/aws-inlineable/src/route53_resource_id_preprocessor.rs @@ -69,7 +69,7 @@ where impl Interceptor for Route53ResourceIdInterceptor where - G: for<'a> Fn(&'a mut T) -> &'a mut Option, + G: for<'a> Fn(&'a mut T) -> &'a mut Option + Send + Sync, T: fmt::Debug + Send + Sync + 'static, { fn modify_before_serialization( diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/InterceptorConfigCustomization.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/InterceptorConfigCustomization.kt index 2dfec5159ed744a432cdbb297e8f45423c061f3a..9bb9e16ef3ff4086c996203ca98ed53c3124fe9f 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/InterceptorConfigCustomization.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/InterceptorConfigCustomization.kt @@ -91,7 +91,7 @@ class InterceptorConfigCustomization(codegenContext: ClientCodegenContext) : Con /// ## } /// ## } /// ``` - pub fn interceptor(mut self, interceptor: impl #{Interceptor} + Send + Sync + 'static) -> Self { + pub fn interceptor(mut self, interceptor: impl #{Interceptor} + 'static) -> Self { self.push_interceptor(#{SharedInterceptor}::new(interceptor)); self } diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/CustomizableOperationGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/CustomizableOperationGenerator.kt index 633a50985a2e397b40842e03b7797fa425164df1..4046d29e7fb6dbed121bb38992bb50b33deb666d 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/CustomizableOperationGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/CustomizableOperationGenerator.kt @@ -199,7 +199,7 @@ class CustomizableOperationGenerator( /// `map_request`, and `mutate_request` (the last two are implemented via interceptors under the hood). /// The order in which those user-specified operation interceptors are invoked should not be relied upon /// as it is an implementation detail. - pub fn interceptor(mut self, interceptor: impl #{Interceptor} + #{Send} + #{Sync} + 'static) -> Self { + pub fn interceptor(mut self, interceptor: impl #{Interceptor} + 'static) -> Self { self.interceptors.push(#{SharedInterceptor}::new(interceptor)); self } diff --git a/rust-runtime/aws-smithy-http/src/result.rs b/rust-runtime/aws-smithy-http/src/result.rs index bd6d2f1f3b8e1df64d0a6906a75159c7ebd79ed6..8d60c8d2ed759dbe55f6481a69148cea7f4ce130 100644 --- a/rust-runtime/aws-smithy-http/src/result.rs +++ b/rust-runtime/aws-smithy-http/src/result.rs @@ -232,11 +232,16 @@ impl DispatchFailure { self.source.is_user() } - /// Returns the optional error kind associated with an unclassified error - pub fn is_other(&self) -> Option { + /// Returns true if the error is an unclassified error. + pub fn is_other(&self) -> bool { self.source.is_other() } + /// Returns the optional error kind associated with an unclassified error + pub fn as_other(&self) -> Option { + self.source.as_other() + } + /// Returns the inner error if it is a connector error pub fn as_connector_error(&self) -> Option<&ConnectorError> { Some(&self.source) @@ -633,8 +638,13 @@ impl ConnectorError { matches!(self.kind, ConnectorErrorKind::User) } + /// Returns true if the error is an unclassified error. + pub fn is_other(&self) -> bool { + matches!(self.kind, ConnectorErrorKind::Other(..)) + } + /// Returns the optional error kind associated with an unclassified error - pub fn is_other(&self) -> Option { + pub fn as_other(&self) -> Option { match &self.kind { ConnectorErrorKind::Other(ek) => *ek, _ => None, diff --git a/rust-runtime/aws-smithy-http/src/retry.rs b/rust-runtime/aws-smithy-http/src/retry.rs index 30c86549267858deb4541a6ba47fb60a99619e8f..eaf7d0e0933f5425bc5eead5c7d35588059f32ad 100644 --- a/rust-runtime/aws-smithy-http/src/retry.rs +++ b/rust-runtime/aws-smithy-http/src/retry.rs @@ -45,7 +45,7 @@ impl DefaultResponseRetryClassifier { Err(SdkError::DispatchFailure(err)) => { if err.is_timeout() || err.is_io() { Err(RetryKind::Error(ErrorKind::TransientError)) - } else if let Some(ek) = err.is_other() { + } else if let Some(ek) = err.as_other() { Err(RetryKind::Error(ek)) } else { Err(RetryKind::UnretryableFailure) diff --git a/rust-runtime/aws-smithy-runtime-api/src/client/interceptors.rs b/rust-runtime/aws-smithy-runtime-api/src/client/interceptors.rs index a755a34e448b9ca7ca294185f9e59dea46157ab1..91d722526292510309c4e7e8087caf841b67fccf 100644 --- a/rust-runtime/aws-smithy-runtime-api/src/client/interceptors.rs +++ b/rust-runtime/aws-smithy-runtime-api/src/client/interceptors.rs @@ -13,12 +13,10 @@ use crate::client::interceptors::context::{ BeforeDeserializationInterceptorContextRef, BeforeSerializationInterceptorContextMut, BeforeSerializationInterceptorContextRef, BeforeTransmitInterceptorContextMut, BeforeTransmitInterceptorContextRef, FinalizerInterceptorContextMut, - FinalizerInterceptorContextRef, InterceptorContext, + FinalizerInterceptorContextRef, }; use crate::client::runtime_components::RuntimeComponents; use aws_smithy_types::config_bag::{ConfigBag, Storable, StoreReplace}; -use aws_smithy_types::error::display::DisplayErrorContext; -use context::{Error, Input, Output}; use std::fmt; use std::marker::PhantomData; use std::ops::Deref; @@ -38,9 +36,7 @@ macro_rules! interceptor_trait_fn { runtime_components: &RuntimeComponents, cfg: &mut ConfigBag, ) -> Result<(), BoxError> { - let _ctx = context; - let _rc = runtime_components; - let _cfg = cfg; + let (_ctx, _rc, _cfg) = (context, runtime_components, cfg); Ok(()) } }; @@ -52,9 +48,7 @@ macro_rules! interceptor_trait_fn { runtime_components: &RuntimeComponents, cfg: &mut ConfigBag, ) -> Result<(), BoxError> { - let _ctx = context; - let _rc = runtime_components; - let _cfg = cfg; + let (_ctx, _rc, _cfg) = (context, runtime_components, cfg); Ok(()) } }; @@ -70,7 +64,7 @@ macro_rules! interceptor_trait_fn { /// of the SDK ’s request execution pipeline. Hooks are either "read" hooks, which make it possible /// to read in-flight request or response messages, or "read/write" hooks, which make it possible /// to modify in-flight request or output messages. -pub trait Interceptor: fmt::Debug { +pub trait Interceptor: fmt::Debug + Send + Sync { /// A hook called at the start of an execution, before the SDK /// does anything else. /// @@ -78,14 +72,14 @@ pub trait Interceptor: fmt::Debug { /// between invocation of this hook and `after_execution` is very close /// to full duration of the execution. /// - /// **Available Information:** The [InterceptorContext::input()] is - /// **ALWAYS** available. Other information **WILL NOT** be available. + /// **Available Information:** The [`InterceptorContext::input`](context::InterceptorContext::input) + /// is **ALWAYS** available. Other information **WILL NOT** be available. /// /// **Error Behavior:** Errors raised by this hook will be stored /// until all interceptors have had their `before_execution` invoked. /// Other hooks will then be skipped and execution will jump to /// `modify_before_completion` with the raised error as the - /// [InterceptorContext::output_or_error()]. If multiple + /// [`InterceptorContext::output_or_error`](context::InterceptorContext::output_or_error). If multiple /// `before_execution` methods raise errors, the latest /// will be used and earlier ones will be logged and dropped. fn read_before_execution( @@ -93,8 +87,7 @@ pub trait Interceptor: fmt::Debug { context: &BeforeSerializationInterceptorContextRef<'_>, cfg: &mut ConfigBag, ) -> Result<(), BoxError> { - let _ctx = context; - let _cfg = cfg; + let (_ctx, _cfg) = (context, cfg); Ok(()) } @@ -110,14 +103,14 @@ pub trait Interceptor: fmt::Debug { **When:** This will **ALWAYS** be called once per execution, except when a failure occurs earlier in the request pipeline. - **Available Information:** The [InterceptorContext::input()] is + **Available Information:** The [`InterceptorContext::input`](context::InterceptorContext::input) is **ALWAYS** available. This request may have been modified by earlier `modify_before_serialization` hooks, and may be modified further by later hooks. Other information **WILL NOT** be available. **Error Behavior:** If errors are raised by this hook, execution will jump to `modify_before_completion` with the raised - error as the [InterceptorContext::output_or_error()]. + error as the [`InterceptorContext::output_or_error`](context::InterceptorContext::output_or_error). **Return Constraints:** The input message returned by this hook MUST be the same type of input message passed into this hook. @@ -138,12 +131,12 @@ pub trait Interceptor: fmt::Debug { duration between invocation of this hook and `after_serialization` is very close to the amount of time spent marshalling the request. - **Available Information:** The [InterceptorContext::input()] is + **Available Information:** The [`InterceptorContext::input`](context::InterceptorContext::input) is **ALWAYS** available. Other information **WILL NOT** be available. **Error Behavior:** If errors are raised by this hook, execution will jump to `modify_before_completion` with the raised - error as the [InterceptorContext::output_or_error()]. + error as the [`InterceptorContext::output_or_error`](context::InterceptorContext::output_or_error). " ); @@ -151,21 +144,20 @@ pub trait Interceptor: fmt::Debug { read_after_serialization, BeforeTransmitInterceptorContextRef, " - /// A hook called after the input message is marshalled into - /// a transport message. - /// - /// **When:** This will **ALWAYS** be called once per execution, except when a - /// failure occurs earlier in the request pipeline. The duration - /// between invocation of this hook and `before_serialization` is very - /// close to the amount of time spent marshalling the request. - /// - /// **Available Information:** The [InterceptorContext::input()] - /// and [InterceptorContext::request()] are **ALWAYS** available. - /// Other information **WILL NOT** be available. - /// - /// **Error Behavior:** If errors are raised by this hook, - /// execution will jump to `modify_before_completion` with the raised - /// error as the [InterceptorContext::output_or_error()]. + A hook called after the input message is marshalled into + a transport message. + + **When:** This will **ALWAYS** be called once per execution, except when a + failure occurs earlier in the request pipeline. The duration + between invocation of this hook and `before_serialization` is very + close to the amount of time spent marshalling the request. + + **Available Information:** The [`InterceptorContext::request`](context::InterceptorContext::request) + is **ALWAYS** available. Other information **WILL NOT** be available. + + **Error Behavior:** If errors are raised by this hook, + execution will jump to `modify_before_completion` with the raised + error as the [`InterceptorContext::output_or_error`](context::InterceptorContext::output_or_error). " ); @@ -177,13 +169,12 @@ pub trait Interceptor: fmt::Debug { has the ability to modify and return a new transport request message of the same type, except when a failure occurs earlier in the request pipeline. - **Available Information:** The [InterceptorContext::input()] - and [InterceptorContext::request()] are **ALWAYS** available. - Other information **WILL NOT** be available. + **Available Information:** The [`InterceptorContext::request`](context::InterceptorContext::request) + is **ALWAYS** available. Other information **WILL NOT** be available. **Error Behavior:** If errors are raised by this hook, execution will jump to `modify_before_completion` with the raised - error as the [InterceptorContext::output_or_error()]. + error as the [`InterceptorContext::output_or_error`](context::InterceptorContext::output_or_error). **Return Constraints:** The transport request message returned by this hook MUST be the same type of request message passed into this hook @@ -202,9 +193,8 @@ pub trait Interceptor: fmt::Debug { failure occurs earlier in the request pipeline. This method will be called multiple times in the event of retries. - **Available Information:** The [InterceptorContext::input()] - and [InterceptorContext::request()] are **ALWAYS** available. - Other information **WILL NOT** be available. In the event of retries, + **Available Information:** The [`InterceptorContext::request`](context::InterceptorContext::request) + is **ALWAYS** available. Other information **WILL NOT** be available. In the event of retries, the `InterceptorContext` will not include changes made in previous attempts (e.g. by request signers or other interceptors). @@ -212,7 +202,7 @@ pub trait Interceptor: fmt::Debug { until all interceptors have had their `before_attempt` invoked. Other hooks will then be skipped and execution will jump to `modify_before_attempt_completion` with the raised error as the - [InterceptorContext::output_or_error()]. If multiple + [`InterceptorContext::output_or_error`](context::InterceptorContext::output_or_error). If multiple `before_attempt` methods raise errors, the latest will be used and earlier ones will be logged and dropped. " @@ -230,18 +220,16 @@ pub trait Interceptor: fmt::Debug { failure occurs earlier in the request pipeline. This method may be called multiple times in the event of retries. - **Available Information:** The [InterceptorContext::input()] - and [InterceptorContext::request()] are **ALWAYS** available. - The `http::Request` may have been modified by earlier + **Available Information:** The [`InterceptorContext::request`](context::InterceptorContext::request) + is **ALWAYS** available. The `http::Request` may have been modified by earlier `modify_before_signing` hooks, and may be modified further by later hooks. Other information **WILL NOT** be available. In the event of retries, the `InterceptorContext` will not include changes made - in previous attempts - (e.g. by request signers or other interceptors). + in previous attempts (e.g. by request signers or other interceptors). **Error Behavior:** If errors are raised by this hook, execution will jump to `modify_before_attempt_completion` with - the raised error as the [InterceptorContext::output_or_error()]. + the raised error as the [`InterceptorContext::output_or_error`](context::InterceptorContext::output_or_error). **Return Constraints:** The transport request message returned by this hook MUST be the same type of request message passed into this hook @@ -262,15 +250,14 @@ pub trait Interceptor: fmt::Debug { invocation of this hook and `after_signing` is very close to the amount of time spent signing the request. - **Available Information:** The [InterceptorContext::input()] - and [InterceptorContext::request()] are **ALWAYS** available. + **Available Information:** The [`InterceptorContext::request`](context::InterceptorContext::request) is **ALWAYS** available. Other information **WILL NOT** be available. In the event of retries, the `InterceptorContext` will not include changes made in previous attempts (e.g. by request signers or other interceptors). **Error Behavior:** If errors are raised by this hook, execution will jump to `modify_before_attempt_completion` with - the raised error as the [InterceptorContext::output_or_error()]. + the raised error as the [`InterceptorContext::output_or_error`](context::InterceptorContext::output_or_error). " ); @@ -286,15 +273,14 @@ pub trait Interceptor: fmt::Debug { invocation of this hook and `before_signing` is very close to the amount of time spent signing the request. - **Available Information:** The [InterceptorContext::input()] - and [InterceptorContext::request()] are **ALWAYS** available. + **Available Information:** The [`InterceptorContext::request`](context::InterceptorContext::request) is **ALWAYS** available. Other information **WILL NOT** be available. In the event of retries, the `InterceptorContext` will not include changes made in previous attempts (e.g. by request signers or other interceptors). **Error Behavior:** If errors are raised by this hook, execution will jump to `modify_before_attempt_completion` with - the raised error as the [InterceptorContext::output_or_error()]. + the raised error as the [`InterceptorContext::output_or_error`](context::InterceptorContext::output_or_error). " ); @@ -302,26 +288,25 @@ pub trait Interceptor: fmt::Debug { mut modify_before_transmit, BeforeTransmitInterceptorContextMut, " - /// A hook called before the transport request message is sent to the - /// service. This method has the ability to modify and return - /// a new transport request message of the same type. - /// - /// **When:** This will **ALWAYS** be called once per attempt, except when a - /// failure occurs earlier in the request pipeline. This method may be - /// called multiple times in the event of retries. - /// - /// **Available Information:** The [InterceptorContext::input()] - /// and [InterceptorContext::request()] are **ALWAYS** available. - /// The `http::Request` may have been modified by earlier - /// `modify_before_transmit` hooks, and may be modified further by later - /// hooks. Other information **WILL NOT** be available. - /// In the event of retries, the `InterceptorContext` will not include - /// changes made in previous attempts (e.g. by request signers or + A hook called before the transport request message is sent to the + service. This method has the ability to modify and return + a new transport request message of the same type. + + **When:** This will **ALWAYS** be called once per attempt, except when a + failure occurs earlier in the request pipeline. This method may be + called multiple times in the event of retries. + + **Available Information:** The [`InterceptorContext::request`](context::InterceptorContext::request) + is **ALWAYS** available. The `http::Request` may have been modified by earlier + `modify_before_transmit` hooks, and may be modified further by later + hooks. Other information **WILL NOT** be available. + In the event of retries, the `InterceptorContext` will not include + changes made in previous attempts (e.g. by request signers or other interceptors). **Error Behavior:** If errors are raised by this hook, execution will jump to `modify_before_attempt_completion` with - the raised error as the [InterceptorContext::output_or_error()]. + the raised error as the [`InterceptorContext::output_or_error`](context::InterceptorContext::output_or_error). **Return Constraints:** The transport request message returned by this hook MUST be the same type of request message passed into this hook @@ -345,16 +330,15 @@ pub trait Interceptor: fmt::Debug { Depending on the protocol, the duration may not include the time spent reading the response data. - **Available Information:** The [InterceptorContext::input()] - and [InterceptorContext::request()] are **ALWAYS** available. - Other information **WILL NOT** be available. In the event of retries, + **Available Information:** The [`InterceptorContext::request`](context::InterceptorContext::request) + is **ALWAYS** available. Other information **WILL NOT** be available. In the event of retries, the `InterceptorContext` will not include changes made in previous attempts (e.g. by request signers or other interceptors). **Error Behavior:** If errors are raised by this hook, execution will jump to `modify_before_attempt_completion` with - the raised error as the [InterceptorContext::output_or_error()]. + the raised error as the [`InterceptorContext::output_or_error`](context::InterceptorContext::output_or_error). " ); @@ -373,16 +357,14 @@ pub trait Interceptor: fmt::Debug { Depending on the protocol, the duration may not include the time spent reading the response data. - **Available Information:** The [InterceptorContext::input()], - [InterceptorContext::request()] and - [InterceptorContext::response()] are **ALWAYS** available. - Other information **WILL NOT** be available. In the event of retries, + **Available Information:** The [`InterceptorContext::response`](context::InterceptorContext::response) + is **ALWAYS** available. Other information **WILL NOT** be available. In the event of retries, the `InterceptorContext` will not include changes made in previous attempts (e.g. by request signers or other interceptors). **Error Behavior:** If errors are raised by this hook, execution will jump to `modify_before_attempt_completion` with - the raised error as the [InterceptorContext::output_or_error()]. + the raised error as the [`InterceptorContext::output_or_error`](context::InterceptorContext::output_or_error). " ); @@ -398,10 +380,8 @@ pub trait Interceptor: fmt::Debug { failure occurs earlier in the request pipeline. This method may be called multiple times in the event of retries. - **Available Information:** The [InterceptorContext::input()], - [InterceptorContext::request()] and - [InterceptorContext::response()] are **ALWAYS** available. - The transmit_response may have been modified by earlier + **Available Information:** The [`InterceptorContext::response`](context::InterceptorContext::response) + is **ALWAYS** available. The transmit_response may have been modified by earlier `modify_before_deserialization` hooks, and may be modified further by later hooks. Other information **WILL NOT** be available. In the event of retries, the `InterceptorContext` will not include changes made in @@ -410,7 +390,7 @@ pub trait Interceptor: fmt::Debug { **Error Behavior:** If errors are raised by this hook, execution will jump to `modify_before_attempt_completion` with the raised error as the - [InterceptorContext::output_or_error()]. + [`InterceptorContext::output_or_error`](context::InterceptorContext::output_or_error). **Return Constraints:** The transport response message returned by this hook MUST be the same type of response message passed into @@ -432,16 +412,14 @@ pub trait Interceptor: fmt::Debug { Depending on the protocol and operation, the duration may include the time spent downloading the response data. - **Available Information:** The [InterceptorContext::input()], - [InterceptorContext::request()] and - [InterceptorContext::response()] are **ALWAYS** available. - Other information **WILL NOT** be available. In the event of retries, + **Available Information:** The [`InterceptorContext::response`](context::InterceptorContext::response) + is **ALWAYS** available. Other information **WILL NOT** be available. In the event of retries, the `InterceptorContext` will not include changes made in previous attempts (e.g. by request signers or other interceptors). **Error Behavior:** If errors are raised by this hook, execution will jump to `modify_before_attempt_completion` - with the raised error as the [InterceptorContext::output_or_error()]. + with the raised error as the [`InterceptorContext::output_or_error`](context::InterceptorContext::output_or_error). " ); @@ -459,16 +437,14 @@ pub trait Interceptor: fmt::Debug { the duration may include the time spent downloading the response data. - **Available Information:** The [InterceptorContext::input()], - [InterceptorContext::request()], - [InterceptorContext::response()] and - [InterceptorContext::output_or_error()] are **ALWAYS** available. In the event - of retries, the `InterceptorContext` will not include changes made + **Available Information:** The [`InterceptorContext::response`](context::InterceptorContext::response) + and [`InterceptorContext::output_or_error`](context::InterceptorContext::output_or_error) + are **ALWAYS** available. In the event of retries, the `InterceptorContext` will not include changes made in previous attempts (e.g. by request signers or other interceptors). **Error Behavior:** If errors are raised by this hook, execution will jump to `modify_before_attempt_completion` with - the raised error as the [InterceptorContext::output_or_error()]. + the raised error as the [`InterceptorContext::output_or_error`](context::InterceptorContext::output_or_error). " ); @@ -480,16 +456,19 @@ pub trait Interceptor: fmt::Debug { /// failure occurs before `before_attempt`. This method may /// be called multiple times in the event of retries. /// - /// **Available Information:** The [InterceptorContext::input()], - /// [InterceptorContext::request()], - /// [InterceptorContext::response()] and - /// [InterceptorContext::output_or_error()] are **ALWAYS** available. In the event - /// of retries, the `InterceptorContext` will not include changes made + /// **Available Information:** + /// The [`InterceptorContext::input`](context::InterceptorContext::input), + /// [`InterceptorContext::request`](context::InterceptorContext::request), + /// [`InterceptorContext::response`](context::InterceptorContext::response), or + /// [`InterceptorContext::output_or_error`](context::InterceptorContext::output_or_error) **MAY** be available. + /// If the operation succeeded, the `output` will be available. Otherwise, any of the other + /// pieces of information may be available depending on where in the operation lifecycle it failed. + /// In the event of retries, the `InterceptorContext` will not include changes made /// in previous attempts (e.g. by request signers or other interceptors). /// /// **Error Behavior:** If errors are raised by this /// hook, execution will jump to `after_attempt` with - /// the raised error as the [InterceptorContext::output_or_error()]. + /// the raised error as the [`InterceptorContext::output_or_error`](context::InterceptorContext::output_or_error). /// /// **Return Constraints:** Any output message returned by this /// hook MUST match the operation being invoked. Any error type can be @@ -500,9 +479,7 @@ pub trait Interceptor: fmt::Debug { runtime_components: &RuntimeComponents, cfg: &mut ConfigBag, ) -> Result<(), BoxError> { - let _ctx = context; - let _rc = runtime_components; - let _cfg = cfg; + let (_ctx, _rc, _cfg) = (context, runtime_components, cfg); Ok(()) } @@ -511,14 +488,15 @@ pub trait Interceptor: fmt::Debug { /// **When:** This will **ALWAYS** be called once per attempt, as long as /// `before_attempt` has been executed. /// - /// **Available Information:** The [InterceptorContext::input()], - /// [InterceptorContext::request()] and - /// [InterceptorContext::output_or_error()] are **ALWAYS** available. - /// The [InterceptorContext::response()] is available if a - /// response was received by the service for this attempt. - /// In the event of retries, the `InterceptorContext` will not include - /// changes made in previous attempts (e.g. by request signers or other - /// interceptors). + /// **Available Information:** + /// The [`InterceptorContext::input`](context::InterceptorContext::input), + /// [`InterceptorContext::request`](context::InterceptorContext::request), + /// [`InterceptorContext::response`](context::InterceptorContext::response), or + /// [`InterceptorContext::output_or_error`](context::InterceptorContext::output_or_error) **MAY** be available. + /// If the operation succeeded, the `output` will be available. Otherwise, any of the other + /// pieces of information may be available depending on where in the operation lifecycle it failed. + /// In the event of retries, the `InterceptorContext` will not include changes made + /// in previous attempts (e.g. by request signers or other interceptors). /// /// **Error Behavior:** Errors raised by this hook will be stored /// until all interceptors have had their `after_attempt` invoked. @@ -527,16 +505,14 @@ pub trait Interceptor: fmt::Debug { /// retry strategy determines that the execution is retryable, /// execution will then jump to `before_attempt`. Otherwise, /// execution will jump to `modify_before_attempt_completion` with the - /// raised error as the [InterceptorContext::output_or_error()]. + /// raised error as the [`InterceptorContext::output_or_error`](context::InterceptorContext::output_or_error). fn read_after_attempt( &self, context: &FinalizerInterceptorContextRef<'_>, runtime_components: &RuntimeComponents, cfg: &mut ConfigBag, ) -> Result<(), BoxError> { - let _ctx = context; - let _rc = runtime_components; - let _cfg = cfg; + let (_ctx, _rc, _cfg) = (context, runtime_components, cfg); Ok(()) } @@ -547,15 +523,19 @@ pub trait Interceptor: fmt::Debug { /// /// **When:** This will **ALWAYS** be called once per execution. /// - /// **Available Information:** The [InterceptorContext::input()] - /// and [InterceptorContext::output_or_error()] are **ALWAYS** available. The - /// [InterceptorContext::request()] - /// and [InterceptorContext::response()] are available if the - /// execution proceeded far enough for them to be generated. + /// **Available Information:** + /// The [`InterceptorContext::input`](context::InterceptorContext::input), + /// [`InterceptorContext::request`](context::InterceptorContext::request), + /// [`InterceptorContext::response`](context::InterceptorContext::response), or + /// [`InterceptorContext::output_or_error`](context::InterceptorContext::output_or_error) **MAY** be available. + /// If the operation succeeded, the `output` will be available. Otherwise, any of the other + /// pieces of information may be available depending on where in the operation lifecycle it failed. + /// In the event of retries, the `InterceptorContext` will not include changes made + /// in previous attempts (e.g. by request signers or other interceptors). /// /// **Error Behavior:** If errors are raised by this /// hook , execution will jump to `after_attempt` with - /// the raised error as the [InterceptorContext::output_or_error()]. + /// the raised error as the [`InterceptorContext::output_or_error`](context::InterceptorContext::output_or_error). /// /// **Return Constraints:** Any output message returned by this /// hook MUST match the operation being invoked. Any error type can be @@ -566,9 +546,7 @@ pub trait Interceptor: fmt::Debug { runtime_components: &RuntimeComponents, cfg: &mut ConfigBag, ) -> Result<(), BoxError> { - let _ctx = context; - let _rc = runtime_components; - let _cfg = cfg; + let (_ctx, _rc, _cfg) = (context, runtime_components, cfg); Ok(()) } @@ -578,17 +556,21 @@ pub trait Interceptor: fmt::Debug { /// between invocation of this hook and `before_execution` is very /// close to the full duration of the execution. /// - /// **Available Information:** The [InterceptorContext::input()] - /// and [InterceptorContext::output_or_error()] are **ALWAYS** available. The - /// [InterceptorContext::request()] and - /// [InterceptorContext::response()] are available if the - /// execution proceeded far enough for them to be generated. + /// **Available Information:** + /// The [`InterceptorContext::input`](context::InterceptorContext::input), + /// [`InterceptorContext::request`](context::InterceptorContext::request), + /// [`InterceptorContext::response`](context::InterceptorContext::response), or + /// [`InterceptorContext::output_or_error`](context::InterceptorContext::output_or_error) **MAY** be available. + /// If the operation succeeded, the `output` will be available. Otherwise, any of the other + /// pieces of information may be available depending on where in the operation lifecycle it failed. + /// In the event of retries, the `InterceptorContext` will not include changes made + /// in previous attempts (e.g. by request signers or other interceptors). /// /// **Error Behavior:** Errors raised by this hook will be stored /// until all interceptors have had their `after_execution` invoked. /// The error will then be treated as the - /// [InterceptorContext::output_or_error()] to the customer. If multiple - /// `after_execution` methods raise errors , the latest will be + /// [`InterceptorContext::output_or_error`](context::InterceptorContext::output_or_error) + /// to the customer. If multiple `after_execution` methods raise errors , the latest will be /// used and earlier ones will be logged and dropped. fn read_after_execution( &self, @@ -596,9 +578,7 @@ pub trait Interceptor: fmt::Debug { runtime_components: &RuntimeComponents, cfg: &mut ConfigBag, ) -> Result<(), BoxError> { - let _ctx = context; - let _rc = runtime_components; - let _cfg = cfg; + let (_ctx, _rc, _cfg) = (context, runtime_components, cfg); Ok(()) } } @@ -606,7 +586,7 @@ pub trait Interceptor: fmt::Debug { /// Interceptor wrapper that may be shared #[derive(Clone)] pub struct SharedInterceptor { - interceptor: Arc, + interceptor: Arc, check_enabled: Arc bool + Send + Sync>, } @@ -619,8 +599,8 @@ impl fmt::Debug for SharedInterceptor { } impl SharedInterceptor { - /// Create a new `SharedInterceptor` from `Interceptor` - pub fn new(interceptor: T) -> Self { + /// Create a new `SharedInterceptor` from `Interceptor`. + pub fn new(interceptor: T) -> Self { Self { interceptor: Arc::new(interceptor), check_enabled: Arc::new(|conf: &ConfigBag| { @@ -629,7 +609,8 @@ impl SharedInterceptor { } } - fn enabled(&self, conf: &ConfigBag) -> bool { + /// Checks if this interceptor is enabled in the given config. + pub fn enabled(&self, conf: &ConfigBag) -> bool { (self.check_enabled)(conf) } } @@ -641,84 +622,12 @@ impl AsRef for SharedInterceptor { } impl Deref for SharedInterceptor { - type Target = Arc; + type Target = Arc; fn deref(&self) -> &Self::Target { &self.interceptor } } -/// A interceptor wrapper to conditionally enable the interceptor based on [`DisableInterceptor`] -struct ConditionallyEnabledInterceptor(SharedInterceptor); -impl ConditionallyEnabledInterceptor { - fn if_enabled(&self, cfg: &ConfigBag) -> Option<&dyn Interceptor> { - if self.0.enabled(cfg) { - Some(self.0.as_ref()) - } else { - None - } - } -} - -#[derive(Debug)] -pub struct Interceptors { - interceptors: I, -} - -macro_rules! interceptor_impl_fn { - (mut $interceptor:ident) => { - pub fn $interceptor( - self, - ctx: &mut InterceptorContext, - runtime_components: &RuntimeComponents, - cfg: &mut ConfigBag, - ) -> Result<(), InterceptorError> { - tracing::trace!(concat!( - "running `", - stringify!($interceptor), - "` interceptors" - )); - let mut result: Result<(), BoxError> = Ok(()); - let mut ctx = ctx.into(); - for interceptor in self.into_iter() { - if let Some(interceptor) = interceptor.if_enabled(cfg) { - if let Err(new_error) = - interceptor.$interceptor(&mut ctx, runtime_components, cfg) - { - if let Err(last_error) = result { - tracing::debug!("{}", DisplayErrorContext(&*last_error)); - } - result = Err(new_error); - } - } - } - result.map_err(InterceptorError::$interceptor) - } - }; - (ref $interceptor:ident) => { - pub fn $interceptor( - self, - ctx: &InterceptorContext, - runtime_components: &RuntimeComponents, - cfg: &mut ConfigBag, - ) -> Result<(), InterceptorError> { - let mut result: Result<(), BoxError> = Ok(()); - let ctx = ctx.into(); - for interceptor in self.into_iter() { - if let Some(interceptor) = interceptor.if_enabled(cfg) { - if let Err(new_error) = interceptor.$interceptor(&ctx, runtime_components, cfg) - { - if let Err(last_error) = result { - tracing::debug!("{}", DisplayErrorContext(&*last_error)); - } - result = Err(new_error); - } - } - } - result.map_err(InterceptorError::$interceptor) - } - }; -} - /// Generalized interceptor disabling interface /// /// RuntimePlugins can disable interceptors by inserting [`DisableInterceptor`](DisableInterceptor) into the config bag @@ -744,215 +653,3 @@ pub fn disable_interceptor(cause: &'static str) -> DisableInterc cause, } } - -impl Interceptors -where - I: Iterator, -{ - pub fn new(interceptors: I) -> Self { - Self { interceptors } - } - - fn into_iter(self) -> impl Iterator { - self.interceptors.map(ConditionallyEnabledInterceptor) - } - - pub fn read_before_execution( - self, - operation: bool, - ctx: &InterceptorContext, - cfg: &mut ConfigBag, - ) -> Result<(), InterceptorError> { - tracing::trace!( - "running {} `read_before_execution` interceptors", - if operation { "operation" } else { "client" } - ); - let mut result: Result<(), BoxError> = Ok(()); - let ctx: BeforeSerializationInterceptorContextRef<'_> = ctx.into(); - for interceptor in self.into_iter() { - if let Some(interceptor) = interceptor.if_enabled(cfg) { - if let Err(new_error) = interceptor.read_before_execution(&ctx, cfg) { - if let Err(last_error) = result { - tracing::debug!("{}", DisplayErrorContext(&*last_error)); - } - result = Err(new_error); - } - } - } - result.map_err(InterceptorError::read_before_execution) - } - - interceptor_impl_fn!(mut modify_before_serialization); - interceptor_impl_fn!(ref read_before_serialization); - interceptor_impl_fn!(ref read_after_serialization); - interceptor_impl_fn!(mut modify_before_retry_loop); - interceptor_impl_fn!(ref read_before_attempt); - interceptor_impl_fn!(mut modify_before_signing); - interceptor_impl_fn!(ref read_before_signing); - interceptor_impl_fn!(ref read_after_signing); - interceptor_impl_fn!(mut modify_before_transmit); - interceptor_impl_fn!(ref read_before_transmit); - interceptor_impl_fn!(ref read_after_transmit); - interceptor_impl_fn!(mut modify_before_deserialization); - interceptor_impl_fn!(ref read_before_deserialization); - interceptor_impl_fn!(ref read_after_deserialization); - - pub fn modify_before_attempt_completion( - self, - ctx: &mut InterceptorContext, - runtime_components: &RuntimeComponents, - cfg: &mut ConfigBag, - ) -> Result<(), InterceptorError> { - tracing::trace!("running `modify_before_attempt_completion` interceptors"); - let mut result: Result<(), BoxError> = Ok(()); - let mut ctx: FinalizerInterceptorContextMut<'_> = ctx.into(); - for interceptor in self.into_iter() { - if let Some(interceptor) = interceptor.if_enabled(cfg) { - if let Err(new_error) = - interceptor.modify_before_attempt_completion(&mut ctx, runtime_components, cfg) - { - if let Err(last_error) = result { - tracing::debug!("{}", DisplayErrorContext(&*last_error)); - } - result = Err(new_error); - } - } - } - result.map_err(InterceptorError::modify_before_attempt_completion) - } - - pub fn read_after_attempt( - self, - ctx: &InterceptorContext, - runtime_components: &RuntimeComponents, - cfg: &mut ConfigBag, - ) -> Result<(), InterceptorError> { - tracing::trace!("running `read_after_attempt` interceptors"); - let mut result: Result<(), BoxError> = Ok(()); - let ctx: FinalizerInterceptorContextRef<'_> = ctx.into(); - for interceptor in self.into_iter() { - if let Some(interceptor) = interceptor.if_enabled(cfg) { - if let Err(new_error) = - interceptor.read_after_attempt(&ctx, runtime_components, cfg) - { - if let Err(last_error) = result { - tracing::debug!("{}", DisplayErrorContext(&*last_error)); - } - result = Err(new_error); - } - } - } - result.map_err(InterceptorError::read_after_attempt) - } - - pub fn modify_before_completion( - self, - ctx: &mut InterceptorContext, - runtime_components: &RuntimeComponents, - cfg: &mut ConfigBag, - ) -> Result<(), InterceptorError> { - tracing::trace!("running `modify_before_completion` interceptors"); - let mut result: Result<(), BoxError> = Ok(()); - let mut ctx: FinalizerInterceptorContextMut<'_> = ctx.into(); - for interceptor in self.into_iter() { - if let Some(interceptor) = interceptor.if_enabled(cfg) { - if let Err(new_error) = - interceptor.modify_before_completion(&mut ctx, runtime_components, cfg) - { - if let Err(last_error) = result { - tracing::debug!("{}", DisplayErrorContext(&*last_error)); - } - result = Err(new_error); - } - } - } - result.map_err(InterceptorError::modify_before_completion) - } - - pub fn read_after_execution( - self, - ctx: &InterceptorContext, - runtime_components: &RuntimeComponents, - cfg: &mut ConfigBag, - ) -> Result<(), InterceptorError> { - tracing::trace!("running `read_after_execution` interceptors"); - let mut result: Result<(), BoxError> = Ok(()); - let ctx: FinalizerInterceptorContextRef<'_> = ctx.into(); - for interceptor in self.into_iter() { - if let Some(interceptor) = interceptor.if_enabled(cfg) { - if let Err(new_error) = - interceptor.read_after_execution(&ctx, runtime_components, cfg) - { - if let Err(last_error) = result { - tracing::debug!("{}", DisplayErrorContext(&*last_error)); - } - result = Err(new_error); - } - } - } - result.map_err(InterceptorError::read_after_execution) - } -} - -#[cfg(all(test, feature = "test-util"))] -mod tests { - use crate::client::interceptors::context::Input; - use crate::client::interceptors::{ - disable_interceptor, BeforeTransmitInterceptorContextRef, BoxError, Interceptor, - InterceptorContext, Interceptors, SharedInterceptor, - }; - use crate::client::runtime_components::{RuntimeComponents, RuntimeComponentsBuilder}; - use aws_smithy_types::config_bag::ConfigBag; - - #[derive(Debug)] - struct TestInterceptor; - impl Interceptor for TestInterceptor {} - - #[test] - fn test_disable_interceptors() { - #[derive(Debug)] - struct PanicInterceptor; - impl Interceptor for PanicInterceptor { - fn read_before_transmit( - &self, - _context: &BeforeTransmitInterceptorContextRef<'_>, - _rc: &RuntimeComponents, - _cfg: &mut ConfigBag, - ) -> Result<(), BoxError> { - Err("boom".into()) - } - } - let rc = RuntimeComponentsBuilder::for_tests() - .with_interceptor(SharedInterceptor::new(PanicInterceptor)) - .with_interceptor(SharedInterceptor::new(TestInterceptor)) - .build() - .unwrap(); - - let mut cfg = ConfigBag::base(); - let interceptors = Interceptors::new(rc.interceptors()); - assert_eq!( - interceptors - .into_iter() - .filter(|i| i.if_enabled(&cfg).is_some()) - .count(), - 2 - ); - - Interceptors::new(rc.interceptors()) - .read_before_transmit(&InterceptorContext::new(Input::new(5)), &rc, &mut cfg) - .expect_err("interceptor returns error"); - cfg.interceptor_state() - .store_put(disable_interceptor::("test")); - assert_eq!( - Interceptors::new(rc.interceptors()) - .into_iter() - .filter(|i| i.if_enabled(&cfg).is_some()) - .count(), - 1 - ); - // shouldn't error because interceptors won't run - Interceptors::new(rc.interceptors()) - .read_before_transmit(&InterceptorContext::new(Input::new(5)), &rc, &mut cfg) - .expect("interceptor is now disabled"); - } -} diff --git a/rust-runtime/aws-smithy-runtime-api/src/client/interceptors/context.rs b/rust-runtime/aws-smithy-runtime-api/src/client/interceptors/context.rs index b97915dcbb93929ddee34e54b1f2afef67162eef..7aeb1904c31e9509b2498c53eb639fc56e4f221c 100644 --- a/rust-runtime/aws-smithy-runtime-api/src/client/interceptors/context.rs +++ b/rust-runtime/aws-smithy-runtime-api/src/client/interceptors/context.rs @@ -37,9 +37,13 @@ use std::{fmt, mem}; use tracing::{debug, error, trace}; // TODO(enableNewSmithyRuntimeLaunch): New-type `Input`/`Output`/`Error` +/// Type-erased operation input. pub type Input = TypeErasedBox; +/// Type-erased operation output. pub type Output = TypeErasedBox; +/// Type-erased operation error. pub type Error = TypeErasedError; +/// Type-erased result for an operation. pub type OutputOrError = Result>; type Request = HttpRequest; @@ -89,38 +93,7 @@ impl InterceptorContext { } } -impl InterceptorContext { - /// Decomposes the context into its constituent parts. - #[doc(hidden)] - #[allow(clippy::type_complexity)] - pub fn into_parts( - self, - ) -> ( - Option, - Option>>, - Option, - Option, - ) { - ( - self.input, - self.output_or_error, - self.request, - self.response, - ) - } - - pub fn finalize(self) -> Result> { - let Self { - output_or_error, - response, - phase, - .. - } = self; - output_or_error - .expect("output_or_error must always be set before finalize is called.") - .map_err(|error| OrchestratorError::into_sdk_error(error, &phase, response)) - } - +impl InterceptorContext { /// Retrieve the input for the operation being invoked. pub fn input(&self) -> Option<&I> { self.input.as_ref() @@ -188,6 +161,14 @@ impl InterceptorContext { self.output_or_error.as_mut() } + /// Return `true` if this context's `output_or_error` is an error. Otherwise, return `false`. + pub fn is_failed(&self) -> bool { + self.output_or_error + .as_ref() + .map(Result::is_err) + .unwrap_or_default() + } + /// Advance to the Serialization phase. #[doc(hidden)] pub fn enter_serialization_phase(&mut self) { @@ -314,6 +295,44 @@ impl InterceptorContext { self.output_or_error = None; RewindResult::Occurred } +} + +impl InterceptorContext +where + E: Debug, +{ + /// Decomposes the context into its constituent parts. + #[doc(hidden)] + #[allow(clippy::type_complexity)] + pub fn into_parts( + self, + ) -> ( + Option, + Option>>, + Option, + Option, + ) { + ( + self.input, + self.output_or_error, + self.request, + self.response, + ) + } + + /// Convert this context into the final operation result that is returned in client's the public API. + #[doc(hidden)] + pub fn finalize(self) -> Result> { + let Self { + output_or_error, + response, + phase, + .. + } = self; + output_or_error + .expect("output_or_error must always be set before finalize is called.") + .map_err(|error| OrchestratorError::into_sdk_error(error, &phase, response)) + } /// Mark this context as failed due to errors during the operation. Any errors already contained /// by the context will be replaced by the given error. @@ -328,14 +347,6 @@ impl InterceptorContext { error!("orchestrator context received an error but one was already present; Throwing away previous error: {:?}", existing_err); } } - - /// Return `true` if this context's `output_or_error` is an error. Otherwise, return `false`. - pub fn is_failed(&self) -> bool { - self.output_or_error - .as_ref() - .map(Result::is_err) - .unwrap_or_default() - } } /// The result of attempting to rewind a request. diff --git a/rust-runtime/aws-smithy-runtime-api/src/client/interceptors/context/wrappers.rs b/rust-runtime/aws-smithy-runtime-api/src/client/interceptors/context/wrappers.rs index 64ebdbe08a6c8bdababaaed4fc7fa376abb00346..77f751e136991c5b68b2def8b992cbeace072676 100644 --- a/rust-runtime/aws-smithy-runtime-api/src/client/interceptors/context/wrappers.rs +++ b/rust-runtime/aws-smithy-runtime-api/src/client/interceptors/context/wrappers.rs @@ -8,191 +8,222 @@ use crate::client::interceptors::context::{Request, Response}; use crate::client::orchestrator::OrchestratorError; use std::fmt::Debug; -macro_rules! output { - (&Option>) => { - Option> - }; - (&Option<$ty:ty>) => { - Option<&$ty> - }; - (&mut Option<$ty:ty>) => { - Option<&mut $ty> - }; - (&Result<$o_ty:ty, $e_ty:ty>) => { - Result<&$o_ty, &$e_ty> - }; - (&$($tt:tt)+) => { - &$($tt)+ - }; - (&mut $($tt:tt)+) => { - &mut $($tt)+ - }; -} - -macro_rules! declare_method { - (&mut $name:ident, $inner_name:ident, $doc:literal, Option<$ty:ty>) => { - #[doc=$doc] - pub fn $name(&mut self) -> Option<&mut $ty> { - self.inner.$inner_name.as_ref() - } - }; - (&$name:ident, $inner_name:ident, $doc:literal, Option<$ty:ty>) => { - #[doc=$doc] - pub fn $name(&self) -> Option<$ty> { - self.inner.$inner_name.as_mut() - } - }; - (&mut $name:ident, $doc:literal, $($tt:tt)+) => { - #[doc=$doc] - pub fn $name(&mut self) -> output!(&mut $($tt)+) { - self.inner.$name().expect(concat!("`", stringify!($name), "` wasn't set in the underlying interceptor context. This is a bug.")) +macro_rules! impl_from_interceptor_context { + (ref $wrapper:ident) => { + impl<'a, I, O, E> From<&'a InterceptorContext> for $wrapper<'a, I, O, E> { + fn from(inner: &'a InterceptorContext) -> Self { + Self { inner } + } } }; - (&$name:ident, $doc:literal, $($tt:tt)+) => { - #[doc=$doc] - pub fn $name(&self) -> output!(&$($tt)+) { - self.inner.$name().expect(concat!("`", stringify!($name), "` wasn't set in the underlying interceptor context. This is a bug.")) + (mut $wrapper:ident) => { + impl<'a, I, O, E> From<&'a mut InterceptorContext> for $wrapper<'a, I, O, E> { + fn from(inner: &'a mut InterceptorContext) -> Self { + Self { inner } + } } }; } -macro_rules! declare_known_method { - (output_or_error: &mut $($tt:tt)+) => { - declare_method!(&mut output_or_error_mut, "Returns a mutable reference to the deserialized output or error.", $($tt)+); - }; - (output_or_error: &$($tt:tt)+) => { - declare_method!(&output_or_error, "Returns a reference to the deserialized output or error.", $($tt)+); - }; - (input: &mut $($tt:tt)+) => { - declare_method!(&mut input_mut, "Returns a mutable reference to the input.", $($tt)+); - }; - (input: &$($tt:tt)+) => { - declare_method!(&input, "Returns a reference to the input.", $($tt)+); - }; - (request: &mut $($tt:tt)+) => { - declare_method!(&mut request_mut, "Returns a mutable reference to the transmittable request for the operation being invoked.", $($tt)+); - }; - (request: &$($tt:tt)+) => { - declare_method!(&request, "Returns a reference to the transmittable request for the operation being invoked.", $($tt)+); - }; - (response: &mut $($tt:tt)+) => { - declare_method!(&mut response_mut, "Returns a mutable reference to the response.", $($tt)+); - }; - (response: &$($tt:tt)+) => { - declare_method!(&response, "Returns a reference to the response.", $($tt)+); +macro_rules! expect { + ($self:ident, $what:ident) => { + $self.inner.$what().expect(concat!( + "`", + stringify!($what), + "` wasn't set in the underlying interceptor context. This is a bug." + )) }; } -macro_rules! declare_wrapper { - (($ref_struct_name:ident readonly)$($tt:tt)+) => { - pub struct $ref_struct_name<'a, I = Input, O = Output, E = Error> { - inner: &'a InterceptorContext, - } +// +// BeforeSerializationInterceptorContextRef +// - impl<'a, I, O, E: Debug> From<&'a InterceptorContext> for $ref_struct_name<'a, I, O, E> - { - fn from(inner: &'a InterceptorContext) -> Self { - Self { inner } - } - } +/// Interceptor context for the `read_before_execution` and `read_before_serialization` hooks. +/// +/// Only the input is available at this point in the operation. +#[derive(Debug)] +pub struct BeforeSerializationInterceptorContextRef<'a, I = Input, O = Output, E = Error> { + inner: &'a InterceptorContext, +} - impl<'a, I, O, E: Debug> $ref_struct_name<'a, I, O, E> { - declare_ref_wrapper_methods!($($tt)+); - } - }; - (($ref_struct_name:ident $mut_struct_name:ident)$($tt:tt)+) => { - declare_wrapper!(($ref_struct_name readonly) $($tt)+); +impl_from_interceptor_context!(ref BeforeSerializationInterceptorContextRef); - pub struct $mut_struct_name<'a, I = Input, O = Output, E = Error> { - inner: &'a mut InterceptorContext, - } +impl<'a, I, O, E> BeforeSerializationInterceptorContextRef<'a, I, O, E> { + /// Returns a reference to the input. + pub fn input(&self) -> &I { + expect!(self, input) + } +} - impl<'a, I, O, E: Debug> From<&'a mut InterceptorContext> for $mut_struct_name<'a, I, O, E> - { - fn from(inner: &'a mut InterceptorContext) -> Self { - Self { inner } - } - } +// +// BeforeSerializationInterceptorContextMut +// - impl<'a, I, O, E: Debug> $mut_struct_name<'a, I, O, E> { - declare_ref_wrapper_methods!($($tt)+); - declare_mut_wrapper_methods!($($tt)+); - } - }; +/// Interceptor context for the `modify_before_serialization` hook. +/// +/// Only the input is available at this point in the operation. +#[derive(Debug)] +pub struct BeforeSerializationInterceptorContextMut<'a, I = Input, O = Output, E = Error> { + inner: &'a mut InterceptorContext, } -macro_rules! declare_ref_wrapper_methods { - (($field:ident: $($head:tt)+)$($tail:tt)+) => { - declare_known_method!($field: &$($head)+); - declare_ref_wrapper_methods!($($tail)+); - }; - (($field:ident: $($tt:tt)+)) => { - declare_known_method!($field: &$($tt)+); - }; +impl_from_interceptor_context!(mut BeforeSerializationInterceptorContextMut); + +impl<'a, I, O, E> BeforeSerializationInterceptorContextMut<'a, I, O, E> { + /// Returns a reference to the input. + pub fn input(&self) -> &I { + expect!(self, input) + } + + /// Returns a mutable reference to the input. + pub fn input_mut(&mut self) -> &mut I { + expect!(self, input_mut) + } } -macro_rules! declare_mut_wrapper_methods { - (($field:ident: $($head:tt)+)$($tail:tt)+) => { - declare_known_method!($field: &mut $($head)+); - declare_mut_wrapper_methods!($($tail)+); - }; - (($field:ident: $($tt:tt)+)) => { - declare_known_method!($field: &mut $($tt)+); - }; +// +// BeforeSerializationInterceptorContextRef +// + +/// Interceptor context for several hooks in between serialization and transmission. +/// +/// Only the request is available at this point in the operation. +#[derive(Debug)] +pub struct BeforeTransmitInterceptorContextRef<'a, I = Input, O = Output, E = Error> { + inner: &'a InterceptorContext, +} + +impl_from_interceptor_context!(ref BeforeTransmitInterceptorContextRef); + +impl<'a, I, O, E> BeforeTransmitInterceptorContextRef<'a, I, O, E> { + /// Returns a reference to the transmittable request for the operation being invoked. + pub fn request(&self) -> &Request { + expect!(self, request) + } +} + +// +// BeforeSerializationInterceptorContextMut +// + +/// Interceptor context for several hooks in between serialization and transmission. +/// +/// Only the request is available at this point in the operation. +#[derive(Debug)] +pub struct BeforeTransmitInterceptorContextMut<'a, I = Input, O = Output, E = Error> { + inner: &'a mut InterceptorContext, +} + +impl_from_interceptor_context!(mut BeforeTransmitInterceptorContextMut); + +impl<'a, I, O, E> BeforeTransmitInterceptorContextMut<'a, I, O, E> { + /// Returns a reference to the transmittable request for the operation being invoked. + pub fn request(&self) -> &Request { + expect!(self, request) + } + + /// Returns a mutable reference to the transmittable request for the operation being invoked. + pub fn request_mut(&mut self) -> &mut Request { + expect!(self, request_mut) + } +} + +// +// BeforeDeserializationInterceptorContextRef +// + +/// Interceptor context for hooks before deserializing the response. +/// +/// Only the response is available at this point in the operation. +#[derive(Debug)] +pub struct BeforeDeserializationInterceptorContextRef<'a, I = Input, O = Output, E = Error> { + inner: &'a InterceptorContext, } -declare_wrapper!( - (BeforeSerializationInterceptorContextRef BeforeSerializationInterceptorContextMut) - (input: I) -); +impl_from_interceptor_context!(ref BeforeDeserializationInterceptorContextRef); -declare_wrapper!( - (BeforeTransmitInterceptorContextRef BeforeTransmitInterceptorContextMut) - (request: Request) -); +impl<'a, I, O, E> BeforeDeserializationInterceptorContextRef<'a, I, O, E> { + /// Returns a reference to the response. + pub fn response(&self) -> &Response { + expect!(self, response) + } +} + +// +// BeforeDeserializationInterceptorContextMut +// + +/// Interceptor context for hooks before deserializing the response. +/// +/// Only the response is available at this point in the operation. +pub struct BeforeDeserializationInterceptorContextMut<'a, I = Input, O = Output, E = Error> { + inner: &'a mut InterceptorContext, +} -declare_wrapper!( - (BeforeDeserializationInterceptorContextRef BeforeDeserializationInterceptorContextMut) - (input: I) - (request: Request) - (response: Response) -); +impl_from_interceptor_context!(mut BeforeDeserializationInterceptorContextMut); + +impl<'a, I, O, E> BeforeDeserializationInterceptorContextMut<'a, I, O, E> { + /// Returns a reference to the response. + pub fn response(&self) -> &Response { + expect!(self, response) + } + + /// Returns a mutable reference to the response. + pub fn response_mut(&mut self) -> &mut Response { + expect!(self, response_mut) + } -impl<'a, I, O, E: Debug> BeforeDeserializationInterceptorContextMut<'a, I, O, E> { #[doc(hidden)] /// Downgrade this helper struct, returning the underlying InterceptorContext. There's no good /// reason to use this unless you're writing tests or you have to interact with an API that /// doesn't support the helper structs. - pub fn into_inner(&mut self) -> &'_ mut InterceptorContext { + pub fn inner_mut(&mut self) -> &'_ mut InterceptorContext { self.inner } } -declare_wrapper!( - (AfterDeserializationInterceptorContextRef readonly) - (input: I) - (request: Request) - (response: Response) - (output_or_error: Result> -)); - -// Why are all the rest of these defined with a macro but these last two aren't? I simply ran out of -// time. Consider updating the macros to support these last two if you're looking for a challenge. -// - Zelda +// +// AfterDeserializationInterceptorContextRef +// -pub struct FinalizerInterceptorContextRef<'a, I = Input, O = Output, E = Error> { +/// Interceptor context for hooks after deserializing the response. +/// +/// The response and the deserialized output or error are available at this point in the operation. +pub struct AfterDeserializationInterceptorContextRef<'a, I = Input, O = Output, E = Error> { inner: &'a InterceptorContext, } -impl<'a, I, O, E: Debug> From<&'a InterceptorContext> - for FinalizerInterceptorContextRef<'a, I, O, E> -{ - fn from(inner: &'a InterceptorContext) -> Self { - Self { inner } +impl_from_interceptor_context!(ref AfterDeserializationInterceptorContextRef); + +impl<'a, I, O, E> AfterDeserializationInterceptorContextRef<'a, I, O, E> { + /// Returns a reference to the response. + pub fn response(&self) -> &Response { + expect!(self, response) + } + + /// Returns a reference to the deserialized output or error. + pub fn output_or_error(&self) -> Result<&O, &OrchestratorError> { + expect!(self, output_or_error) } } -impl<'a, I, O, E: Debug> FinalizerInterceptorContextRef<'a, I, O, E> { +// +// FinalizerInterceptorContextRef +// + +/// Interceptor context for finalization hooks. +/// +/// This context is used by the `read_after_attempt` and `read_after_execution` hooks +/// that are all called upon both success and failure, and may have varying levels +/// of context available depending on where a failure occurred if the operation failed. +pub struct FinalizerInterceptorContextRef<'a, I = Input, O = Output, E = Error> { + inner: &'a InterceptorContext, +} + +impl_from_interceptor_context!(ref FinalizerInterceptorContextRef); + +impl<'a, I, O, E> FinalizerInterceptorContextRef<'a, I, O, E> { /// Returns the operation input. pub fn input(&self) -> Option<&I> { self.inner.input.as_ref() @@ -214,19 +245,22 @@ impl<'a, I, O, E: Debug> FinalizerInterceptorContextRef<'a, I, O, E> { } } +// +// FinalizerInterceptorContextMut +// + +/// Interceptor context for finalization hooks. +/// +/// This context is used by the `modify_before_attempt_completion` and `modify_before_completion` hooks +/// that are all called upon both success and failure, and may have varying levels +/// of context available depending on where a failure occurred if the operation failed. pub struct FinalizerInterceptorContextMut<'a, I = Input, O = Output, E = Error> { inner: &'a mut InterceptorContext, } -impl<'a, I, O, E: Debug> From<&'a mut InterceptorContext> - for FinalizerInterceptorContextMut<'a, I, O, E> -{ - fn from(inner: &'a mut InterceptorContext) -> Self { - Self { inner } - } -} +impl_from_interceptor_context!(mut FinalizerInterceptorContextMut); -impl<'a, I, O, E: Debug> FinalizerInterceptorContextMut<'a, I, O, E> { +impl<'a, I, O, E> FinalizerInterceptorContextMut<'a, I, O, E> { /// Returns the operation input. pub fn input(&self) -> Option<&I> { self.inner.input.as_ref() 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 cadeccf0d0c12d535c8653c6fdc72653f95e8cee..f47169cb7bce2e154d787e02f8b171c26407aeaa 100644 --- a/rust-runtime/aws-smithy-runtime-api/src/client/orchestrator.rs +++ b/rust-runtime/aws-smithy-runtime-api/src/client/orchestrator.rs @@ -71,62 +71,110 @@ impl Storable for LoadedRequestBody { type Storer = StoreReplace; } -// TODO(enableNewSmithyRuntimeLaunch): Make OrchestratorError adhere to the errors RFC -/// Errors that can occur while running the orchestrator. #[derive(Debug)] -#[non_exhaustive] -pub enum OrchestratorError { +enum ErrorKind { /// An error occurred within an interceptor. - Interceptor { err: InterceptorError }, + Interceptor { source: InterceptorError }, /// An error returned by a service. Operation { err: E }, /// An error that occurs when a request times out. - Timeout { err: BoxError }, + Timeout { source: BoxError }, /// An error that occurs when request dispatch fails. - Connector { err: ConnectorError }, + Connector { source: ConnectorError }, /// An error that occurs when a response can't be deserialized. - Response { err: BoxError }, + Response { source: BoxError }, /// A general orchestrator error. - Other { err: BoxError }, + Other { source: BoxError }, } -impl OrchestratorError { - /// Create a new `OrchestratorError` from a [`BoxError`]. - pub fn other(err: impl Into>) -> Self { - let err = err.into(); - Self::Other { err } +/// Errors that can occur while running the orchestrator. +#[derive(Debug)] +pub struct OrchestratorError { + kind: ErrorKind, +} + +impl OrchestratorError { + /// Create a new `OrchestratorError` from the given source. + pub fn other(source: impl Into>) -> Self { + Self { + kind: ErrorKind::Other { + source: source.into(), + }, + } } - /// Create a new `OrchestratorError` from an error received from a service. + /// Create an operation error. pub fn operation(err: E) -> Self { - Self::Operation { err } + Self { + kind: ErrorKind::Operation { err }, + } } - /// Create a new `OrchestratorError::Interceptor` from an [`InterceptorError`]. - pub fn interceptor(err: InterceptorError) -> Self { - Self::Interceptor { err } + /// True if the underlying error is an operation error. + pub fn is_operation_error(&self) -> bool { + matches!(self.kind, ErrorKind::Operation { .. }) } - /// Create a new `OrchestratorError::Timeout` from a [`BoxError`]. - pub fn timeout(err: BoxError) -> Self { - Self::Timeout { err } + /// Return this orchestrator error as an operation error if possible. + pub fn as_operation_error(&self) -> Option<&E> { + match &self.kind { + ErrorKind::Operation { err } => Some(err), + _ => None, + } } - /// Create a new `OrchestratorError::Response` from a [`BoxError`]. - pub fn response(err: BoxError) -> Self { - Self::Response { err } + /// Create an interceptor error with the given source. + pub fn interceptor(source: InterceptorError) -> Self { + Self { + kind: ErrorKind::Interceptor { source }, + } } - /// Create a new `OrchestratorError::Connector` from a [`ConnectorError`]. - pub fn connector(err: ConnectorError) -> Self { - Self::Connector { err } + /// True if the underlying error is an interceptor error. + pub fn is_interceptor_error(&self) -> bool { + matches!(self.kind, ErrorKind::Interceptor { .. }) } - /// Convert the `OrchestratorError` into `Some` operation specific error if it is one. Otherwise, - /// return `None`. - pub fn as_operation_error(&self) -> Option<&E> { - match self { - Self::Operation { err } => Some(err), + /// Create a timeout error with the given source. + pub fn timeout(source: BoxError) -> Self { + Self { + kind: ErrorKind::Timeout { source }, + } + } + + /// True if the underlying error is a timeout error. + pub fn is_timeout_error(&self) -> bool { + matches!(self.kind, ErrorKind::Timeout { .. }) + } + + /// Create a response error with the given source. + pub fn response(source: BoxError) -> Self { + Self { + kind: ErrorKind::Response { source }, + } + } + + /// True if the underlying error is a response error. + pub fn is_response_error(&self) -> bool { + matches!(self.kind, ErrorKind::Response { .. }) + } + + /// Create a connector error with the given source. + pub fn connector(source: ConnectorError) -> Self { + Self { + kind: ErrorKind::Connector { source }, + } + } + + /// True if the underlying error is a [`ConnectorError`]. + pub fn is_connector_error(&self) -> bool { + matches!(self.kind, ErrorKind::Connector { .. }) + } + + /// Return this orchestrator error as a connector error if possible. + pub fn as_connector_error(&self) -> Option<&ConnectorError> { + match &self.kind { + ErrorKind::Connector { source } => Some(source), _ => None, } } @@ -137,34 +185,36 @@ impl OrchestratorError { phase: &Phase, response: Option, ) -> SdkError { - match self { - Self::Interceptor { err } => { + match self.kind { + ErrorKind::Interceptor { source } => { use Phase::*; match phase { - BeforeSerialization | Serialization => SdkError::construction_failure(err), + BeforeSerialization | Serialization => SdkError::construction_failure(source), BeforeTransmit | Transmit => match response { - Some(response) => SdkError::response_error(err, response), - None => SdkError::dispatch_failure(ConnectorError::other(err.into(), None)), + Some(response) => SdkError::response_error(source, response), + None => { + SdkError::dispatch_failure(ConnectorError::other(source.into(), None)) + } }, BeforeDeserialization | Deserialization | AfterDeserialization => { - SdkError::response_error(err, response.expect("phase has a response")) + SdkError::response_error(source, response.expect("phase has a response")) } } } - Self::Operation { err } => { + ErrorKind::Operation { err } => { debug_assert!(phase.is_after_deserialization(), "operation errors are a result of successfully receiving and parsing a response from the server. Therefore, we must be in the 'After Deserialization' phase."); SdkError::service_error(err, response.expect("phase has a response")) } - Self::Connector { err } => SdkError::dispatch_failure(err), - Self::Timeout { err } => SdkError::timeout_error(err), - Self::Response { err } => SdkError::response_error(err, response.unwrap()), - Self::Other { err } => { + ErrorKind::Connector { source } => SdkError::dispatch_failure(source), + ErrorKind::Timeout { source } => SdkError::timeout_error(source), + ErrorKind::Response { source } => SdkError::response_error(source, response.unwrap()), + ErrorKind::Other { source } => { use Phase::*; match phase { - BeforeSerialization | Serialization => SdkError::construction_failure(err), - BeforeTransmit | Transmit => convert_dispatch_error(err, response), + BeforeSerialization | Serialization => SdkError::construction_failure(source), + BeforeTransmit | Transmit => convert_dispatch_error(source, response), BeforeDeserialization | Deserialization | AfterDeserialization => { - SdkError::response_error(err, response.expect("phase has a response")) + SdkError::response_error(source, response.expect("phase has a response")) } } } @@ -202,12 +252,3 @@ 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) - } -} diff --git a/rust-runtime/aws-smithy-runtime-api/src/lib.rs b/rust-runtime/aws-smithy-runtime-api/src/lib.rs index 3f95c9304a5a68b86144040ba5944249d188e7de..ebc9225a8a397238f71f4fec7ef457462d49b966 100644 --- a/rust-runtime/aws-smithy-runtime-api/src/lib.rs +++ b/rust-runtime/aws-smithy-runtime-api/src/lib.rs @@ -4,8 +4,7 @@ */ #![warn( - // TODO(enableNewSmithyRuntimeLaunch): Add in remaining missing docs - // missing_docs, + missing_docs, rustdoc::missing_crate_level_docs, unreachable_pub, rust_2018_idioms diff --git a/rust-runtime/aws-smithy-runtime/src/client/connectors/connection_poisoning.rs b/rust-runtime/aws-smithy-runtime/src/client/connectors/connection_poisoning.rs index 6e3d2aadfbb757debbda913d02a69bd9d5e77214..1903198a9544c75653f0ae951e81fb86225543ee 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/connectors/connection_poisoning.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/connectors/connection_poisoning.rs @@ -72,7 +72,7 @@ impl Interceptor for ConnectionPoisoningInterceptor { .ok_or("retry classifiers are required for connection poisoning to work")?; let error_is_transient = retry_classifiers - .classify_retry(context.into_inner()) + .classify_retry(context.inner_mut()) .map(|reason| reason == RetryReason::Error(ErrorKind::TransientError)) .unwrap_or_default(); let connection_poisoning_is_enabled = diff --git a/rust-runtime/aws-smithy-runtime/src/client/interceptors.rs b/rust-runtime/aws-smithy-runtime/src/client/interceptors.rs index 396aeb607866b0fd445f38b420ed99ce9092e74d..931866c621996f39882ce5c508591fcc3d3fa87f 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/interceptors.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/interceptors.rs @@ -5,15 +5,246 @@ use aws_smithy_http::body::SdkBody; use aws_smithy_runtime_api::box_error::BoxError; -use aws_smithy_runtime_api::client::interceptors::context::BeforeTransmitInterceptorContextMut; -use aws_smithy_runtime_api::client::interceptors::Interceptor; +use aws_smithy_runtime_api::client::interceptors::context::{ + BeforeSerializationInterceptorContextRef, BeforeTransmitInterceptorContextMut, + FinalizerInterceptorContextMut, FinalizerInterceptorContextRef, +}; +use aws_smithy_runtime_api::client::interceptors::context::{ + Error, Input, InterceptorContext, Output, +}; +use aws_smithy_runtime_api::client::interceptors::{ + Interceptor, InterceptorError, SharedInterceptor, +}; use aws_smithy_runtime_api::client::orchestrator::HttpRequest; use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents; use aws_smithy_types::config_bag::ConfigBag; +use aws_smithy_types::error::display::DisplayErrorContext; use std::error::Error as StdError; use std::fmt; use std::marker::PhantomData; +macro_rules! interceptor_impl_fn { + (mut $interceptor:ident) => { + pub(crate) fn $interceptor( + self, + ctx: &mut InterceptorContext, + runtime_components: &RuntimeComponents, + cfg: &mut ConfigBag, + ) -> Result<(), InterceptorError> { + tracing::trace!(concat!( + "running `", + stringify!($interceptor), + "` interceptors" + )); + let mut result: Result<(), BoxError> = Ok(()); + let mut ctx = ctx.into(); + for interceptor in self.into_iter() { + if let Some(interceptor) = interceptor.if_enabled(cfg) { + if let Err(new_error) = + interceptor.$interceptor(&mut ctx, runtime_components, cfg) + { + if let Err(last_error) = result { + tracing::debug!("{}", DisplayErrorContext(&*last_error)); + } + result = Err(new_error); + } + } + } + result.map_err(InterceptorError::$interceptor) + } + }; + (ref $interceptor:ident) => { + pub(crate) fn $interceptor( + self, + ctx: &InterceptorContext, + runtime_components: &RuntimeComponents, + cfg: &mut ConfigBag, + ) -> Result<(), InterceptorError> { + let mut result: Result<(), BoxError> = Ok(()); + let ctx = ctx.into(); + for interceptor in self.into_iter() { + if let Some(interceptor) = interceptor.if_enabled(cfg) { + if let Err(new_error) = interceptor.$interceptor(&ctx, runtime_components, cfg) + { + if let Err(last_error) = result { + tracing::debug!("{}", DisplayErrorContext(&*last_error)); + } + result = Err(new_error); + } + } + } + result.map_err(InterceptorError::$interceptor) + } + }; +} + +#[derive(Debug)] +pub(crate) struct Interceptors { + interceptors: I, +} + +impl Interceptors +where + I: Iterator, +{ + pub(crate) fn new(interceptors: I) -> Self { + Self { interceptors } + } + + fn into_iter(self) -> impl Iterator { + self.interceptors.map(ConditionallyEnabledInterceptor) + } + + pub(crate) fn read_before_execution( + self, + operation: bool, + ctx: &InterceptorContext, + cfg: &mut ConfigBag, + ) -> Result<(), InterceptorError> { + tracing::trace!( + "running {} `read_before_execution` interceptors", + if operation { "operation" } else { "client" } + ); + let mut result: Result<(), BoxError> = Ok(()); + let ctx: BeforeSerializationInterceptorContextRef<'_> = ctx.into(); + for interceptor in self.into_iter() { + if let Some(interceptor) = interceptor.if_enabled(cfg) { + if let Err(new_error) = interceptor.read_before_execution(&ctx, cfg) { + if let Err(last_error) = result { + tracing::debug!("{}", DisplayErrorContext(&*last_error)); + } + result = Err(new_error); + } + } + } + result.map_err(InterceptorError::read_before_execution) + } + + interceptor_impl_fn!(mut modify_before_serialization); + interceptor_impl_fn!(ref read_before_serialization); + interceptor_impl_fn!(ref read_after_serialization); + interceptor_impl_fn!(mut modify_before_retry_loop); + interceptor_impl_fn!(ref read_before_attempt); + interceptor_impl_fn!(mut modify_before_signing); + interceptor_impl_fn!(ref read_before_signing); + interceptor_impl_fn!(ref read_after_signing); + interceptor_impl_fn!(mut modify_before_transmit); + interceptor_impl_fn!(ref read_before_transmit); + interceptor_impl_fn!(ref read_after_transmit); + interceptor_impl_fn!(mut modify_before_deserialization); + interceptor_impl_fn!(ref read_before_deserialization); + interceptor_impl_fn!(ref read_after_deserialization); + + pub(crate) fn modify_before_attempt_completion( + self, + ctx: &mut InterceptorContext, + runtime_components: &RuntimeComponents, + cfg: &mut ConfigBag, + ) -> Result<(), InterceptorError> { + tracing::trace!("running `modify_before_attempt_completion` interceptors"); + let mut result: Result<(), BoxError> = Ok(()); + let mut ctx: FinalizerInterceptorContextMut<'_> = ctx.into(); + for interceptor in self.into_iter() { + if let Some(interceptor) = interceptor.if_enabled(cfg) { + if let Err(new_error) = + interceptor.modify_before_attempt_completion(&mut ctx, runtime_components, cfg) + { + if let Err(last_error) = result { + tracing::debug!("{}", DisplayErrorContext(&*last_error)); + } + result = Err(new_error); + } + } + } + result.map_err(InterceptorError::modify_before_attempt_completion) + } + + pub(crate) fn read_after_attempt( + self, + ctx: &InterceptorContext, + runtime_components: &RuntimeComponents, + cfg: &mut ConfigBag, + ) -> Result<(), InterceptorError> { + tracing::trace!("running `read_after_attempt` interceptors"); + let mut result: Result<(), BoxError> = Ok(()); + let ctx: FinalizerInterceptorContextRef<'_> = ctx.into(); + for interceptor in self.into_iter() { + if let Some(interceptor) = interceptor.if_enabled(cfg) { + if let Err(new_error) = + interceptor.read_after_attempt(&ctx, runtime_components, cfg) + { + if let Err(last_error) = result { + tracing::debug!("{}", DisplayErrorContext(&*last_error)); + } + result = Err(new_error); + } + } + } + result.map_err(InterceptorError::read_after_attempt) + } + + pub(crate) fn modify_before_completion( + self, + ctx: &mut InterceptorContext, + runtime_components: &RuntimeComponents, + cfg: &mut ConfigBag, + ) -> Result<(), InterceptorError> { + tracing::trace!("running `modify_before_completion` interceptors"); + let mut result: Result<(), BoxError> = Ok(()); + let mut ctx: FinalizerInterceptorContextMut<'_> = ctx.into(); + for interceptor in self.into_iter() { + if let Some(interceptor) = interceptor.if_enabled(cfg) { + if let Err(new_error) = + interceptor.modify_before_completion(&mut ctx, runtime_components, cfg) + { + if let Err(last_error) = result { + tracing::debug!("{}", DisplayErrorContext(&*last_error)); + } + result = Err(new_error); + } + } + } + result.map_err(InterceptorError::modify_before_completion) + } + + pub(crate) fn read_after_execution( + self, + ctx: &InterceptorContext, + runtime_components: &RuntimeComponents, + cfg: &mut ConfigBag, + ) -> Result<(), InterceptorError> { + tracing::trace!("running `read_after_execution` interceptors"); + let mut result: Result<(), BoxError> = Ok(()); + let ctx: FinalizerInterceptorContextRef<'_> = ctx.into(); + for interceptor in self.into_iter() { + if let Some(interceptor) = interceptor.if_enabled(cfg) { + if let Err(new_error) = + interceptor.read_after_execution(&ctx, runtime_components, cfg) + { + if let Err(last_error) = result { + tracing::debug!("{}", DisplayErrorContext(&*last_error)); + } + result = Err(new_error); + } + } + } + result.map_err(InterceptorError::read_after_execution) + } +} + +/// A interceptor wrapper to conditionally enable the interceptor based on +/// [`DisableInterceptor`](aws_smithy_runtime_api::client::interceptors::DisableInterceptor) +struct ConditionallyEnabledInterceptor(SharedInterceptor); +impl ConditionallyEnabledInterceptor { + fn if_enabled(&self, cfg: &ConfigBag) -> Option<&dyn Interceptor> { + if self.0.enabled(cfg) { + Some(self.0.as_ref()) + } else { + None + } + } +} + pub struct MapRequestInterceptor { f: F, _phantom: PhantomData, @@ -86,3 +317,71 @@ where Ok(()) } } + +#[cfg(all(test, feature = "test-util"))] +mod tests { + use super::*; + use aws_smithy_runtime_api::box_error::BoxError; + use aws_smithy_runtime_api::client::interceptors::context::{ + BeforeTransmitInterceptorContextRef, Input, InterceptorContext, + }; + use aws_smithy_runtime_api::client::interceptors::{ + disable_interceptor, Interceptor, SharedInterceptor, + }; + use aws_smithy_runtime_api::client::runtime_components::{ + RuntimeComponents, RuntimeComponentsBuilder, + }; + use aws_smithy_types::config_bag::ConfigBag; + + #[derive(Debug)] + struct TestInterceptor; + impl Interceptor for TestInterceptor {} + + #[test] + fn test_disable_interceptors() { + #[derive(Debug)] + struct PanicInterceptor; + impl Interceptor for PanicInterceptor { + fn read_before_transmit( + &self, + _context: &BeforeTransmitInterceptorContextRef<'_>, + _rc: &RuntimeComponents, + _cfg: &mut ConfigBag, + ) -> Result<(), BoxError> { + Err("boom".into()) + } + } + let rc = RuntimeComponentsBuilder::for_tests() + .with_interceptor(SharedInterceptor::new(PanicInterceptor)) + .with_interceptor(SharedInterceptor::new(TestInterceptor)) + .build() + .unwrap(); + + let mut cfg = ConfigBag::base(); + let interceptors = Interceptors::new(rc.interceptors()); + assert_eq!( + interceptors + .into_iter() + .filter(|i| i.if_enabled(&cfg).is_some()) + .count(), + 2 + ); + + Interceptors::new(rc.interceptors()) + .read_before_transmit(&InterceptorContext::new(Input::new(5)), &rc, &mut cfg) + .expect_err("interceptor returns error"); + cfg.interceptor_state() + .store_put(disable_interceptor::("test")); + assert_eq!( + Interceptors::new(rc.interceptors()) + .into_iter() + .filter(|i| i.if_enabled(&cfg).is_some()) + .count(), + 1 + ); + // shouldn't error because interceptors won't run + Interceptors::new(rc.interceptors()) + .read_before_transmit(&InterceptorContext::new(Input::new(5)), &rc, &mut cfg) + .expect("interceptor is now disabled"); + } +} diff --git a/rust-runtime/aws-smithy-runtime/src/client/orchestrator.rs b/rust-runtime/aws-smithy-runtime/src/client/orchestrator.rs index 15c3b0791c2f3eece5ca4d58ecfc2e52167dc27c..2adaab14e252e8ef233c6d54f244168a2d95fed2 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/orchestrator.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/orchestrator.rs @@ -7,6 +7,7 @@ #![allow(unknown_lints)] use self::auth::orchestrate_auth; +use crate::client::interceptors::Interceptors; use crate::client::orchestrator::endpoints::orchestrate_endpoint; use crate::client::orchestrator::http::read_body; use crate::client::timeout::{MaybeTimeout, MaybeTimeoutConfig, TimeoutKind}; @@ -19,7 +20,6 @@ use aws_smithy_runtime_api::client::connectors::HttpConnector; use aws_smithy_runtime_api::client::interceptors::context::{ Error, Input, InterceptorContext, Output, RewindResult, }; -use aws_smithy_runtime_api::client::interceptors::Interceptors; use aws_smithy_runtime_api::client::orchestrator::{ HttpResponse, LoadedRequestBody, OrchestratorError, }; @@ -200,7 +200,10 @@ async fn try_op( debug!("loading request body into memory"); let mut body = SdkBody::taken(); mem::swap(&mut body, ctx.request_mut().expect("set above").body_mut()); - let loaded_body = halt_on_err!([ctx] => ByteStream::new(body).collect().await).into_bytes(); + let loaded_body = halt_on_err!([ctx] => + ByteStream::new(body).collect().await.map_err(OrchestratorError::other) + ) + .into_bytes(); *ctx.request_mut().as_mut().expect("set above").body_mut() = SdkBody::from(loaded_body.clone()); cfg.interceptor_state() diff --git a/rust-runtime/aws-smithy-runtime/src/client/retries/classifier.rs b/rust-runtime/aws-smithy-runtime/src/client/retries/classifier.rs index 86bbdde0f61635735f207b93fb25f849264bf9c0..5ff4911f0feeb3ca606c0d4c307a89fd335f4575 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/retries/classifier.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/retries/classifier.rs @@ -4,7 +4,6 @@ */ use aws_smithy_runtime_api::client::interceptors::context::InterceptorContext; -use aws_smithy_runtime_api::client::orchestrator::OrchestratorError; use aws_smithy_runtime_api::client::retries::{ClassifyRetry, RetryReason}; use aws_smithy_types::retry::{ErrorKind, ProvideErrorKind}; use std::borrow::Cow; @@ -77,17 +76,16 @@ where Err(err) => err, }; - match error { - OrchestratorError::Response { .. } | OrchestratorError::Timeout { .. } => { - Some(RetryReason::Error(ErrorKind::TransientError)) - } - OrchestratorError::Connector { err } if err.is_timeout() || err.is_io() => { + if error.is_response_error() || error.is_timeout_error() { + Some(RetryReason::Error(ErrorKind::TransientError)) + } else if let Some(error) = error.as_connector_error() { + if error.is_timeout() || error.is_io() { Some(RetryReason::Error(ErrorKind::TransientError)) + } else { + error.as_other().map(RetryReason::Error) } - OrchestratorError::Connector { err } if err.is_other().is_some() => { - err.is_other().map(RetryReason::Error) - } - _ => None, + } else { + None } }