diff --git a/.changes/ac672b00-f8bb-4235-8db4-aad0ae2f157d.json b/.changes/ac672b00-f8bb-4235-8db4-aad0ae2f157d.json new file mode 100644 index 00000000000..5fcb78bcba6 --- /dev/null +++ b/.changes/ac672b00-f8bb-4235-8db4-aad0ae2f157d.json @@ -0,0 +1,8 @@ +{ + "id": "ac672b00-f8bb-4235-8db4-aad0ae2f157d", + "type": "bugfix", + "description": "ignore `__type` when deserializing union for AWS JSON 1.0, AWS JSON 1.1, and AWS restJson 1", + "issues": [ + "awslabs/aws-sdk-kotlin#1044" + ] +} \ No newline at end of file diff --git a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/AwsJson1_0.kt b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/AwsJson1_0.kt index 433e8a1b500..028d1cfa773 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/AwsJson1_0.kt +++ b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/AwsJson1_0.kt @@ -7,13 +7,16 @@ package aws.sdk.kotlin.codegen.protocols import aws.sdk.kotlin.codegen.protocols.core.AwsHttpBindingProtocolGenerator import aws.sdk.kotlin.codegen.protocols.json.AwsJsonHttpBindingResolver import aws.sdk.kotlin.codegen.protocols.json.AwsJsonProtocolMiddleware +import aws.sdk.kotlin.codegen.protocols.json.AwsJsonProtocolParserGenerator import aws.sdk.kotlin.codegen.protocols.json.JsonHttpBindingProtocolGenerator import software.amazon.smithy.aws.traits.protocols.AwsJson1_0Trait import software.amazon.smithy.kotlin.codegen.rendering.protocol.HttpBindingResolver import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolGenerator import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolMiddleware +import software.amazon.smithy.kotlin.codegen.rendering.serde.StructuredDataParserGenerator import software.amazon.smithy.model.Model -import software.amazon.smithy.model.shapes.* +import software.amazon.smithy.model.shapes.ServiceShape +import software.amazon.smithy.model.shapes.ShapeId /** * Handles generating the aws.protocols#awsJson1_0 protocol for services. @@ -36,4 +39,7 @@ class AwsJson1_0 : JsonHttpBindingProtocolGenerator() { override fun getProtocolHttpBindingResolver(model: Model, serviceShape: ServiceShape): HttpBindingResolver = AwsJsonHttpBindingResolver(model, serviceShape, "application/x-amz-json-1.0") + + override fun structuredDataParser(ctx: ProtocolGenerator.GenerationContext): StructuredDataParserGenerator = + AwsJsonProtocolParserGenerator(this, supportsJsonNameTrait) } diff --git a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/AwsJson1_1.kt b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/AwsJson1_1.kt index 79ac05d8787..1b92f14b3b0 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/AwsJson1_1.kt +++ b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/AwsJson1_1.kt @@ -8,11 +8,13 @@ package aws.sdk.kotlin.codegen.protocols import aws.sdk.kotlin.codegen.protocols.core.AwsHttpBindingProtocolGenerator import aws.sdk.kotlin.codegen.protocols.json.AwsJsonHttpBindingResolver import aws.sdk.kotlin.codegen.protocols.json.AwsJsonProtocolMiddleware +import aws.sdk.kotlin.codegen.protocols.json.AwsJsonProtocolParserGenerator import aws.sdk.kotlin.codegen.protocols.json.JsonHttpBindingProtocolGenerator import software.amazon.smithy.aws.traits.protocols.AwsJson1_1Trait import software.amazon.smithy.kotlin.codegen.rendering.protocol.HttpBindingResolver import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolGenerator import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolMiddleware +import software.amazon.smithy.kotlin.codegen.rendering.serde.StructuredDataParserGenerator import software.amazon.smithy.model.Model import software.amazon.smithy.model.shapes.ServiceShape import software.amazon.smithy.model.shapes.ShapeId @@ -38,4 +40,7 @@ class AwsJson1_1 : JsonHttpBindingProtocolGenerator() { override fun getProtocolHttpBindingResolver(model: Model, serviceShape: ServiceShape): HttpBindingResolver = AwsJsonHttpBindingResolver(model, serviceShape, "application/x-amz-json-1.1") + + override fun structuredDataParser(ctx: ProtocolGenerator.GenerationContext): StructuredDataParserGenerator = + AwsJsonProtocolParserGenerator(this, supportsJsonNameTrait) } diff --git a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/RestJson1.kt b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/RestJson1.kt index 0309ae9a293..07c94a942fe 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/RestJson1.kt +++ b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/RestJson1.kt @@ -5,6 +5,7 @@ package aws.sdk.kotlin.codegen.protocols import aws.sdk.kotlin.codegen.protocols.core.AwsHttpBindingProtocolGenerator +import aws.sdk.kotlin.codegen.protocols.json.AwsJsonProtocolParserGenerator import aws.sdk.kotlin.codegen.protocols.json.JsonHttpBindingProtocolGenerator import software.amazon.smithy.aws.traits.protocols.RestJson1Trait import software.amazon.smithy.kotlin.codegen.core.KotlinWriter @@ -12,6 +13,7 @@ import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes import software.amazon.smithy.kotlin.codegen.core.defaultName import software.amazon.smithy.kotlin.codegen.core.withBlock import software.amazon.smithy.kotlin.codegen.rendering.protocol.* +import software.amazon.smithy.kotlin.codegen.rendering.serde.StructuredDataParserGenerator import software.amazon.smithy.model.Model import software.amazon.smithy.model.knowledge.HttpBinding import software.amazon.smithy.model.shapes.OperationShape @@ -62,4 +64,7 @@ class RestJson1 : JsonHttpBindingProtocolGenerator() { } } } + + override fun structuredDataParser(ctx: ProtocolGenerator.GenerationContext): StructuredDataParserGenerator = + AwsJsonProtocolParserGenerator(this, supportsJsonNameTrait) } diff --git a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/json/AwsJsonProtocolParserGenerator.kt b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/json/AwsJsonProtocolParserGenerator.kt new file mode 100644 index 00000000000..58807e7eda4 --- /dev/null +++ b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/json/AwsJsonProtocolParserGenerator.kt @@ -0,0 +1,32 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package aws.sdk.kotlin.codegen.protocols.json + +import software.amazon.smithy.kotlin.codegen.core.KotlinWriter +import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolGenerator +import software.amazon.smithy.kotlin.codegen.rendering.protocol.toRenderingContext +import software.amazon.smithy.kotlin.codegen.rendering.serde.JsonParserGenerator +import software.amazon.smithy.kotlin.codegen.rendering.serde.JsonSerdeDescriptorGenerator +import software.amazon.smithy.model.shapes.MemberShape +import software.amazon.smithy.model.shapes.Shape + +/** + * Overrides the [JsonParserGenerator] when using `AWS Json 1.0`, `AWS Json 1.1`, and `AWS RestJson 1` protocols. + * + * See https://github.com/smithy-lang/smithy/pull/1945 + */ +class AwsJsonProtocolParserGenerator( + private val protocolGenerator: ProtocolGenerator, + private val supportsJsonNameTrait: Boolean = true, +) : JsonParserGenerator(protocolGenerator, supportsJsonNameTrait) { + + override fun descriptorGenerator( + ctx: ProtocolGenerator.GenerationContext, + shape: Shape, + members: List, + writer: KotlinWriter, + ): JsonSerdeDescriptorGenerator = AwsJsonProtocolSerdeDescriptorGenerator(ctx.toRenderingContext(protocolGenerator, shape, writer), members, supportsJsonNameTrait) +} diff --git a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/json/AwsJsonProtocolSerdeDescriptorGenerator.kt b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/json/AwsJsonProtocolSerdeDescriptorGenerator.kt new file mode 100644 index 00000000000..6e635459793 --- /dev/null +++ b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/json/AwsJsonProtocolSerdeDescriptorGenerator.kt @@ -0,0 +1,46 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package aws.sdk.kotlin.codegen.protocols.json + +import software.amazon.smithy.kotlin.codegen.core.RenderingContext +import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes +import software.amazon.smithy.kotlin.codegen.rendering.serde.JsonSerdeDescriptorGenerator +import software.amazon.smithy.kotlin.codegen.rendering.serde.SdkFieldDescriptorTrait +import software.amazon.smithy.kotlin.codegen.rendering.serde.add +import software.amazon.smithy.kotlin.codegen.utils.dq +import software.amazon.smithy.model.shapes.MemberShape +import software.amazon.smithy.model.shapes.Shape + +/** + * Overrides the [JsonSerdeDescriptorGenerator] when using `AWS Json 1.0`, `AWS Json 1.1`, and `AWS RestJson 1` protocols. + * + * See: https://github.com/smithy-lang/smithy/pull/1945 + */ +class AwsJsonProtocolSerdeDescriptorGenerator( + ctx: RenderingContext, + memberShapes: List? = null, + supportsJsonNameTrait: Boolean = true, +) : JsonSerdeDescriptorGenerator(ctx, memberShapes, supportsJsonNameTrait) { + + /** + * Adds a trait to ignore `__type` in union shapes for `AWS Json 1.0`, `AWS Json 1.1`, `RestJson 1.0` protocols + * Sometimes the unnecessary field `__type` is added and needs to be ignored + * + * NOTE: Will be ignored unless it's in the model + * + * Source: https://github.com/smithy-lang/smithy/pull/1945 + */ + override fun getObjectDescriptorTraits(): List { + val traitList = super.getObjectDescriptorTraits().toMutableList() + val typeMember = memberShapes.find { it.memberName == "__type" } + + if (ctx.shape?.isUnionShape == true && typeMember == null) { + traitList.add(RuntimeTypes.Serde.SerdeJson.IgnoreKey, "__type".dq()) + } + + return traitList + } +} diff --git a/codegen/smithy-aws-kotlin-codegen/src/test/kotlin/aws/sdk/kotlin/codegen/protocols/json/AwsJsonProtocolSerdeDescriptorGeneratorTest.kt b/codegen/smithy-aws-kotlin-codegen/src/test/kotlin/aws/sdk/kotlin/codegen/protocols/json/AwsJsonProtocolSerdeDescriptorGeneratorTest.kt new file mode 100644 index 00000000000..228d83d5b84 --- /dev/null +++ b/codegen/smithy-aws-kotlin-codegen/src/test/kotlin/aws/sdk/kotlin/codegen/protocols/json/AwsJsonProtocolSerdeDescriptorGeneratorTest.kt @@ -0,0 +1,91 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package aws.sdk.kotlin.codegen.protocols.json + +import software.amazon.smithy.kotlin.codegen.test.* +import software.amazon.smithy.model.shapes.ShapeId +import kotlin.test.Test + +class AwsJsonProtocolSerdeDescriptorGeneratorTest { + @Test + fun itAddsIgnoreKeysTrait() { + val model = """ + @http(method: "POST", uri: "/foo") + operation Foo { + input: FooRequest + } + + structure FooRequest { + strVal: String, + intVal: Integer + } + + union Bar { + x: String, + y: String, + } + """.prependNamespaceAndService(operations = listOf("Foo")).toSmithyModel() + + val testCtx = model.newTestContext() + val writer = testCtx.newWriter() + val shape = model.expectShape(ShapeId.from("com.test#Bar")) + val renderingCtx = testCtx.toRenderingContext(writer, shape) + + AwsJsonProtocolSerdeDescriptorGenerator(renderingCtx).render() + + val expectedDescriptors = """ + val X_DESCRIPTOR = SdkFieldDescriptor(SerialKind.String, JsonSerialName("x")) + val Y_DESCRIPTOR = SdkFieldDescriptor(SerialKind.String, JsonSerialName("y")) + val OBJ_DESCRIPTOR = SdkObjectDescriptor.build { + trait(IgnoreKey("__type")) + field(X_DESCRIPTOR) + field(Y_DESCRIPTOR) + } + """.formatForTest("") + + val contents = writer.toString() + contents.shouldContainOnlyOnceWithDiff(expectedDescriptors) + } + + @Test + fun itDoesNotAddIgnoreKeysTrait() { + val model = """ + @http(method: "POST", uri: "/foo") + operation Foo { + input: FooRequest + } + + structure FooRequest { + strVal: String, + intVal: Integer + } + + union Bar { + __type: String, + y: String, + } + """.prependNamespaceAndService(operations = listOf("Foo")).toSmithyModel() + + val testCtx = model.newTestContext() + val writer = testCtx.newWriter() + val shape = model.expectShape(ShapeId.from("com.test#Bar")) + val renderingCtx = testCtx.toRenderingContext(writer, shape) + + AwsJsonProtocolSerdeDescriptorGenerator(renderingCtx).render() + + val expectedDescriptors = """ + val TYPE_DESCRIPTOR = SdkFieldDescriptor(SerialKind.String, JsonSerialName("__type")) + val Y_DESCRIPTOR = SdkFieldDescriptor(SerialKind.String, JsonSerialName("y")) + val OBJ_DESCRIPTOR = SdkObjectDescriptor.build { + field(TYPE_DESCRIPTOR) + field(Y_DESCRIPTOR) + } + """.formatForTest("") + + val contents = writer.toString() + contents.shouldContainOnlyOnceWithDiff(expectedDescriptors) + } +}