Skip to content

Commit

Permalink
feat(codegen): improve nullability of generated types (smithy-lang#1059)
Browse files Browse the repository at this point in the history
  • Loading branch information
aajtodd authored Oct 25, 2023
1 parent 813953a commit b52bf39
Show file tree
Hide file tree
Showing 9 changed files with 93 additions and 169 deletions.
4 changes: 2 additions & 2 deletions codegen/protocol-tests/model/error-correction-tests.smithy
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ apply SayHello @httpResponseTests([
mapValue: {},
nestedListValue: [],
document: null,
nested: { a: "" },
nested: null,
timestampValue: 0
},
code: 200,
Expand All @@ -147,7 +147,7 @@ apply SayHelloXml @httpResponseTests([
listValue: [],
mapValue: {},
nestedListValue: [],
nested: { a: "" },
nested: null,
timestampValue: 0
},
code: 200,
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package aws.sdk.kotlin.codegen.customization.ec2
import software.amazon.smithy.kotlin.codegen.KotlinSettings
import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration
import software.amazon.smithy.model.Model
import software.amazon.smithy.model.shapes.Shape
import software.amazon.smithy.model.shapes.ShapeId
import software.amazon.smithy.model.traits.ClientOptionalTrait
import software.amazon.smithy.model.transform.ModelTransformer

/**
* EC2 incorrectly models primitive shapes as unboxed when they actually
* need to be boxed for the API to work properly (e.g. sending default values).
* This integration pre-processes the model to make all members optional.
*
* See: https://github.com/awslabs/aws-sdk-kotlin/issues/261
*/
class EC2MakePrimitivesOptional : KotlinIntegration {
override val order: Byte = -127
override fun enabledForService(model: Model, settings: KotlinSettings): Boolean =
settings.service == ShapeId.from("com.amazonaws.ec2#AmazonEC2")

override fun preprocessModel(model: Model, settings: KotlinSettings): Model {
val updates = mutableListOf<Shape>()
for (struct in model.structureShapes) {
for (member in struct.allMembers.values) {
updates.add(member.toBuilder().addTrait(ClientOptionalTrait()).build())
}
}
return ModelTransformer.create().replaceShapes(model, updates)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class PollyPresigner : KotlinIntegration {
writer.write("unsignedRequest.method = #T.GET", RuntimeTypes.Http.HttpMethod)
writer.withBlock("unsignedRequest.url.#T {", "}", RuntimeTypes.Core.Net.parameters) {
val bindings = resolver.requestBindings(operation)
HttpStringValuesMapSerializer(ctx.model, ctx.symbolProvider, bindings, resolver, defaultTimestampFormat).render(writer)
HttpStringValuesMapSerializer(ctx.model, ctx.symbolProvider, ctx.settings, bindings, resolver, defaultTimestampFormat).render(writer)
}

// Remove the headers that were created by the default HTTP operation serializer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ class S3Generator : RestXml() {
.dedent()
.withBlock("} catch (ex: Exception) {", "}") {
withBlock("""throw #T("Failed to parse response as '${ctx.protocol.name}' error", ex).also {""", "}", exceptionBaseSymbol) {
write("#T(it, wrappedResponse, null)", setS3ErrorMetadata)
write("#T(it, wrappedCall.response, null)", setS3ErrorMetadata)
}
}
.write("")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ abstract class AwsHttpBindingProtocolGenerator : HttpBindingProtocolGenerator()
.dedent()
.withBlock("} catch (ex: Exception) {", "}") {
withBlock("""throw #T("Failed to parse response as '${ctx.protocol.name}' error", ex).also {""", "}", exceptionBaseSymbol) {
write("#T(it, wrappedResponse, null)", RuntimeTypes.AwsProtocolCore.setAseErrorMetadata)
write("#T(it, wrappedCall.response, null)", RuntimeTypes.AwsProtocolCore.setAseErrorMetadata)
}
}
.write("")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ aws.sdk.kotlin.codegen.customization.apigateway.ApiGatewayAddAcceptHeader
aws.sdk.kotlin.codegen.customization.glacier.GlacierAddVersionHeader
aws.sdk.kotlin.codegen.customization.glacier.GlacierAccountIdDefault
aws.sdk.kotlin.codegen.customization.polly.PollyPresigner
aws.sdk.kotlin.codegen.customization.BoxServices
aws.sdk.kotlin.codegen.customization.glacier.GlacierBodyChecksum
aws.sdk.kotlin.codegen.customization.machinelearning.MachineLearningEndpointCustomization
aws.sdk.kotlin.codegen.customization.BackfillOptionalAuth
Expand All @@ -30,4 +29,5 @@ aws.sdk.kotlin.codegen.customization.s3control.ClientConfigIntegration
aws.sdk.kotlin.codegen.protocols.endpoints.BindAwsEndpointBuiltins
aws.sdk.kotlin.codegen.customization.s3.HostPrefixRequestRouteFilter
aws.sdk.kotlin.codegen.customization.s3.UnwrappedXmlOutputIntegration
aws.sdk.kotlin.codegen.customization.ClockSkew
aws.sdk.kotlin.codegen.customization.ClockSkew
aws.sdk.kotlin.codegen.customization.ec2.EC2MakePrimitivesOptional

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package aws.sdk.kotlin.codegen.customization.ec2

import org.junit.jupiter.api.Test
import software.amazon.smithy.kotlin.codegen.model.expectShape
import software.amazon.smithy.kotlin.codegen.model.isNullable
import software.amazon.smithy.kotlin.codegen.test.newTestContext
import software.amazon.smithy.kotlin.codegen.test.prependNamespaceAndService
import software.amazon.smithy.kotlin.codegen.test.toSmithyModel
import software.amazon.smithy.model.shapes.StructureShape
import kotlin.test.assertTrue

class EC2MakePrimitivesOptionalTest {
@Test
fun testNullability() {
val model = """
operation Foo {
input: FooInput
}
structure FooInput {
@required
@default(0)
int: PrimitiveInteger,
@default(false)
bool: PrimitiveBoolean,
other: Other,
@default(1)
defaultInt: Integer
@required
requiredString: String
}
structure Other {}
""".prependNamespaceAndService(version = "2", operations = listOf("Foo")).toSmithyModel()

val ctx = model.newTestContext()
val transformed = EC2MakePrimitivesOptional().preprocessModel(model, ctx.generationCtx.settings)

// get the synthetic input which is the one that will be transformed
val struct = transformed.expectShape<StructureShape>("smithy.kotlin.synthetic.test#FooRequest")
struct.members().forEach {
val memberSymbol = ctx.generationCtx.symbolProvider.toSymbol(it)
assertTrue(memberSymbol.isNullable)
}
}
}

0 comments on commit b52bf39

Please sign in to comment.