Revise unhandled error variant according to RFC-39 (#3191)

This PR implements
[RFC-39](https://github.com/smithy-lang/smithy-rs/blob/main/design/src/rfcs/rfc0039_forward_compatible_errors.md)
with a couple slight deviations:
- No `introspect` method is added since `Error` already implements
`ProvideErrorMetadata`.
- The same opaqueness and deprecation pointer is applied to the enum
unknown variant for consistency.

----

_By submitting this pull request, I confirm that you can use, modify,
copy, and redistribute this contribution, under the terms of your
choice._
This commit is contained in:
John DiSanti 2023-11-15 10:12:13 -08:00 committed by GitHub
parent c0f72fbfe8
commit c830caa281
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 292 additions and 81 deletions

View File

@ -119,3 +119,61 @@ message = "Remove deprecated error kind type aliases."
references = ["smithy-rs#3189"]
meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "client" }
author = "jdisanti"
[[aws-sdk-rust]]
message = """
Unhandled errors have been made opaque to ensure code is written in a future-proof manner. Where previously, you
might have:
```rust
match service_error.err() {
GetStorageError::StorageAccessNotAuthorized(_) => { /* ... */ }
GetStorageError::Unhandled(unhandled) if unhandled.code() == Some("SomeUnmodeledErrorCode") {
// unhandled error handling
}
_ => { /* ... */ }
}
```
It should now look as follows:
```rust
match service_error.err() {
GetStorageError::StorageAccessNotAuthorized(_) => { /* ... */ }
err if err.code() == Some("SomeUnmodeledErrorCode") {
// unhandled error handling
}
_ => { /* ... */ }
}
```
The `Unhandled` variant should never be referenced directly.
"""
references = ["smithy-rs#3191"]
meta = { "breaking" = true, "tada" = false, "bug" = false }
author = "jdisanti"
[[smithy-rs]]
message = """
Unhandled errors have been made opaque to ensure code is written in a future-proof manner. Where previously, you
might have:
```rust
match service_error.err() {
GetStorageError::StorageAccessNotAuthorized(_) => { /* ... */ }
GetStorageError::Unhandled(unhandled) if unhandled.code() == Some("SomeUnmodeledErrorCode") {
// unhandled error handling
}
_ => { /* ... */ }
}
```
It should now look as follows:
```rust
match service_error.err() {
GetStorageError::StorageAccessNotAuthorized(_) => { /* ... */ }
err if err.code() == Some("SomeUnmodeledErrorCode") {
// unhandled error handling
}
_ => { /* ... */ }
}
```
The `Unhandled` variant should never be referenced directly.
"""
references = ["smithy-rs#3191"]
meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "client" }
author = "jdisanti"

View File

@ -8,6 +8,7 @@ use aws_smithy_runtime_api::http::{Headers, Response};
use aws_smithy_types::error::metadata::{
Builder as ErrorMetadataBuilder, ErrorMetadata, ProvideErrorMetadata,
};
#[allow(deprecated)]
use aws_smithy_types::error::Unhandled;
const EXTENDED_REQUEST_ID: &str = "s3_extended_request_id";
@ -36,6 +37,7 @@ impl RequestIdExt for ErrorMetadata {
}
}
#[allow(deprecated)]
impl RequestIdExt for Unhandled {
fn extended_request_id(&self) -> Option<&str> {
self.meta().extended_request_id()

View File

@ -11,6 +11,8 @@ use aws_smithy_runtime_api::http::Response;
use aws_smithy_types::error::metadata::{
Builder as ErrorMetadataBuilder, ErrorMetadata, ProvideErrorMetadata,
};
#[allow(deprecated)]
use aws_smithy_types::error::Unhandled;
/// Constant for the [`ErrorMetadata`] extra field that contains the request ID
@ -38,6 +40,7 @@ impl RequestId for ErrorMetadata {
}
}
#[allow(deprecated)]
impl RequestId for Unhandled {
fn request_id(&self) -> Option<&str> {
self.meta().request_id()

View File

@ -146,7 +146,7 @@ abstract class BaseRequestIdDecorator : ClientCodegenDecorator {
val sym = codegenContext.symbolProvider.toSymbol(error)
rust("Self::${sym.name}(e) => #T,", wrapped)
}
rust("Self::Unhandled(e) => e.$accessorFunctionName(),")
rust("Self::Unhandled(e) => e.meta.$accessorFunctionName(),")
}
}
}

