Skip to content

Commit fbb9e45

Browse files
authored
refactor(codegen): restructure protocol codegen to use structured parser generators (#512)
1 parent 5c1bc59 commit fbb9e45

File tree

8 files changed

+378
-572
lines changed

8 files changed

+378
-572
lines changed

codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/customization/s3/S3Generator.kt

Lines changed: 16 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -61,26 +61,16 @@ class S3Generator : RestXml() {
6161
namespace = "${ctx.settings.pkg.name}.internal"
6262
}
6363

64-
listOf(
65-
exceptionBaseSymbol,
66-
RuntimeTypes.Http.readAll,
67-
RuntimeTypes.Http.StatusCode,
68-
AwsRuntimeTypes.Http.withPayload,
69-
s3ErrorDetails,
70-
setS3ErrorMetadata,
71-
parseS3ErrorResponse,
72-
).forEach(writer::addImport)
73-
74-
writer.write("""val payload = response.body.readAll()""")
75-
.write("val wrappedResponse = response.withPayload(payload)")
64+
writer.write("val payload = response.body.#T()", RuntimeTypes.Http.readAll)
65+
.write("val wrappedResponse = response.#T(payload)", AwsRuntimeTypes.Http.withPayload)
7666
.write("")
7767
.write("val errorDetails = try {")
7868
.indent()
7969
.call {
8070
// customize error matching to handle HeadObject/HeadBucket error responses which have no payload
81-
writer.write("if (payload == null && response.status == HttpStatusCode.NotFound) {")
71+
writer.write("if (payload == null && response.status == #T.NotFound) {", RuntimeTypes.Http.StatusCode)
8272
.indent()
83-
.write("""S3ErrorDetails(code = "NotFound")""")
73+
.write("#T(code = #S)", s3ErrorDetails, "NotFound")
8474
.dedent()
8575
.write("} else {")
8676
.indent()
@@ -97,25 +87,20 @@ class S3Generator : RestXml() {
9787
}
9888
.write("")
9989

100-
if (op.errors.isEmpty()) {
101-
writer.write("throw #T(errorDetails.message)", exceptionBaseSymbol)
102-
} else {
103-
writer.openBlock("val modeledExceptionDeserializer = when(errorDetails.code) {", "}") {
104-
op.errors.forEach { err ->
105-
val errSymbol = ctx.symbolProvider.toSymbol(ctx.model.expectShape(err))
106-
val errDeserializerSymbol = buildSymbol {
107-
name = "${errSymbol.name}Deserializer"
108-
namespace = "${ctx.settings.pkg.name}.transform"
109-
}
110-
writer.write("#S -> #T()", getErrorCode(ctx, err), errDeserializerSymbol)
90+
writer.withBlock("val ex = when(errorDetails.code) {", "}") {
91+
op.errors.forEach { err ->
92+
val errSymbol = ctx.symbolProvider.toSymbol(ctx.model.expectShape(err))
93+
val errDeserializerSymbol = buildSymbol {
94+
name = "${errSymbol.name}Deserializer"
95+
namespace = "${ctx.settings.pkg.name}.transform"
11196
}
112-
writer.write("else -> throw #T(errorDetails.message)", exceptionBaseSymbol)
97+
writer.write("#S -> #T().deserialize(context, wrappedResponse)", getErrorCode(ctx, err), errDeserializerSymbol)
11398
}
114-
115-
writer.write("")
116-
.write("val modeledException = modeledExceptionDeserializer.deserialize(context, wrappedResponse)")
117-
.write("#T(modeledException, wrappedResponse, errorDetails)", setS3ErrorMetadata)
118-
.write("throw modeledException")
99+
write("else -> #T(errorDetails.message)", exceptionBaseSymbol)
119100
}
101+
102+
writer.write("")
103+
writer.write("#T(ex, wrappedResponse, errorDetails)", setS3ErrorMetadata)
104+
writer.write("throw ex")
120105
}
121106
}

codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/AwsQuery.kt

Lines changed: 66 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,13 @@
66
package aws.sdk.kotlin.codegen.protocols
77

88
import aws.sdk.kotlin.codegen.AwsRuntimeTypes
9+
import aws.sdk.kotlin.codegen.protocols.core.AbstractQueryFormUrlSerializerGenerator
910
import aws.sdk.kotlin.codegen.protocols.core.AwsHttpBindingProtocolGenerator
1011
import aws.sdk.kotlin.codegen.protocols.core.QueryHttpBindingProtocolGenerator
1112
import aws.sdk.kotlin.codegen.protocols.formurl.QuerySerdeFormUrlDescriptorGenerator
1213
import software.amazon.smithy.aws.traits.protocols.AwsQueryErrorTrait
1314
import software.amazon.smithy.aws.traits.protocols.AwsQueryTrait
14-
import software.amazon.smithy.kotlin.codegen.core.KotlinWriter
15-
import software.amazon.smithy.kotlin.codegen.core.RenderingContext
16-
import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes
17-
import software.amazon.smithy.kotlin.codegen.core.addImport
15+
import software.amazon.smithy.kotlin.codegen.core.*
1816
import software.amazon.smithy.kotlin.codegen.model.*
1917
import software.amazon.smithy.kotlin.codegen.model.traits.OperationOutput
2018
import software.amazon.smithy.kotlin.codegen.rendering.protocol.*
@@ -32,56 +30,11 @@ import software.amazon.smithy.model.traits.*
3230
class AwsQuery : QueryHttpBindingProtocolGenerator() {
3331
override val protocol: ShapeId = AwsQueryTrait.ID
3432

35-
override fun getDeserializerDescriptorGenerator(
36-
ctx: ProtocolGenerator.GenerationContext,
37-
shape: Shape,
38-
members: List<MemberShape>,
39-
writer: KotlinWriter,
40-
): AbstractSerdeDescriptorGenerator =
41-
AwsQuerySerdeXmlDescriptorGenerator(ctx.toRenderingContext(this, shape, writer), members)
33+
override fun structuredDataSerializer(ctx: ProtocolGenerator.GenerationContext): StructuredDataSerializerGenerator =
34+
AwsQuerySerializerGenerator(this)
4235

43-
override fun getSerializerDescriptorGenerator(
44-
ctx: ProtocolGenerator.GenerationContext,
45-
shape: Shape,
46-
members: List<MemberShape>,
47-
writer: KotlinWriter,
48-
): AbstractSerdeDescriptorGenerator =
49-
AwsQuerySerdeFormUrlDescriptorGenerator(ctx.toRenderingContext(this, shape, writer), members)
50-
51-
/**
52-
* Unwraps the response body as specified by
53-
* https://awslabs.github.io/smithy/1.0/spec/aws/aws-query-protocol.html#response-serialization so that the
54-
* deserializer is in the correct state.
55-
*/
56-
override fun unwrapOperationResponseBody(
57-
operationName: String,
58-
writer: KotlinWriter
59-
) {
60-
writer
61-
.addImport(
62-
RuntimeTypes.Serde.SdkFieldDescriptor,
63-
RuntimeTypes.Serde.SerdeXml.XmlSerialName,
64-
RuntimeTypes.Serde.SdkObjectDescriptor,
65-
RuntimeTypes.Serde.deserializeStruct
66-
)
67-
.write("")
68-
.write("val resultDescriptor = #T(SerialKind.Struct, #T(#S))", RuntimeTypes.Serde.SdkFieldDescriptor, RuntimeTypes.Serde.SerdeXml.XmlSerialName, "${operationName}Result")
69-
.openBlock("val wrapperDescriptor = #T.build {", "}", RuntimeTypes.Serde.SdkObjectDescriptor) {
70-
writer
71-
.addImport(RuntimeTypes.Serde.field)
72-
.write("trait(#T(#S))", RuntimeTypes.Serde.SerdeXml.XmlSerialName, "${operationName}Response")
73-
.write("#T(resultDescriptor)", RuntimeTypes.Serde.field)
74-
}
75-
.write("")
76-
// abandon the iterator, this only occurs at the top level operational output
77-
.write("val wrapper = deserializer.#T(wrapperDescriptor)", RuntimeTypes.Serde.deserializeStruct)
78-
.openBlock("if (wrapper.findNextFieldIndex() != resultDescriptor.index) {", "}") {
79-
writer
80-
.addImport(RuntimeTypes.Serde.DeserializationException)
81-
.write("throw #T(#S)", RuntimeTypes.Serde.DeserializationException, "failed to unwrap $operationName response")
82-
}
83-
writer.write("")
84-
}
36+
override fun structuredDataParser(ctx: ProtocolGenerator.GenerationContext): StructuredDataParserGenerator =
37+
AwsQueryXmlParserGenerator(this)
8538

8639
override fun getErrorCode(ctx: ProtocolGenerator.GenerationContext, errShapeId: ShapeId): String {
8740
val errShape = ctx.model.expectShape(errShapeId)
@@ -134,3 +87,63 @@ private class AwsQuerySerdeXmlDescriptorGenerator(
13487
return traits
13588
}
13689
}
90+
91+
private class AwsQuerySerializerGenerator(
92+
private val protocolGenerator: AwsQuery
93+
) : AbstractQueryFormUrlSerializerGenerator(protocolGenerator, protocolGenerator.defaultTimestampFormat) {
94+
override fun descriptorGenerator(
95+
ctx: ProtocolGenerator.GenerationContext,
96+
shape: Shape,
97+
members: List<MemberShape>,
98+
writer: KotlinWriter
99+
): FormUrlSerdeDescriptorGenerator = AwsQuerySerdeFormUrlDescriptorGenerator(ctx.toRenderingContext(protocolGenerator, shape, writer), members)
100+
}
101+
102+
private class AwsQueryXmlParserGenerator(
103+
private val protocolGenerator: AwsQuery
104+
) : XmlParserGenerator(protocolGenerator, protocolGenerator.defaultTimestampFormat) {
105+
106+
override fun descriptorGenerator(
107+
ctx: ProtocolGenerator.GenerationContext,
108+
shape: Shape,
109+
members: List<MemberShape>,
110+
writer: KotlinWriter
111+
): XmlSerdeDescriptorGenerator = AwsQuerySerdeXmlDescriptorGenerator(ctx.toRenderingContext(protocolGenerator, shape, writer), members)
112+
113+
override fun renderDeserializeOperationBody(
114+
ctx: ProtocolGenerator.GenerationContext,
115+
op: OperationShape,
116+
documentMembers: List<MemberShape>,
117+
writer: KotlinWriter
118+
) {
119+
writer.write("val deserializer = #T(payload)", RuntimeTypes.Serde.SerdeXml.XmlDeserializer)
120+
unwrapOperationResponseBody(op.id.name, writer)
121+
val shape = ctx.model.expectShape(op.output.get())
122+
renderDeserializerBody(ctx, shape, documentMembers, writer)
123+
}
124+
125+
/**
126+
* Unwraps the response body as specified by
127+
* https://awslabs.github.io/smithy/1.0/spec/aws/aws-query-protocol.html#response-serialization so that the
128+
* deserializer is in the correct state.
129+
*/
130+
private fun unwrapOperationResponseBody(
131+
operationName: String,
132+
writer: KotlinWriter
133+
) {
134+
writer.write("// begin unwrap response wrapper")
135+
.write("val resultDescriptor = #T(#T.Struct, #T(#S))", RuntimeTypes.Serde.SdkFieldDescriptor, RuntimeTypes.Serde.SerialKind, RuntimeTypes.Serde.SerdeXml.XmlSerialName, "${operationName}Result")
136+
.withBlock("val wrapperDescriptor = #T.build {", "}", RuntimeTypes.Serde.SdkObjectDescriptor) {
137+
write("trait(#T(#S))", RuntimeTypes.Serde.SerdeXml.XmlSerialName, "${operationName}Response")
138+
write("#T(resultDescriptor)", RuntimeTypes.Serde.field)
139+
}
140+
.write("")
141+
// abandon the iterator, this only occurs at the top level operational output
142+
.write("val wrapper = deserializer.#T(wrapperDescriptor)", RuntimeTypes.Serde.deserializeStruct)
143+
.withBlock("if (wrapper.findNextFieldIndex() != resultDescriptor.index) {", "}") {
144+
write("throw #T(#S)", RuntimeTypes.Serde.DeserializationException, "failed to unwrap $operationName response")
145+
}
146+
.write("// end unwrap response wrapper")
147+
.write("")
148+
}
149+
}

codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/Ec2Query.kt

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package aws.sdk.kotlin.codegen.protocols
66

77
import aws.sdk.kotlin.codegen.AwsRuntimeTypes
8+
import aws.sdk.kotlin.codegen.protocols.core.AbstractQueryFormUrlSerializerGenerator
89
import aws.sdk.kotlin.codegen.protocols.core.QueryHttpBindingProtocolGenerator
910
import aws.sdk.kotlin.codegen.protocols.formurl.QuerySerdeFormUrlDescriptorGenerator
1011
import software.amazon.smithy.aws.traits.protocols.Ec2QueryNameTrait
@@ -18,10 +19,7 @@ import software.amazon.smithy.kotlin.codegen.model.hasTrait
1819
import software.amazon.smithy.kotlin.codegen.model.traits.OperationOutput
1920
import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolGenerator
2021
import software.amazon.smithy.kotlin.codegen.rendering.protocol.toRenderingContext
21-
import software.amazon.smithy.kotlin.codegen.rendering.serde.AbstractSerdeDescriptorGenerator
22-
import software.amazon.smithy.kotlin.codegen.rendering.serde.SdkFieldDescriptorTrait
23-
import software.amazon.smithy.kotlin.codegen.rendering.serde.XmlSerdeDescriptorGenerator
24-
import software.amazon.smithy.kotlin.codegen.rendering.serde.add
22+
import software.amazon.smithy.kotlin.codegen.rendering.serde.*
2523
import software.amazon.smithy.kotlin.codegen.utils.dq
2624
import software.amazon.smithy.model.shapes.*
2725
import software.amazon.smithy.model.traits.XmlNameTrait
@@ -32,25 +30,11 @@ import software.amazon.smithy.model.traits.XmlNameTrait
3230
class Ec2Query : QueryHttpBindingProtocolGenerator() {
3331
override val protocol: ShapeId = Ec2QueryTrait.ID
3432

35-
override fun getDeserializerDescriptorGenerator(
36-
ctx: ProtocolGenerator.GenerationContext,
37-
shape: Shape,
38-
members: List<MemberShape>,
39-
writer: KotlinWriter,
40-
): AbstractSerdeDescriptorGenerator =
41-
Ec2QuerySerdeXmlDescriptorGenerator(ctx.toRenderingContext(this, shape, writer), members)
33+
override fun structuredDataSerializer(ctx: ProtocolGenerator.GenerationContext): StructuredDataSerializerGenerator =
34+
Ec2QuerySerializerGenerator(this)
4235

43-
override fun getSerializerDescriptorGenerator(
44-
ctx: ProtocolGenerator.GenerationContext,
45-
shape: Shape,
46-
members: List<MemberShape>,
47-
writer: KotlinWriter,
48-
): AbstractSerdeDescriptorGenerator =
49-
Ec2QuerySerdeFormUrlDescriptorGenerator(ctx.toRenderingContext(this, shape, writer), members)
50-
51-
override fun unwrapOperationResponseBody(operationName: String, writer: KotlinWriter) {
52-
// Do nothing. EC2 query doesn't require the kind of response unwrapping that AWS query does.
53-
}
36+
override fun structuredDataParser(ctx: ProtocolGenerator.GenerationContext): StructuredDataParserGenerator =
37+
Ec2QueryParserGenerator(this)
5438

5539
override fun renderDeserializeErrorDetails(
5640
ctx: ProtocolGenerator.GenerationContext,
@@ -106,3 +90,27 @@ private class Ec2QuerySerdeXmlDescriptorGenerator(
10690
return traits
10791
}
10892
}
93+
94+
private class Ec2QuerySerializerGenerator(
95+
private val protocolGenerator: Ec2Query
96+
) : AbstractQueryFormUrlSerializerGenerator(protocolGenerator, protocolGenerator.defaultTimestampFormat) {
97+
98+
override fun descriptorGenerator(
99+
ctx: ProtocolGenerator.GenerationContext,
100+
shape: Shape,
101+
members: List<MemberShape>,
102+
writer: KotlinWriter
103+
): FormUrlSerdeDescriptorGenerator = Ec2QuerySerdeFormUrlDescriptorGenerator(ctx.toRenderingContext(protocolGenerator, shape, writer), members)
104+
}
105+
106+
private class Ec2QueryParserGenerator(
107+
private val protocolGenerator: Ec2Query
108+
) : XmlParserGenerator(protocolGenerator, protocolGenerator.defaultTimestampFormat) {
109+
110+
override fun descriptorGenerator(
111+
ctx: ProtocolGenerator.GenerationContext,
112+
shape: Shape,
113+
members: List<MemberShape>,
114+
writer: KotlinWriter
115+
): XmlSerdeDescriptorGenerator = Ec2QuerySerdeXmlDescriptorGenerator(ctx.toRenderingContext(protocolGenerator, shape, writer), members)
116+
}

0 commit comments

Comments
 (0)