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 e1307e45fd0..d9f51d4cea3 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 @@ -166,9 +166,9 @@ class CombinedErrorGenerator( rust( """ /// An unexpected error, e.g. invalid JSON returned by the service or an unknown error code - Unhandled(Box), + Unhandled(#T), """, - RuntimeType.StdError, + unhandledError(), ) } writer.rustBlock("impl #T for ${errorSymbol.name}", RuntimeType.Display) { @@ -215,7 +215,7 @@ class CombinedErrorGenerator( /// Creates the `${errorSymbol.name}::Unhandled` variant from any error type. pub fn unhandled(err: impl Into>) -> Self { Self { - kind: ${errorSymbol.name}Kind::Unhandled(err.into()), + kind: ${errorSymbol.name}Kind::Unhandled(#{Unhandled}::new(err.into())), meta: Default::default() } } @@ -224,7 +224,7 @@ class CombinedErrorGenerator( pub fn generic(err: #{generic_error}) -> Self { Self { meta: err.clone(), - kind: ${errorSymbol.name}Kind::Unhandled(err.into()), + kind: ${errorSymbol.name}Kind::Unhandled(#{Unhandled}::new(err.into())), } } @@ -249,7 +249,9 @@ class CombinedErrorGenerator( self.meta.code() } """, - "generic_error" to genericError, "std_error" to RuntimeType.StdError, + "generic_error" to genericError, + "std_error" to RuntimeType.StdError, + "Unhandled" to unhandledError(), ) errors.forEach { error -> val errorVariantSymbol = symbolProvider.toSymbol(error) @@ -265,10 +267,7 @@ class CombinedErrorGenerator( rustBlock("fn source(&self) -> Option<&(dyn #T + 'static)>", RuntimeType.StdError) { delegateToVariants(errors, errorSymbol) { writable { - when (it) { - is VariantMatch.Unhandled -> rust("Some(_inner.as_ref())") - is VariantMatch.Modeled -> rust("Some(_inner)") - } + rust("Some(_inner)") } } } diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/TopLevelErrorGenerator.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/TopLevelErrorGenerator.kt index f83b8b4b3bc..f8d074757c3 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/TopLevelErrorGenerator.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/TopLevelErrorGenerator.kt @@ -20,6 +20,7 @@ import software.amazon.smithy.rust.codegen.core.rustlang.documentShape import software.amazon.smithy.rust.codegen.core.rustlang.rust import software.amazon.smithy.rust.codegen.core.rustlang.rustBlock import software.amazon.smithy.rust.codegen.core.rustlang.rustBlockTemplate +import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext import software.amazon.smithy.rust.codegen.core.smithy.CodegenTarget import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType @@ -92,17 +93,17 @@ class TopLevelErrorGenerator(private val codegenContext: CodegenContext, private } } - private fun RustWriter.renderImplFrom(symbol: RuntimeType, errors: List) { + private fun RustWriter.renderImplFrom(errorSymbol: RuntimeType, errors: List) { if (errors.isNotEmpty() || CodegenTarget.CLIENT == codegenContext.target) { rustBlock( "impl From<#T<#T, R>> for Error where R: Send + Sync + std::fmt::Debug + 'static", sdkError, - symbol, + errorSymbol, ) { rustBlockTemplate( "fn from(err: #{SdkError}<#{OpError}, R>) -> Self", "SdkError" to sdkError, - "OpError" to symbol, + "OpError" to errorSymbol, ) { rustBlock("match err") { val operationErrors = errors.map { model.expectShape(it) } @@ -111,12 +112,16 @@ class TopLevelErrorGenerator(private val codegenContext: CodegenContext, private val errSymbol = symbolProvider.toSymbol(errorShape) rust( "#TKind::${errSymbol.name}(inner) => Error::${errSymbol.name}(inner),", - symbol, + errorSymbol, ) } - rust("#TKind::Unhandled(inner) => Error::Unhandled(inner),", symbol) + rustTemplate( + "#{errorSymbol}Kind::Unhandled(inner) => Error::Unhandled(#{unhandled}::new(inner.into())),", + "errorSymbol" to errorSymbol, + "unhandled" to unhandledError(), + ) } - rust("_ => Error::Unhandled(err.into()),") + rust("_ => Error::Unhandled(#T::new(err.into())),", unhandledError()) } } } @@ -137,7 +142,7 @@ class TopLevelErrorGenerator(private val codegenContext: CodegenContext, private rust("${sym.name}(#T),", sym) } rust("/// An unhandled error occurred.") - rust("Unhandled(Box)", RuntimeType.StdError) + rust("Unhandled(#T)", unhandledError()) } } } diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/UnhandledErrorGenerator.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/UnhandledErrorGenerator.kt new file mode 100644 index 00000000000..88e720d2c0e --- /dev/null +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/UnhandledErrorGenerator.kt @@ -0,0 +1,41 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.rust.codegen.core.smithy.generators.error + +import software.amazon.smithy.rust.codegen.core.rustlang.RustModule +import software.amazon.smithy.rust.codegen.core.rustlang.docs +import software.amazon.smithy.rust.codegen.core.rustlang.rust +import software.amazon.smithy.rust.codegen.core.rustlang.rustBlock +import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType + +internal fun unhandledError(): RuntimeType = RuntimeType.forInlineFun("Unhandled", RustModule.Error) { + docs( + """ + An unexpected error occurred (e.g., invalid JSON returned by the service or an unknown error code) + + Call [`Error::source`](std::error::Error::source) for more details about the underlying cause. + """, + ) + rust("##[derive(Debug)]") + rustBlock("pub struct Unhandled") { + rust("source: Box", RuntimeType.StdError) + } + rustBlock("impl Unhandled") { + rustBlock("pub(crate) fn new(source: Box) -> Self", RuntimeType.StdError) { + rust("Self { source }") + } + } + rustBlock("impl std::fmt::Display for Unhandled") { + rustBlock("fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error>") { + rust("write!(f, \"unhandled error\")") + } + } + rustBlock("impl std::error::Error for Unhandled") { + rustBlock("fn source(&self) -> Option<&(dyn std::error::Error + 'static)>") { + rust("Some(self.source.as_ref() as _)") + } + } +} diff --git a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/protocols/parse/EventStreamUnmarshallerGeneratorTest.kt b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/protocols/parse/EventStreamUnmarshallerGeneratorTest.kt index b2521012177..0b219a569d7 100644 --- a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/protocols/parse/EventStreamUnmarshallerGeneratorTest.kt +++ b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/protocols/parse/EventStreamUnmarshallerGeneratorTest.kt @@ -275,7 +275,9 @@ class EventStreamUnmarshallerGeneratorTest { assert!(result.is_ok(), "expected ok, got: {:?}", result); match expect_error(result.unwrap())$kindSuffix { TestStreamErrorKind::Unhandled(err) => { - assert!(format!("{}", err).contains("message: \"unmodeled error\"")); + let message = format!("{}", aws_smithy_types::error::display::DisplayErrorContext(&err)); + let expected = "message: \"unmodeled error\""; + assert!(message.contains(expected), "Expected '{message}' to contain '{expected}'"); } kind => panic!("expected generic error, but got {:?}", kind), }