diff --git a/CHANGELOG.next.toml b/CHANGELOG.next.toml index a99b8c59a8..cdb838524a 100644 --- a/CHANGELOG.next.toml +++ b/CHANGELOG.next.toml @@ -28,3 +28,9 @@ message = "The `AssumeRoleBuilder::policy_arns` now accepts strings instead of a references = ["smithy-rs#3205"] meta = { "breaking" = true, "tada" = false, "bug" = false } author = "rcoh" + +[[aws-sdk-rust]] +message = "Make certain types for EMR Serverless optional. Previously, they defaulted to 0, but this created invalid requests." +references = ["smithy-rs#3217"] +meta = { "breaking" = true, "tada" = false, "bug" = true } +author = "milesziemer" diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCodegenDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCodegenDecorator.kt index 59ef326a2f..5e694c1cea 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCodegenDecorator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCodegenDecorator.kt @@ -10,6 +10,7 @@ import software.amazon.smithy.rust.codegen.client.smithy.customizations.DocsRsMe import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator import software.amazon.smithy.rust.codegen.client.smithy.customize.CombinedClientCodegenDecorator import software.amazon.smithy.rustsdk.customize.DisabledAuthDecorator +import software.amazon.smithy.rustsdk.customize.RemoveDefaultsDecorator import software.amazon.smithy.rustsdk.customize.apigateway.ApiGatewayDecorator import software.amazon.smithy.rustsdk.customize.applyDecorators import software.amazon.smithy.rustsdk.customize.ec2.Ec2Decorator @@ -53,6 +54,7 @@ val DECORATORS: List = listOf( RecursionDetectionDecorator(), InvocationIdDecorator(), RetryInformationHeaderDecorator(), + RemoveDefaultsDecorator(), ), // Service specific decorators diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/RemoveDefaults.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/RemoveDefaults.kt new file mode 100644 index 0000000000..bebfd639b2 --- /dev/null +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/RemoveDefaults.kt @@ -0,0 +1,58 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.rustsdk.customize + +import software.amazon.smithy.model.Model +import software.amazon.smithy.model.shapes.AbstractShapeBuilder +import software.amazon.smithy.model.shapes.MemberShape +import software.amazon.smithy.model.shapes.Shape +import software.amazon.smithy.model.shapes.ShapeId +import software.amazon.smithy.model.traits.DefaultTrait +import software.amazon.smithy.model.transform.ModelTransformer +import software.amazon.smithy.rust.codegen.core.util.hasTrait +import software.amazon.smithy.rust.codegen.core.util.letIf +import software.amazon.smithy.utils.ToSmithyBuilder +import java.util.logging.Logger + +/** + * Removes default values from specified root shapes, and any members that target those + * root shapes. + */ +object RemoveDefaults { + private val logger: Logger = Logger.getLogger(javaClass.name) + + fun processModel(model: Model, removeDefaultsFrom: Set): Model { + val removedRootDefaults: MutableSet = HashSet() + val removedRootDefaultsModel = ModelTransformer.create().mapShapes(model) { shape -> + shape.letIf(shouldRemoveRootDefault(shape, removeDefaultsFrom)) { + logger.info("Removing default trait from root $shape") + removedRootDefaults.add(shape.id) + removeDefault(shape) + } + } + + return ModelTransformer.create().mapShapes(removedRootDefaultsModel) { shape -> + shape.letIf(shouldRemoveMemberDefault(shape, removedRootDefaults)) { + logger.info("Removing default trait from member $shape") + removeDefault(shape) + } + } + } + + private fun shouldRemoveRootDefault(shape: Shape, removeDefaultsFrom: Set): Boolean { + return shape !is MemberShape && removeDefaultsFrom.contains(shape.id) && shape.hasTrait() + } + + private fun shouldRemoveMemberDefault(shape: Shape, removeDefaultsFrom: Set): Boolean { + return shape is MemberShape && removeDefaultsFrom.contains(shape.target) && shape.hasTrait() + } + + private fun removeDefault(shape: Shape): Shape { + return ((shape as ToSmithyBuilder<*>).toBuilder() as AbstractShapeBuilder<*, *>) + .removeTrait(DefaultTrait.ID) + .build() + } +} diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/RemoveDefaultsDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/RemoveDefaultsDecorator.kt new file mode 100644 index 0000000000..91623f3d96 --- /dev/null +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/RemoveDefaultsDecorator.kt @@ -0,0 +1,43 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.rustsdk.customize + +import software.amazon.smithy.model.Model +import software.amazon.smithy.model.shapes.ServiceShape +import software.amazon.smithy.rust.codegen.client.smithy.ClientRustSettings +import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator +import software.amazon.smithy.rust.codegen.core.util.shapeId +import java.util.logging.Logger + +/** + * Removes default values from certain shapes, and any member that targets those shapes, + * for some services where the default value causes serialization issues, validation + * issues, or other unexpected behavior. + */ +class RemoveDefaultsDecorator : ClientCodegenDecorator { + override val name: String = "RemoveDefaults" + override val order: Byte = 0 + private val logger: Logger = Logger.getLogger(javaClass.name) + + // Service shape id -> Shape id of each root shape to remove the default from. + private val removeDefaults = mapOf( + "com.amazonaws.emrserverless#AwsToledoWebService".shapeId() to setOf( + // Service expects this to have a min value > 0 + "com.amazonaws.emrserverless#WorkerCounts".shapeId(), + ), + ) + + private fun applies(service: ServiceShape) = + removeDefaults.containsKey(service.id) + + override fun transformModel(service: ServiceShape, model: Model, settings: ClientRustSettings): Model { + if (!applies(service)) { + return model + } + logger.info("Removing invalid defaults from ${service.id}") + return RemoveDefaults.processModel(model, removeDefaults[service.id]!!) + } +} diff --git a/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/customize/RemoveDefaultsTest.kt b/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/customize/RemoveDefaultsTest.kt new file mode 100644 index 0000000000..266c3d6be3 --- /dev/null +++ b/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/customize/RemoveDefaultsTest.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.rustsdk.customize + +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Test +import software.amazon.smithy.model.shapes.IntegerShape +import software.amazon.smithy.model.shapes.MemberShape +import software.amazon.smithy.model.traits.DefaultTrait +import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel +import software.amazon.smithy.rust.codegen.core.util.hasTrait +import software.amazon.smithy.rust.codegen.core.util.lookup +import software.amazon.smithy.rust.codegen.core.util.shapeId + +internal class RemoveDefaultsTest { + @Test + fun `defaults should be removed`() { + val removeDefaults = setOf( + "test#Bar".shapeId(), + ) + val baseModel = """ + namespace test + + structure Foo { + bar: Bar = 0 + } + + @default(0) + integer Bar + + """.asSmithyModel(smithyVersion = "2.0") + val model = RemoveDefaults.processModel(baseModel, removeDefaults) + val member = model.lookup("test#Foo\$bar") + member.hasTrait() shouldBe false + val root = model.lookup("test#Bar") + root.hasTrait() shouldBe false + } +}