View File

@ -9,6 +9,7 @@ use aws_sdk_lambda::operation::RequestId;
use aws_sdk_lambda::{Client, Config};
use aws_smithy_runtime::client::http::test_util::infallible_client_fn;
#[allow(deprecated)]
async fn run_test(
response: impl Fn() -> http::Response<&'static str> + Send + Sync + 'static,
expect_error: bool,

View File

@ -7,6 +7,7 @@
use aws_credential_types::provider::SharedCredentialsProvider;
use aws_sdk_s3::config::{Credentials, Region};
use aws_sdk_s3::operation::list_objects_v2::ListObjectsV2Error;
use aws_sdk_s3::{Client, Config};
use aws_smithy_runtime::client::http::test_util::capture_request;
@ -58,8 +59,8 @@ async fn test_s3_signer_query_string_with_all_valid_chars() {
// test must be run against an actual bucket so we `ignore` it unless the runner specifically requests it
#[tokio::test]
#[ignore]
#[allow(deprecated)]
async fn test_query_strings_are_correctly_encoded() {
use aws_sdk_s3::operation::list_objects_v2::ListObjectsV2Error;
use aws_smithy_runtime_api::client::result::SdkError;
tracing_subscriber::fmt::init();
@ -80,22 +81,19 @@ async fn test_query_strings_are_correctly_encoded() {
.send()
.await;
if let Err(SdkError::ServiceError(context)) = res {
match context.err() {
ListObjectsV2Error::Unhandled(e)
if e.to_string().contains("SignatureDoesNotMatch") =>
{
chars_that_break_signing.push(byte);
}
ListObjectsV2Error::Unhandled(e) if e.to_string().contains("InvalidUri") => {
chars_that_break_uri_parsing.push(byte);
}
ListObjectsV2Error::Unhandled(e) if e.to_string().contains("InvalidArgument") => {
chars_that_are_invalid_arguments.push(byte);
}
ListObjectsV2Error::Unhandled(e) if e.to_string().contains("InvalidToken") => {
panic!("refresh your credentials and run this test again");
}
e => todo!("unexpected error: {:?}", e),
let err = context.err();
let msg = err.to_string();
let unhandled = matches!(err, ListObjectsV2Error::Unhandled(_));
if unhandled && msg.contains("SignatureDoesNotMatch") {
chars_that_break_signing.push(byte);
} else if unhandled && msg.to_string().contains("InvalidUri") {
chars_that_break_uri_parsing.push(byte);
} else if unhandled && msg.to_string().contains("InvalidArgument") {
chars_that_are_invalid_arguments.push(byte);
} else if unhandled && msg.to_string().contains("InvalidToken") {
panic!("refresh your credentials and run this test again");
} else {
todo!("unexpected error: {:?}", err);
}
}
}

View File

@ -59,6 +59,7 @@ async fn get_request_id_from_modeled_error() {
}
#[tokio::test]
#[allow(deprecated)]
async fn get_request_id_from_unmodeled_error() {
let (http_client, request) = capture_request(Some(
http::Response::builder()

View File

@ -10,6 +10,7 @@ import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
import software.amazon.smithy.rust.codegen.client.smithy.ClientRustModule
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.docs
import software.amazon.smithy.rust.codegen.core.rustlang.rust
@ -75,12 +76,39 @@ data class InfallibleEnumType(
)
}
override fun additionalEnumImpls(context: EnumGeneratorContext): Writable = writable {
// `try_parse` isn't needed for unnamed enums
if (context.enumTrait.hasNames()) {
rustTemplate(
"""
impl ${context.enumName} {
/// Parses the enum value while disallowing unknown variants.
///
/// Unknown variants will result in an error.
pub fn try_parse(value: &str) -> #{Result}<Self, #{UnknownVariantError}> {
match Self::from(value) {
##[allow(deprecated)]
Self::Unknown(_) => #{Err}(#{UnknownVariantError}::new(value)),
known => Ok(known),
}
}
}
""",
*preludeScope,
"UnknownVariantError" to unknownVariantError(),
)
}
}
override fun additionalDocs(context: EnumGeneratorContext): Writable = writable {
renderForwardCompatibilityNote(context.enumName, context.sortedMembers, UnknownVariant, UnknownVariantValue)
}
override fun additionalEnumMembers(context: EnumGeneratorContext): Writable = writable {
docs("`$UnknownVariant` contains new variants that have been added since this code was generated.")
rust(
"""##[deprecated(note = "Don't directly match on `$UnknownVariant`. See the docs on this enum for the correct way to handle unknown variants.")]""",
)
rust("$UnknownVariant(#T)", unknownVariantValue(context))
}
@ -93,10 +121,9 @@ data class InfallibleEnumType(
docs(
"""
Opaque struct used as inner data for the `Unknown` variant defined in enums in
the crate
the crate.
While this is not intended to be used directly, it is marked as `pub` because it is
part of the enums that are public interface.
This is not intended to be used directly.
""".trimIndent(),
)
context.enumMeta.render(this)
@ -174,5 +201,35 @@ class ClientEnumGenerator(codegenContext: ClientCodegenContext, shape: StringSha
codegenContext.model,
codegenContext.symbolProvider,
shape,
InfallibleEnumType(ClientRustModule.primitives),
InfallibleEnumType(
RustModule.new(
"sealed_enum_unknown",
visibility = Visibility.PUBCRATE,
parent = ClientRustModule.primitives,
),
),
)
private fun unknownVariantError(): RuntimeType = RuntimeType.forInlineFun("UnknownVariantError", ClientRustModule.Error) {
rustTemplate(
"""
/// The given enum value failed to parse since it is not a known value.
##[derive(Debug)]
pub struct UnknownVariantError {
value: #{String},
}
impl UnknownVariantError {
pub(crate) fn new(value: impl #{Into}<#{String}>) -> Self {
Self { value: value.into() }
}
}
impl ::std::fmt::Display for UnknownVariantError {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> #{Result}<(), ::std::fmt::Error> {
write!(f, "unknown enum variant: '{}'", self.value)
}
}
impl ::std::error::Error for UnknownVariantError {}
""",
*preludeScope,
)
}

View File

@ -28,7 +28,6 @@ import software.amazon.smithy.rust.codegen.core.rustlang.writable
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType.Companion.errorMetadata
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType.Companion.preludeScope
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType.Companion.unhandledError
import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider
import software.amazon.smithy.rust.codegen.core.smithy.customize.Section
import software.amazon.smithy.rust.codegen.core.smithy.customize.writeCustomizations
@ -83,12 +82,14 @@ class OperationErrorGenerator(
val errorVariantSymbol = symbolProvider.toSymbol(errorVariant)
write("${errorVariantSymbol.name}(#T),", errorVariantSymbol)
}
rust(
rustTemplate(
"""
/// An unexpected error occurred (e.g., invalid JSON returned by the service or an unknown error code).
Unhandled(#T),
#{deprecation}
Unhandled(#{Unhandled}),
""",
unhandledError(runtimeConfig),
"deprecation" to writable { renderUnhandledErrorDeprecation(runtimeConfig, errorSymbol.name) },
"Unhandled" to unhandledError(runtimeConfig),
)
}
@ -114,15 +115,9 @@ class OperationErrorGenerator(
"StdError" to RuntimeType.StdError,
"ErrorMeta" to errorMetadata,
) {
rust(
"""
Self::Unhandled({
let mut builder = #T::builder().source(source);
builder.set_meta(meta);
builder.build()
})
""",
unhandledError(runtimeConfig),
rustTemplate(
"""Self::Unhandled(#{Unhandled} { source, meta: meta.unwrap_or_default() })""",
"Unhandled" to unhandledError(runtimeConfig),
)
}
}
@ -131,8 +126,23 @@ class OperationErrorGenerator(
private fun RustWriter.renderImplDisplay(errorSymbol: Symbol, errors: List<StructureShape>) {
rustBlock("impl #T for ${errorSymbol.name}", RuntimeType.Display) {
rustBlock("fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result") {
delegateToVariants(errors) {
writable { rust("_inner.fmt(f)") }
delegateToVariants(errors) { variantMatch ->
when (variantMatch) {
is VariantMatch.Unhandled -> writable {
rustTemplate(
"""
if let #{Some}(code) = #{ProvideErrorMetadata}::code(self) {
write!(f, "unhandled error ({code})")
} else {
f.write_str("unhandled error")
}
""",
*preludeScope,
"ProvideErrorMetadata" to RuntimeType.provideErrorMetadataTrait(runtimeConfig),
)
}
is VariantMatch.Modeled -> writable { rust("_inner.fmt(f)") }
}
}
}
}
@ -142,8 +152,13 @@ class OperationErrorGenerator(
val errorMetadataTrait = RuntimeType.provideErrorMetadataTrait(runtimeConfig)
rustBlock("impl #T for ${errorSymbol.name}", errorMetadataTrait) {
rustBlock("fn meta(&self) -> &#T", errorMetadata(runtimeConfig)) {
delegateToVariants(errors) {
writable { rust("#T::meta(_inner)", errorMetadataTrait) }
delegateToVariants(errors) { variantMatch ->
writable {
when (variantMatch) {
is VariantMatch.Unhandled -> rust("&_inner.meta")
is VariantMatch.Modeled -> rust("#T::meta(_inner)", errorMetadataTrait)
}
}
}
}
}
@ -189,16 +204,16 @@ class OperationErrorGenerator(
"""
/// Creates the `${errorSymbol.name}::Unhandled` variant from any error type.
pub fn unhandled(err: impl #{Into}<#{Box}<dyn #{StdError} + #{Send} + #{Sync} + 'static>>) -> Self {
Self::Unhandled(#{Unhandled}::builder().source(err).build())
Self::Unhandled(#{Unhandled} { source: err.into(), meta: #{Default}::default() })
}
/// Creates the `${errorSymbol.name}::Unhandled` variant from a `#{error_metadata}`.
pub fn generic(err: #{error_metadata}) -> Self {
Self::Unhandled(#{Unhandled}::builder().source(err.clone()).meta(err).build())
/// Creates the `${errorSymbol.name}::Unhandled` variant from an [`ErrorMetadata`](#{ErrorMetadata}).
pub fn generic(err: #{ErrorMetadata}) -> Self {
Self::Unhandled(#{Unhandled} { source: err.clone().into(), meta: err })
}
""",
*preludeScope,
"error_metadata" to errorMetadata,
"ErrorMetadata" to errorMetadata,
"StdError" to RuntimeType.StdError,
"Unhandled" to unhandledError(runtimeConfig),
)
@ -209,13 +224,15 @@ class OperationErrorGenerator(
""",
)
rustBlock("pub fn meta(&self) -> &#T", errorMetadata) {
rust("use #T;", RuntimeType.provideErrorMetadataTrait(runtimeConfig))
rustBlock("match self") {
errors.forEach { error ->
val errorVariantSymbol = symbolProvider.toSymbol(error)
rust("Self::${errorVariantSymbol.name}(e) => e.meta(),")
rustTemplate(
"Self::${errorVariantSymbol.name}(e) => #{ProvideErrorMetadata}::meta(e),",
"ProvideErrorMetadata" to RuntimeType.provideErrorMetadataTrait(runtimeConfig),
)
}
rust("Self::Unhandled(e) => e.meta(),")
rust("Self::Unhandled(e) => &e.meta,")
}
}
errors.forEach { error ->
@ -236,9 +253,14 @@ class OperationErrorGenerator(
*preludeScope,
"StdError" to RuntimeType.StdError,
) {
delegateToVariants(errors) {
writable {
rustTemplate("#{Some}(_inner)", *preludeScope)
delegateToVariants(errors) { variantMatch ->
when (variantMatch) {
is VariantMatch.Unhandled -> writable {
rustTemplate("#{Some}(&*_inner.source)", *preludeScope)
}
is VariantMatch.Modeled -> writable {
rustTemplate("#{Some}(_inner)", *preludeScope)
}
}
}
}

View File

@ -9,6 +9,7 @@ import software.amazon.smithy.codegen.core.Symbol
import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.model.shapes.ShapeId
import software.amazon.smithy.model.shapes.StructureShape
import software.amazon.smithy.rust.codegen.client.smithy.ClientRustModule
import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
import software.amazon.smithy.rust.codegen.core.rustlang.RustMetadata
import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
@ -24,9 +25,9 @@ import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
import software.amazon.smithy.rust.codegen.core.rustlang.writable
import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext
import software.amazon.smithy.rust.codegen.core.smithy.CodegenTarget
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType.Companion.preludeScope
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType.Companion.unhandledError
import software.amazon.smithy.rust.codegen.core.smithy.RustCrate
import software.amazon.smithy.rust.codegen.core.smithy.customize.writeCustomizations
import software.amazon.smithy.rust.codegen.core.smithy.generators.operationBuildError
@ -91,7 +92,7 @@ class ServiceErrorGenerator(
allErrors.forEach {
rust("Error::${symbolProvider.toSymbol(it).name}(inner) => inner.source(),")
}
rust("Error::Unhandled(inner) => inner.source()")
rustTemplate("Error::Unhandled(inner) => #{Some}(&*inner.source)", *preludeScope)
}
}
}
@ -107,7 +108,17 @@ class ServiceErrorGenerator(
allErrors.forEach {
rust("Error::${symbolProvider.toSymbol(it).name}(inner) => inner.fmt(f),")
}
rust("Error::Unhandled(inner) => inner.fmt(f)")
rustTemplate(
"""
Error::Unhandled(_) => if let #{Some}(code) = #{ProvideErrorMetadata}::code(self) {
write!(f, "unhandled error ({code})")
} else {
f.write_str("unhandled error")
}
""",
*preludeScope,
"ProvideErrorMetadata" to RuntimeType.provideErrorMetadataTrait(codegenContext.runtimeConfig),
)
}
}
}
@ -118,11 +129,12 @@ class ServiceErrorGenerator(
"""
impl From<#{BuildError}> for Error {
fn from(value: #{BuildError}) -> Self {
Error::Unhandled(#{Unhandled}::builder().source(value).build())
Error::Unhandled(#{Unhandled} { source: value.into(), meta: #{Default}::default() })
}
}
""",
*preludeScope,
"BuildError" to codegenContext.runtimeConfig.operationBuildError(),
"Unhandled" to unhandledError(codegenContext.runtimeConfig),
)
@ -146,10 +158,10 @@ class ServiceErrorGenerator(
rustTemplate(
"""
_ => Error::Unhandled(
#{Unhandled}::builder()
.meta(#{ProvideErrorMetadata}::meta(&err).clone())
.source(err)
.build()
#{Unhandled} {
meta: #{ProvideErrorMetadata}::meta(&err).clone(),
source: err.into(),
}
),
""",
"Unhandled" to unhandledError(codegenContext.runtimeConfig),
@ -187,7 +199,7 @@ class ServiceErrorGenerator(
fn meta(&self) -> &#{ErrorMetadata} {
match self {
#{matchers}
Self::Unhandled(inner) => inner.meta(),
Self::Unhandled(inner) => &inner.meta,
}
}
}
@ -220,7 +232,57 @@ class ServiceErrorGenerator(
rust("${sym.name}(#T),", sym)
}
docs("An unexpected error occurred (e.g., invalid JSON returned by the service or an unknown error code).")
renderUnhandledErrorDeprecation(codegenContext.runtimeConfig, "Error")
rust("Unhandled(#T)", unhandledError(codegenContext.runtimeConfig))
}
}
}
fun unhandledError(rc: RuntimeConfig): RuntimeType = RuntimeType.forInlineFun(
"Unhandled",
// Place in a sealed module so that it can't be referenced at all
RustModule.pubCrate("sealed_unhandled", ClientRustModule.Error),
) {
rustTemplate(
"""
/// This struct is not intended to be used.
///
/// This struct holds information about an unhandled error,
/// but that information should be obtained by using the
/// [`ProvideErrorMetadata`](#{ProvideErrorMetadata}) trait
/// on the error type.
///
/// This struct intentionally doesn't yield any useful information itself.
#{deprecation}
##[derive(Debug)]
pub struct Unhandled {
pub(crate) source: #{BoxError},
pub(crate) meta: #{ErrorMetadata},
}
""",
"BoxError" to RuntimeType.smithyRuntimeApi(rc).resolve("box_error::BoxError"),
"deprecation" to writable { renderUnhandledErrorDeprecation(rc) },
"ErrorMetadata" to RuntimeType.smithyTypes(rc).resolve("error::metadata::ErrorMetadata"),
"ProvideErrorMetadata" to RuntimeType.smithyTypes(rc).resolve("error::metadata::ProvideErrorMetadata"),
)
}
fun RustWriter.renderUnhandledErrorDeprecation(rc: RuntimeConfig, errorName: String? = null) {
val link = if (errorName != null) {
"##impl-ProvideErrorMetadata-for-$errorName"
} else {
"#{ProvideErrorMetadata}"
}
val message = """
Matching `Unhandled` directly is not forwards compatible. Instead, match using a
variable wildcard pattern and check `.code()`:<br/>
&nbsp;&nbsp;&nbsp;`err if err.code() == Some("SpecificExceptionCode") => { /* handle the error */ }`<br/>
See [`ProvideErrorMetadata`]($link) for what information is available for the error.
""".trimIndent()
// `.dq()` doesn't quite do what we want here since we actually want a Rust multi-line string
val messageEscaped = message.replace("\"", "\\\"").replace("\n", " \\\n").replace("<br/>", "\n")
rustTemplate(
"""##[deprecated(note = "$messageEscaped")]""",
"ProvideErrorMetadata" to RuntimeType.provideErrorMetadataTrait(rc),
)
}

View File

@ -119,7 +119,10 @@ class ClientEnumGeneratorTest {
"""
assert_eq!(SomeEnum::from("Unknown"), SomeEnum::UnknownValue);
assert_eq!(SomeEnum::from("UnknownValue"), SomeEnum::UnknownValue_);
assert_eq!(SomeEnum::from("SomethingNew"), SomeEnum::Unknown(crate::primitives::UnknownVariantValue("SomethingNew".to_owned())));
assert_eq!(
SomeEnum::from("SomethingNew"),
SomeEnum::Unknown(crate::primitives::sealed_enum_unknown::UnknownVariantValue("SomethingNew".to_owned()))
);
""",
)
}
@ -150,7 +153,10 @@ class ClientEnumGeneratorTest {
assert_eq!(instance.as_str(), "t2.micro");
assert_eq!(InstanceType::from("t2.nano"), InstanceType::T2Nano);
// round trip unknown variants:
assert_eq!(InstanceType::from("other"), InstanceType::Unknown(crate::primitives::UnknownVariantValue("other".to_owned())));
assert_eq!(
InstanceType::from("other"),
InstanceType::Unknown(crate::primitives::sealed_enum_unknown::UnknownVariantValue("other".to_owned()))
);
assert_eq!(InstanceType::from("other").as_str(), "other");
""",
)

View File

@ -51,7 +51,7 @@ class ClientEventStreamUnmarshallerGeneratorTest {
let result = $generator::new().unmarshall(&message);
assert!(result.is_ok(), "expected ok, got: {:?}", result);
match expect_error(result.unwrap()) {
TestStreamError::Unhandled(err) => {
err @ TestStreamError::Unhandled(_) => {
let message = format!("{}", crate::error::DisplayErrorContext(&err));
let expected = "message: \"unmodeled error\"";
assert!(message.contains(expected), "Expected '{message}' to contain '{expected}'");

View File

@ -425,7 +425,6 @@ data class RuntimeType(val path: String, val dependency: RustDependency? = null)
fun provideErrorMetadataTrait(runtimeConfig: RuntimeConfig) =
smithyTypes(runtimeConfig).resolve("error::metadata::ProvideErrorMetadata")
fun unhandledError(runtimeConfig: RuntimeConfig) = smithyTypes(runtimeConfig).resolve("error::Unhandled")
fun jsonErrors(runtimeConfig: RuntimeConfig) = forInlineDependency(InlineDependency.jsonErrors(runtimeConfig))
fun awsQueryCompatibleErrors(runtimeConfig: RuntimeConfig) =
forInlineDependency(InlineDependency.awsQueryCompatibleErrors(runtimeConfig))

View File

@ -2,21 +2,20 @@
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
/// Copyright © 2023, Amazon, LLC.
///
/// This example demonstrates how to handle service generated errors.
///
/// The example assumes that the Pokémon service is running on the localhost on TCP port 13734.
/// Refer to the [README.md](https://github.com/smithy-lang/smithy-rs/tree/main/examples/pokemon-service-client-usage/README.md)
/// file for instructions on how to launch the service locally.
///
/// The example can be run using `cargo run --example handling-errors`.
///
//! This example demonstrates how to handle service generated errors.
//!
//! The example assumes that the Pokémon service is running on the localhost on TCP port 13734.
//! Refer to the [README.md](https://github.com/smithy-lang/smithy-rs/tree/main/examples/pokemon-service-client-usage/README.md)
//! file for instructions on how to launch the service locally.
//!
//! The example can be run using `cargo run --example handling-errors`.
use pokemon_service_client::error::DisplayErrorContext;
use pokemon_service_client::Client as PokemonClient;
use pokemon_service_client::{error::SdkError, operation::get_storage::GetStorageError};
use pokemon_service_client_usage::{setup_tracing_subscriber, POKEMON_SERVICE_URL};
use pokemon_service_client::Client as PokemonClient;
/// Creates a new `smithy-rs` client that is configured to communicate with a locally running Pokémon service on TCP port 13734.
///
/// # Examples
@ -77,14 +76,10 @@ async fn main() {
GetStorageError::ValidationError(ve) => {
tracing::error!(error = %ve, "A required field has not been set.");
}
// An unexpected error occurred (e.g., invalid JSON returned by the service or an unknown error code).
GetStorageError::Unhandled(uh) => {
tracing::error!(error = %uh, "An unhandled error has occurred.")
}
// The SdkError is marked as `#[non_exhaustive]`. Therefore, a catch-all pattern is required to handle
// potential future variants introduced in SdkError.
_ => {
tracing::error!(error = %se.err(), "Some other error has occurred on the server")
tracing::error!(error = %DisplayErrorContext(se.err()), "Some other error has occurred on the server")
}
}
}

View File

@ -8,5 +8,6 @@ allowed_external_types = [
"http::header::value::HeaderValue",
"http::request::Request",
"http::response::Response",
"http::status::StatusCode",
"http::uri::Uri",
]

View File

@ -13,4 +13,4 @@ mod response;
pub use error::HttpError;
pub use headers::{HeaderValue, Headers, HeadersIter};
pub use request::{Request, RequestParts};
pub use response::Response;
pub use response::{Response, StatusCode};

View File

@ -13,6 +13,8 @@ pub mod operation;
mod unhandled;
pub use metadata::ErrorMetadata;
#[allow(deprecated)]
pub use unhandled::Unhandled;
#[derive(Debug)]

View File

@ -3,11 +3,14 @@
* SPDX-License-Identifier: Apache-2.0
*/
#![allow(deprecated)]
//! Unhandled error type.
use crate::error::{metadata::ProvideErrorMetadata, ErrorMetadata};
use std::error::Error as StdError;
#[deprecated(note = "The `Unhandled` type is no longer used by errors.")]
/// Builder for [`Unhandled`]
#[derive(Default, Debug)]
pub struct Builder {
@ -58,6 +61,7 @@ impl Builder {
/// [`DisplayErrorContext`](crate::error::display::DisplayErrorContext), use another
/// error reporter library that visits the error's cause/source chain, or call
/// [`Error::source`](std::error::Error::source) for more details about the underlying cause.
#[deprecated(note = "This type is no longer used by errors.")]
#[derive(Debug)]
pub struct Unhandled {
source: Box<dyn StdError + Send + Sync + 'static>,