Skip to content

Commit

Permalink
Remove invalid defaults for some services
Browse files Browse the repository at this point in the history
Adds a customization that removes default values, and uses it to
remove the default values for some shapes in certain services
that have a default value of 0, but the service expects a value
> 0.
  • Loading branch information
milesziemer committed Nov 16, 2023
1 parent 4411131 commit 4ae08f1
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 0 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.next.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -53,6 +54,7 @@ val DECORATORS: List<ClientCodegenDecorator> = listOf(
RecursionDetectionDecorator(),
InvocationIdDecorator(),
RetryInformationHeaderDecorator(),
RemoveDefaultsDecorator(),
),

// Service specific decorators
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ShapeId>): Model {
val removedRootDefaults: MutableSet<ShapeId> = 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<ShapeId>): Boolean {
return shape !is MemberShape && removeDefaultsFrom.contains(shape.id) && shape.hasTrait<DefaultTrait>()
}

private fun shouldRemoveMemberDefault(shape: Shape, removeDefaultsFrom: Set<ShapeId>): Boolean {
return shape is MemberShape && removeDefaultsFrom.contains(shape.target) && shape.hasTrait<DefaultTrait>()
}

private fun removeDefault(shape: Shape): Shape {
return ((shape as ToSmithyBuilder<*>).toBuilder() as AbstractShapeBuilder<*, *>)
.removeTrait(DefaultTrait.ID)
.build()
}
}
Original file line number Diff line number Diff line change
@@ -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]!!)
}
}
Original file line number Diff line number Diff line change
@@ -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<MemberShape>("test#Foo\$bar")
member.hasTrait<DefaultTrait>() shouldBe false
val root = model.lookup<IntegerShape>("test#Bar")
root.hasTrait<DefaultTrait>() shouldBe false
}
}

0 comments on commit 4ae08f1

Please sign in to comment.