Unverified Commit fc63800f authored by Antoine Büsch's avatar Antoine Büsch Committed by GitHub
Browse files

Implement `StdError::source()` for Error enum (#2564)

## Motivation and Context
This is an attempt at fixing
https://github.com/awslabs/aws-sdk-rust/issues/784.

The service-level `Error` enum implements `std::error::Error` but does
not implement its `source()` method. This means that an error library
like `anyhow` or `eyre` won't be able to display the root cause of an
error, which is especially problematic for the `Unhandled` variant.

## Description
I modified `ServiceErrorGenerator` in the `codegen-client` crate and
replaced the line that output `impl std::error::Error for Error {}` with
an impl block that implements the `source()` method by delegating to the
inner error structure.

## Testing
I've added a simple unit test to `ServiceErrorGeneratorTest`.

## Checklist
<!--- If a checkbox below is not applicable, then please DELETE it
rather than leaving it unchecked -->
- [x] I have updated `CHANGELOG.next.toml` if I made changes to the
smithy-rs codegen or runtime crates
- [x] I have updated `CHANGELOG.next.toml` if I made changes to the AWS
SDK, generated SDK code, or SDK runtime crates

----

_By submitting this pull request, I confirm that you can use, modify,
copy, and redistribute this contribution, under the terms of your
choice._
parent 35f2f27a
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -11,6 +11,18 @@
# meta = { "breaking" = false, "tada" = false, "bug" = false, "target" = "client | server | all"}
# author = "rcoh"

[[aws-sdk-rust]]
message = "Implement std::error::Error#source() properly for the service meta Error enum"
references = ["aws-sdk-rust#784"]
meta = { "breaking" = false, "tada" = false, "bug" = false }
author = "abusch"

[[smithy-rs]]
message = "Implement std::error::Error#source() properly for the service meta Error enum"
references = ["aws-sdk-rust#784"]
meta = { "breaking" = false, "tada" = false, "bug" = false, "target" = "client"}
author = "abusch"

[[aws-sdk-rust]]
message = "The outputs for event stream operations (for example, S3's SelectObjectContent) now implement the `Sync` auto-trait."
references = ["smithy-rs#2496"]
+10 −1
Original line number Diff line number Diff line
@@ -80,7 +80,16 @@ class ServiceErrorGenerator(
                        errors.map { it.id },
                    )
                }
            rust("impl #T for Error {}", RuntimeType.StdError)
            rustBlock("impl #T for Error", RuntimeType.StdError) {
                rustBlock("fn source(&self) -> std::option::Option<&(dyn #T + 'static)>", RuntimeType.StdError) {
                    rustBlock("match self") {
                        allErrors.forEach {
                            rust("Error::${symbolProvider.toSymbol(it).name}(inner) => inner.source(),")
                        }
                        rust("Error::Unhandled(inner) => inner.source()")
                    }
                }
            }
            writeCustomizations(customizations, ErrorSection.ServiceErrorAdditionalTraitImpls(allErrors))
        }
        crate.lib { rust("pub use error_meta::Error;") }
+49 −25
Original line number Diff line number Diff line
@@ -6,15 +6,16 @@
package software.amazon.smithy.rust.codegen.client.smithy.generators.error

import org.junit.jupiter.api.Test
import software.amazon.smithy.model.shapes.StructureShape
import software.amazon.smithy.rust.codegen.client.testutil.clientIntegrationTest
import software.amazon.smithy.rust.codegen.core.rustlang.rust
import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
import software.amazon.smithy.rust.codegen.core.testutil.integrationTest
import software.amazon.smithy.rust.codegen.core.testutil.unitTest
import software.amazon.smithy.rust.codegen.core.util.lookup

internal class ServiceErrorGeneratorTest {
    @Test
    fun `top level errors are send + sync`() {
        val model = """
    private val model = """
        namespace com.example

        use aws.protocols#restJson1
@@ -45,6 +46,8 @@ internal class ServiceErrorGeneratorTest {
        structure MeDeprecated { }
    """.asSmithyModel()

    @Test
    fun `top level errors are send + sync`() {
        clientIntegrationTest(model) { codegenContext, rustCrate ->
            rustCrate.integrationTest("validate_errors") {
                rust(
@@ -60,4 +63,25 @@ internal class ServiceErrorGeneratorTest {
            }
        }
    }

    @Test
    fun `generates combined error enums`() {
        clientIntegrationTest(model) { _, rustCrate ->
            rustCrate.moduleFor(model.lookup<StructureShape>("com.example#CanYouRepeatThat")) {
                unitTest(
                    name = "generates_combined_error_enums",
                    test = """
                        use std::error::Error as StdError;
                        use crate::Error;
                        use crate::operation::say_hello::SayHelloError;

                        // Unhandled variants properly delegate source.
                        let error = Error::from(SayHelloError::unhandled("some other error"));
                        let source = error.source().expect("source should not be None");
                        assert_eq!(format!("{}", source), "some other error");
                    """,
                )
            }
        }
    }
}