Loading .changelog/unknown-debug.md 0 → 100644 +18 −0 Original line number Diff line number Diff line --- applies_to: ["client"] authors: ["landonxjames"] references: ["smithy-rs#4137"] breaking: false new_feature: false bug_fix: true --- Fix bug with enum codegen When the first enum generated has the `@sensitive` trait the opaque type underlying the `UnknownVariant` inherits that sensitivity. This means that it does not derive `Debug`. Since the module is only generated once this causes a problem for non-sensitive enums that rely on the type deriving `Debug` so that they can also derive `Debug`. We manually add `Debug` to the module so it will always be there since the `UnknownVariant` is not modeled and cannot be `@sensitive`. codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ClientEnumGenerator.kt +6 −1 Original line number Diff line number Diff line Loading @@ -194,7 +194,12 @@ data class InfallibleEnumType( This is not intended to be used directly. """.trimIndent(), ) context.enumMeta.render(this) // The UnknownVariant's underlying opaque type should always derive `Debug`. If this isn't explicitly // added and the first enum to render this module has the `@sensitive` trait then the UnknownVariant // inherits that sensitivity and does not derive `Debug`. This leads to issues deriving `Debug` for // all enum variants that are not marked `@sensitive`. context.enumMeta.withDerives(RuntimeType.Debug).render(this) rustTemplate("struct $UNKNOWN_VARIANT_VALUE(pub(crate) #{String});", *preludeScope) rustBlock("impl $UNKNOWN_VARIANT_VALUE") { // The generated as_str is not pub as we need to prevent users from calling it on this opaque struct. Loading codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ClientEnumGeneratorTest.kt +49 −0 Original line number Diff line number Diff line Loading @@ -106,6 +106,55 @@ class ClientEnumGeneratorTest { project.compileAndTest() } // The idempotency of RustModules paired with Smithy's alphabetic order shape iteration meant that // if an @sensitive enum was the first enum generated that the opaque type underlying the Unknown // variant would not derive Debug, breaking all non-@sensitive enums @Test fun `sensitive enum in earlier alphabetic order does not break non-sensitive enums`() { val model = """ namespace test @sensitive @enum([ { name: "Foo", value: "Foo" }, { name: "Bar", value: "Bar" }, ]) string FooA @enum([ { name: "Baz", value: "Baz" }, { name: "Ahh", value: "Ahh" }, ]) string FooB """.asSmithyModel() val shapeA = model.lookup<StringShape>("test#FooA") val shapeB = model.lookup<StringShape>("test#FooB") val context = testClientCodegenContext(model) val project = TestWorkspace.testProject(context.symbolProvider) project.moduleFor(shapeA) { ClientEnumGenerator(context, shapeA).render(this) } project.moduleFor(shapeB) { ClientEnumGenerator(context, shapeB).render(this) unitTest( "impl_debug_for_non_sensitive_enum_should_implement_the_derived_debug_trait", """ assert_eq!(format!("{:?}", FooB::Baz), "Baz"); assert_eq!(format!("{:?}", FooB::Ahh), "Ahh"); assert_eq!(format!("{}", FooB::Baz), "Baz"); assert_eq!(FooB::Ahh.to_string(), "Ahh"); assert_eq!( format!("{:?}", FooB::from("Bar")), "Unknown(UnknownVariantValue(\"Bar\"))" ); """, ) } project.compileAndTest() } @Test fun `it escapes the Unknown variant if the enum has an unknown value in the model`() { val model = Loading Loading
.changelog/unknown-debug.md 0 → 100644 +18 −0 Original line number Diff line number Diff line --- applies_to: ["client"] authors: ["landonxjames"] references: ["smithy-rs#4137"] breaking: false new_feature: false bug_fix: true --- Fix bug with enum codegen When the first enum generated has the `@sensitive` trait the opaque type underlying the `UnknownVariant` inherits that sensitivity. This means that it does not derive `Debug`. Since the module is only generated once this causes a problem for non-sensitive enums that rely on the type deriving `Debug` so that they can also derive `Debug`. We manually add `Debug` to the module so it will always be there since the `UnknownVariant` is not modeled and cannot be `@sensitive`.
codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ClientEnumGenerator.kt +6 −1 Original line number Diff line number Diff line Loading @@ -194,7 +194,12 @@ data class InfallibleEnumType( This is not intended to be used directly. """.trimIndent(), ) context.enumMeta.render(this) // The UnknownVariant's underlying opaque type should always derive `Debug`. If this isn't explicitly // added and the first enum to render this module has the `@sensitive` trait then the UnknownVariant // inherits that sensitivity and does not derive `Debug`. This leads to issues deriving `Debug` for // all enum variants that are not marked `@sensitive`. context.enumMeta.withDerives(RuntimeType.Debug).render(this) rustTemplate("struct $UNKNOWN_VARIANT_VALUE(pub(crate) #{String});", *preludeScope) rustBlock("impl $UNKNOWN_VARIANT_VALUE") { // The generated as_str is not pub as we need to prevent users from calling it on this opaque struct. Loading
codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ClientEnumGeneratorTest.kt +49 −0 Original line number Diff line number Diff line Loading @@ -106,6 +106,55 @@ class ClientEnumGeneratorTest { project.compileAndTest() } // The idempotency of RustModules paired with Smithy's alphabetic order shape iteration meant that // if an @sensitive enum was the first enum generated that the opaque type underlying the Unknown // variant would not derive Debug, breaking all non-@sensitive enums @Test fun `sensitive enum in earlier alphabetic order does not break non-sensitive enums`() { val model = """ namespace test @sensitive @enum([ { name: "Foo", value: "Foo" }, { name: "Bar", value: "Bar" }, ]) string FooA @enum([ { name: "Baz", value: "Baz" }, { name: "Ahh", value: "Ahh" }, ]) string FooB """.asSmithyModel() val shapeA = model.lookup<StringShape>("test#FooA") val shapeB = model.lookup<StringShape>("test#FooB") val context = testClientCodegenContext(model) val project = TestWorkspace.testProject(context.symbolProvider) project.moduleFor(shapeA) { ClientEnumGenerator(context, shapeA).render(this) } project.moduleFor(shapeB) { ClientEnumGenerator(context, shapeB).render(this) unitTest( "impl_debug_for_non_sensitive_enum_should_implement_the_derived_debug_trait", """ assert_eq!(format!("{:?}", FooB::Baz), "Baz"); assert_eq!(format!("{:?}", FooB::Ahh), "Ahh"); assert_eq!(format!("{}", FooB::Baz), "Baz"); assert_eq!(FooB::Ahh.to_string(), "Ahh"); assert_eq!( format!("{:?}", FooB::from("Bar")), "Unknown(UnknownVariantValue(\"Bar\"))" ); """, ) } project.compileAndTest() } @Test fun `it escapes the Unknown variant if the enum has an unknown value in the model`() { val model = Loading