From 5a097a76fc273dd3ffcbc53df020e4aa458c723b Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Wed, 9 Nov 2022 15:05:34 -0800 Subject: [PATCH] Make `SdkError::into_service_error` infallible --- .../error/CombinedErrorGenerator.kt | 12 ++++++ rust-runtime/aws-smithy-http/src/result.rs | 38 +++++++++++++------ tools/ci-cdk/canary-lambda/src/s3_canary.rs | 5 +-- 3 files changed, 40 insertions(+), 15 deletions(-) diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/CombinedErrorGenerator.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/CombinedErrorGenerator.kt index d9f51d4cea3..1711b361a6c 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/CombinedErrorGenerator.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/CombinedErrorGenerator.kt @@ -13,11 +13,13 @@ import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.model.shapes.UnionShape import software.amazon.smithy.model.traits.RetryableTrait import software.amazon.smithy.rust.codegen.core.rustlang.Attribute +import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency import software.amazon.smithy.rust.codegen.core.rustlang.RustMetadata import software.amazon.smithy.rust.codegen.core.rustlang.RustModule import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter import software.amazon.smithy.rust.codegen.core.rustlang.Visibility import software.amazon.smithy.rust.codegen.core.rustlang.Writable +import software.amazon.smithy.rust.codegen.core.rustlang.asType import software.amazon.smithy.rust.codegen.core.rustlang.deprecatedShape import software.amazon.smithy.rust.codegen.core.rustlang.documentShape import software.amazon.smithy.rust.codegen.core.rustlang.rust @@ -124,6 +126,7 @@ class CombinedErrorGenerator( ) { private val runtimeConfig = symbolProvider.config().runtimeConfig private val genericError = RuntimeType.GenericError(symbolProvider.config().runtimeConfig) + private val createUnhandledError = CargoDependency.SmithyHttp(runtimeConfig).asType().member("result::CreateUnhandledError") fun render(writer: RustWriter) { val errorSymbol = RuntimeType("${operationSymbol.name}Error", null, "crate::error") @@ -154,6 +157,15 @@ class CombinedErrorGenerator( RuntimeType.GenericError(runtimeConfig), ) } + writer.rustBlock("impl #T for ${errorSymbol.name}", createUnhandledError) { + rustBlock("fn create_unhandled_error(source: Box) -> Self") { + rustBlock("Self") { + rust("kind: ${errorSymbol.name}Kind::Unhandled(#T::new(source)),", unhandledError()) + rust("meta: Default::default()") + } + } + } + writer.rust("/// Types of errors that can occur for the `${operationSymbol.name}` operation.") meta.render(writer) writer.rustBlock("enum ${errorSymbol.name}Kind") { diff --git a/rust-runtime/aws-smithy-http/src/result.rs b/rust-runtime/aws-smithy-http/src/result.rs index c76d61492ea..82ef8d11910 100644 --- a/rust-runtime/aws-smithy-http/src/result.rs +++ b/rust-runtime/aws-smithy-http/src/result.rs @@ -122,6 +122,14 @@ impl ServiceError { } } +/// Constructs the unhandled variant of a code generated error. +/// +/// This trait exists so that [`SdkError::into_service_error`] can be infallible. +pub trait CreateUnhandledError { + /// Creates an unhandled error variant with the given `source`. + fn create_unhandled_error(source: Box) -> Self; +} + /// Failed SDK Result #[non_exhaustive] #[derive(Debug)] @@ -179,34 +187,42 @@ impl SdkError { /// Returns the underlying service error `E` if there is one /// - /// If a service error is not available (for example, the error is a network timeout), - /// then the full `SdkError` is returned. This makes it easy to match on the service's - /// error response while simultaneously bubbling up transient failures. For example, - /// handling the `NoSuchKey` error for S3's `GetObject` operation may look as follows: + /// If the `SdkError` is not a `ServiceError` (for example, the error is a network timeout), + /// then it will be converted into an unhandled variant of `E`. This makes it easy to match + /// on the service's error response while simultaneously bubbling up transient failures. + /// For example, handling the `NoSuchKey` error for S3's `GetObject` operation may look as + /// follows: /// /// ```no_run - /// # use aws_smithy_http::result::SdkError; + /// # use aws_smithy_http::result::{SdkError, CreateUnhandledError}; /// # #[derive(Debug)] enum GetObjectErrorKind { NoSuchKey(()), Other(()) } /// # #[derive(Debug)] struct GetObjectError { kind: GetObjectErrorKind } /// # impl std::fmt::Display for GetObjectError { /// # fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { unimplemented!() } /// # } /// # impl std::error::Error for GetObjectError {} - /// # fn example() -> Result<(), Box> { + /// # impl CreateUnhandledError for GetObjectError { + /// # fn create_unhandled_error(_: Box) -> Self { unimplemented!() } + /// # } + /// # fn example() -> Result<(), GetObjectError> { /// # let sdk_err = SdkError::service_error(GetObjectError { kind: GetObjectErrorKind::NoSuchKey(()) }, ()); - /// match sdk_err.into_service_error()? { + /// match sdk_err.into_service_error() { /// GetObjectError { kind: GetObjectErrorKind::NoSuchKey(_) } => { /// // handle NoSuchKey /// } - /// err @ _ => return Err(err.into()), + /// err @ _ => return Err(err), /// } /// # Ok(()) /// # } /// ``` - pub fn into_service_error(self) -> Result { + pub fn into_service_error(self) -> E + where + E: std::error::Error + Send + Sync + CreateUnhandledError + 'static, + R: Debug + Send + Sync + 'static, + { match self { - Self::ServiceError(context) => Ok(context.source), - _ => Err(self), + Self::ServiceError(context) => context.source, + _ => E::create_unhandled_error(self.into()), } } diff --git a/tools/ci-cdk/canary-lambda/src/s3_canary.rs b/tools/ci-cdk/canary-lambda/src/s3_canary.rs index 29a571334c4..88a04ca66a4 100644 --- a/tools/ci-cdk/canary-lambda/src/s3_canary.rs +++ b/tools/ci-cdk/canary-lambda/src/s3_canary.rs @@ -34,10 +34,7 @@ pub async fn s3_canary(client: s3::Client, s3_bucket_name: String) -> anyhow::Re CanaryError(format!("Expected object {} to not exist in S3", test_key)).into(), ); } - Err(err) => match err - .into_service_error() - .context("unexpected s3::GetObject failure")? - { + Err(err) => match err.into_service_error() { GetObjectError { kind: GetObjectErrorKind::NoSuchKey(..), ..