Skip to content

Commit 5acf7ef

Browse files
authored
refactor: decrease generated artifact size (#1057)
1 parent 281d415 commit 5acf7ef

File tree

34 files changed

+380
-160
lines changed

34 files changed

+380
-160
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"id": "1332be89-09d8-4b30-9e42-6f7a353c4c72",
3+
"type": "misc",
4+
"description": "Decrease generated client artifact sizes by reducing the number of suspension points for operations and inlining commonly used HTTP builders"
5+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class AwsQuery : QueryHttpBindingProtocolGenerator() {
4545
writer: KotlinWriter,
4646
) {
4747
writer.write("""checkNotNull(payload){ "unable to parse error from empty response" }""")
48-
writer.write("#T(payload)", RuntimeTypes.AwsXmlProtocols.parseRestXmlErrorResponse)
48+
writer.write("#T(payload)", RuntimeTypes.AwsXmlProtocols.parseRestXmlErrorResponseNoSuspend)
4949
}
5050
}
5151

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ class Ec2Query : QueryHttpBindingProtocolGenerator() {
4040
writer: KotlinWriter,
4141
) {
4242
writer.write("""checkNotNull(payload){ "unable to parse error from empty response" }""")
43-
writer.write("#T(payload)", RuntimeTypes.AwsXmlProtocols.parseEc2QueryErrorResponse)
43+
writer.write("#T(payload)", RuntimeTypes.AwsXmlProtocols.parseEc2QueryErrorResponseNoSuspend)
4444
}
4545
}
4646

codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/RestXml.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ open class RestXml : AwsHttpBindingProtocolGenerator() {
6363
writer: KotlinWriter,
6464
) {
6565
writer.write("""checkNotNull(payload){ "unable to parse error from empty response" }""")
66-
writer.write("#T(payload)", RuntimeTypes.AwsXmlProtocols.parseRestXmlErrorResponse)
66+
writer.write("#T(payload)", RuntimeTypes.AwsXmlProtocols.parseRestXmlErrorResponseNoSuspend)
6767
}
6868
}
6969

codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/core/AwsHttpBindingProtocolGenerator.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,11 @@ abstract class AwsHttpBindingProtocolGenerator : HttpBindingProtocolGenerator()
8484
override fun operationErrorHandler(ctx: ProtocolGenerator.GenerationContext, op: OperationShape): Symbol =
8585
op.errorHandler(ctx.settings) { writer ->
8686
writer.withBlock(
87-
"private suspend fun ${op.errorHandlerName()}(context: #T, call: #T): #Q {",
87+
"private fun ${op.errorHandlerName()}(context: #T, call: #T, payload: #T?): #Q {",
8888
"}",
8989
RuntimeTypes.Core.ExecutionContext,
9090
RuntimeTypes.Http.HttpCall,
91+
KotlinTypes.ByteArray,
9192
KotlinTypes.Nothing,
9293
) {
9394
renderThrowOperationError(ctx, op, writer)
@@ -107,8 +108,7 @@ abstract class AwsHttpBindingProtocolGenerator : HttpBindingProtocolGenerator()
107108
),
108109
) {
109110
val exceptionBaseSymbol = ExceptionBaseClassGenerator.baseExceptionSymbol(ctx.settings)
110-
writer.write("val payload = call.response.body.#T()", RuntimeTypes.Http.readAll)
111-
.write("val wrappedResponse = call.response.#T(payload)", RuntimeTypes.AwsProtocolCore.withPayload)
111+
writer.write("val wrappedResponse = call.response.#T(payload)", RuntimeTypes.AwsProtocolCore.withPayload)
112112
.write("val wrappedCall = call.copy(response = wrappedResponse)")
113113
.write("")
114114
.declareSection(
@@ -151,7 +151,7 @@ abstract class AwsHttpBindingProtocolGenerator : HttpBindingProtocolGenerator()
151151
name = "${errSymbol.name}Deserializer"
152152
namespace = ctx.settings.pkg.serde
153153
}
154-
writer.write("#S -> #T().deserialize(context, wrappedCall)", getErrorCode(ctx, err), errDeserializerSymbol)
154+
writer.write("#S -> #T().deserialize(context, wrappedCall, payload)", getErrorCode(ctx, err), errDeserializerSymbol)
155155
}
156156
write("else -> #T(errorDetails.message)", exceptionBaseSymbol)
157157
}

codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,9 @@ object RuntimeTypes {
5555
val EndpointResolver = symbol("EndpointResolver")
5656
val ResolveEndpointRequest = symbol("ResolveEndpointRequest")
5757
val execute = symbol("execute")
58-
val HttpDeserialize = symbol("HttpDeserialize")
58+
val HttpDeserializer = symbol("HttpDeserializer")
5959
val HttpOperationContext = symbol("HttpOperationContext")
60-
val HttpSerialize = symbol("HttpSerialize")
60+
val HttpSerializer = symbol("HttpSerializer")
6161
val OperationAuthConfig = symbol("OperationAuthConfig")
6262
val OperationMetrics = symbol("OperationMetrics")
6363
val OperationRequest = symbol("OperationRequest")
@@ -407,8 +407,8 @@ object RuntimeTypes {
407407
val RestJsonErrorDeserializer = symbol("RestJsonErrorDeserializer")
408408
}
409409
object AwsXmlProtocols : RuntimeTypePackage(KotlinDependency.AWS_XML_PROTOCOLS) {
410-
val parseRestXmlErrorResponse = symbol("parseRestXmlErrorResponse")
411-
val parseEc2QueryErrorResponse = symbol("parseEc2QueryErrorResponse")
410+
val parseRestXmlErrorResponseNoSuspend = symbol("parseRestXmlErrorResponseNoSuspend")
411+
val parseEc2QueryErrorResponseNoSuspend = symbol("parseEc2QueryErrorResponseNoSuspend")
412412
}
413413

414414
object AwsEventStream : RuntimeTypePackage(KotlinDependency.AWS_EVENT_STREAM) {

codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGenerator.kt

Lines changed: 83 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator {
7777
* The function should have the following signature:
7878
*
7979
* ```
80-
* suspend fun throwFooOperationError(context: ExecutionContext, call: HttpCall): Nothing {
80+
* fun throwFooOperationError(context: ExecutionContext, call: HttpCall, payload: ByteArray?): Nothing {
8181
* <-- CURRENT WRITER CONTEXT -->
8282
* }
8383
* ```
@@ -169,20 +169,25 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator {
169169
val operationSerializerSymbols = setOf(
170170
RuntimeTypes.Http.HttpBody,
171171
RuntimeTypes.Http.HttpMethod,
172-
RuntimeTypes.HttpClient.Operation.HttpSerialize,
173-
RuntimeTypes.Http.Request.HttpRequestBuilder,
174172
RuntimeTypes.Http.Request.url,
175173
)
174+
175+
val serdeMeta = HttpSerdeMeta(op.isInputEventStream(ctx.model))
176+
176177
ctx.delegator.useSymbolWriter(serializerSymbol) { writer ->
177-
// import all of http, http.request, and serde packages. All serializers requires one or more of the symbols
178-
// and most require quite a few. Rather than try and figure out which specific ones are used just take them
179-
// all to ensure all the various DSL builders are available, etc
180178
writer
181179
.addImport(operationSerializerSymbols)
182180
.write("")
183-
.openBlock("internal class #T: #T<#T> {", serializerSymbol, RuntimeTypes.HttpClient.Operation.HttpSerialize, inputSymbol)
181+
.openBlock("internal class #T: #T.#L<#T> {", serializerSymbol, RuntimeTypes.HttpClient.Operation.HttpSerializer, serdeMeta.variantName, inputSymbol)
184182
.call {
185-
writer.openBlock("override suspend fun serialize(context: #T, input: #T): #T {", RuntimeTypes.Core.ExecutionContext, inputSymbol, RuntimeTypes.Http.Request.HttpRequestBuilder)
183+
val modifier = if (serdeMeta.isStreaming) "suspend " else ""
184+
writer.openBlock(
185+
"override #Lfun serialize(context: #T, input: #T): #T {",
186+
modifier,
187+
RuntimeTypes.Core.ExecutionContext,
188+
inputSymbol,
189+
RuntimeTypes.Http.Request.HttpRequestBuilder,
190+
)
186191
.write("val builder = #T()", RuntimeTypes.Http.Request.HttpRequestBuilder)
187192
.call {
188193
renderHttpSerialize(ctx, op, writer)
@@ -546,18 +551,22 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator {
546551

547552
val resolver = getProtocolHttpBindingResolver(ctx.model, ctx.service)
548553
val responseBindings = resolver.responseBindings(op)
554+
555+
val serdeMeta = httpDeserializerInfo(ctx, op)
556+
549557
ctx.delegator.useSymbolWriter(deserializerSymbol) { writer ->
550558
writer
551559
.write("")
552560
.openBlock(
553-
"internal class #T: #T<#T> {",
561+
"internal class #T: #T.#L<#T> {",
554562
deserializerSymbol,
555-
RuntimeTypes.HttpClient.Operation.HttpDeserialize,
563+
RuntimeTypes.HttpClient.Operation.HttpDeserializer,
564+
serdeMeta.variantName,
556565
outputSymbol,
557566
)
558567
.write("")
559568
.call {
560-
renderHttpDeserialize(ctx, outputSymbol, responseBindings, op, writer)
569+
renderHttpDeserialize(ctx, outputSymbol, responseBindings, serdeMeta, op, writer)
561570
}
562571
.closeBlock("}")
563572
}
@@ -569,8 +578,12 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator {
569578
protected open fun renderIsHttpError(ctx: ProtocolGenerator.GenerationContext, op: OperationShape, writer: KotlinWriter) {
570579
writer.addImport(RuntimeTypes.Http.isSuccess)
571580
writer.withBlock("if (!response.status.#T()) {", "}", RuntimeTypes.Http.isSuccess) {
581+
val serdeMeta = httpDeserializerInfo(ctx, op)
582+
if (serdeMeta.isStreaming) {
583+
writer.write("val payload = response.body.#T()", RuntimeTypes.Http.readAll)
584+
}
572585
val errorHandlerFn = operationErrorHandler(ctx, op)
573-
write("#T(context, call)", errorHandlerFn)
586+
write("#T(context, call, payload)", errorHandlerFn)
574587
}
575588
}
576589

@@ -587,7 +600,6 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator {
587600
RuntimeTypes.Serde.SerialKind,
588601
RuntimeTypes.Serde.deserializeStruct,
589602
RuntimeTypes.Http.Response.HttpResponse,
590-
RuntimeTypes.HttpClient.Operation.HttpDeserialize,
591603
)
592604

593605
val deserializerSymbol = buildSymbol {
@@ -598,16 +610,19 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator {
598610
reference(outputSymbol, SymbolReference.ContextOption.DECLARE)
599611
}
600612

613+
// exception deserializers are never streaming
614+
val serdeMeta = HttpSerdeMeta(false)
615+
601616
ctx.delegator.useSymbolWriter(deserializerSymbol) { writer ->
602617
val resolver = getProtocolHttpBindingResolver(ctx.model, ctx.service)
603618
val responseBindings = resolver.responseBindings(shape)
604619
writer
605620
.addImport(exceptionDeserializerSymbols)
606621
.write("")
607-
.openBlock("internal class #T: #T<#T> {", deserializerSymbol, RuntimeTypes.HttpClient.Operation.HttpDeserialize, outputSymbol)
622+
.openBlock("internal class #T: #T.NonStreaming<#T> {", deserializerSymbol, RuntimeTypes.HttpClient.Operation.HttpDeserializer, outputSymbol)
608623
.write("")
609624
.call {
610-
renderHttpDeserialize(ctx, outputSymbol, responseBindings, null, writer)
625+
renderHttpDeserialize(ctx, outputSymbol, responseBindings, serdeMeta, null, writer)
611626
}
612627
.closeBlock("}")
613628
}
@@ -617,18 +632,31 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator {
617632
ctx: ProtocolGenerator.GenerationContext,
618633
outputSymbol: Symbol,
619634
responseBindings: List<HttpBindingDescriptor>,
635+
serdeMeta: HttpSerdeMeta,
620636
// this method is shared between operation and exception deserialization. In the case of operations this MUST be set
621637
op: OperationShape?,
622638
writer: KotlinWriter,
623639
) {
624-
writer
625-
.openBlock(
626-
"override suspend fun deserialize(context: #T, call: #T): #T {",
627-
RuntimeTypes.Core.ExecutionContext,
628-
RuntimeTypes.Http.HttpCall,
629-
outputSymbol,
630-
)
631-
.write("val response = call.response")
640+
if (serdeMeta.isStreaming) {
641+
writer
642+
.openBlock(
643+
"override suspend fun deserialize(context: #T, call: #T): #T {",
644+
RuntimeTypes.Core.ExecutionContext,
645+
RuntimeTypes.Http.HttpCall,
646+
outputSymbol,
647+
)
648+
} else {
649+
writer
650+
.openBlock(
651+
"override fun deserialize(context: #T, call: #T, payload: #T?): #T {",
652+
RuntimeTypes.Core.ExecutionContext,
653+
RuntimeTypes.Http.HttpCall,
654+
KotlinTypes.ByteArray,
655+
outputSymbol,
656+
)
657+
}
658+
659+
writer.write("val response = call.response")
632660
.call {
633661
if (outputSymbol.shape?.isError == false && op != null) {
634662
// handle operation errors
@@ -657,7 +685,7 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator {
657685
if (op != null && op.isOutputEventStream(ctx.model)) {
658686
deserializeViaEventStream(ctx, op, writer)
659687
} else {
660-
deserializeViaPayload(ctx, outputSymbol, responseBindings, op, writer)
688+
deserializeViaPayload(ctx, outputSymbol, responseBindings, serdeMeta, op, writer)
661689
}
662690
}
663691
.call {
@@ -681,6 +709,7 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator {
681709
ctx: ProtocolGenerator.GenerationContext,
682710
outputSymbol: Symbol,
683711
responseBindings: List<HttpBindingDescriptor>,
712+
serdeMeta: HttpSerdeMeta,
684713
// this method is shared between operation and exception deserialization. In the case of operations this MUST be set
685714
op: OperationShape?,
686715
writer: KotlinWriter,
@@ -707,10 +736,11 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator {
707736
sdg.errorDeserializer(ctx, outputSymbol.shape as StructureShape, documentMembers)
708737
}
709738

710-
writer.write("val payload = response.body.#T()", RuntimeTypes.Http.readAll)
711-
.withBlock("if (payload != null) {", "}") {
739+
if (!serdeMeta.isStreaming) {
740+
writer.withBlock("if (payload != null) {", "}") {
712741
write("#T(builder, payload)", bodyDeserializerFn)
713742
}
743+
}
714744
}
715745
}
716746
}
@@ -872,7 +902,6 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator {
872902
""
873903
}
874904

875-
// writer.addImport("${KotlinDependency.CLIENT_RT_HTTP.namespace}.util", splitFn)
876905
writer
877906
.addImport(splitFn, KotlinDependency.HTTP, subpackage = "util")
878907
.write("builder.#L = response.headers.getAll(#S)?.flatMap(::$splitFn)$mapFn", memberName, headerName)
@@ -940,9 +969,12 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator {
940969
val memberName = binding.member.defaultName()
941970
val target = ctx.model.expectShape(binding.member.target)
942971
val targetSymbol = ctx.symbolProvider.toSymbol(target)
972+
973+
// NOTE: we don't need serde metadata to know what to do here. Everything is non-streaming except streaming
974+
// blob payloads.
943975
when (target.type) {
944976
ShapeType.STRING -> {
945-
writer.write("val contents = response.body.#T()?.decodeToString()", RuntimeTypes.Http.readAll)
977+
writer.write("val contents = payload?.decodeToString()")
946978
if (target.isEnum) {
947979
writer.write("builder.$memberName = contents?.let { #T.fromValue(it) }", targetSymbol)
948980
} else {
@@ -951,36 +983,32 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator {
951983
}
952984

953985
ShapeType.ENUM -> {
954-
writer.write("val contents = response.body.#T()?.decodeToString()", RuntimeTypes.Http.readAll)
986+
writer.write("val contents = payload?.decodeToString()")
955987
writer.write("builder.#L = contents?.let { #T.fromValue(it) }", memberName, targetSymbol)
956988
}
957989

958990
ShapeType.INT_ENUM -> {
959-
writer.write("val contents = response.body.#T()?.decodeToString()", RuntimeTypes.Http.readAll)
991+
writer.write("val contents = payload?.decodeToString()")
960992
writer.write("builder.#L = contents?.let { #T.fromValue(it.toInt()) }", memberName, targetSymbol)
961993
}
962994

963995
ShapeType.BLOB -> {
964996
val isBinaryStream = target.hasTrait<StreamingTrait>()
965-
val conversion = if (isBinaryStream) {
966-
writer.addImport(RuntimeTypes.Http.toByteStream)
967-
"toByteStream()"
997+
if (isBinaryStream) {
998+
writer.write("builder.#L = response.body.#T()", memberName, RuntimeTypes.Http.toByteStream)
968999
} else {
969-
writer.addImport(RuntimeTypes.Http.readAll)
970-
"readAll()"
1000+
writer.write("builder.#L = payload", memberName)
9711001
}
972-
writer.write("builder.$memberName = response.body.$conversion")
9731002
}
9741003

9751004
ShapeType.STRUCTURE, ShapeType.UNION, ShapeType.DOCUMENT -> {
9761005
// delegate to the payload deserializer
9771006
val sdg = structuredDataParser(ctx)
9781007
val payloadDeserializerFn = sdg.payloadDeserializer(ctx, binding.member)
9791008

980-
writer.write("val payload = response.body.#T()", RuntimeTypes.Http.readAll)
981-
.withBlock("if (payload != null) {", "}") {
982-
write("builder.#L = #T(payload)", memberName, payloadDeserializerFn)
983-
}
1009+
writer.withBlock("if (payload != null) {", "}") {
1010+
write("builder.#L = #T(payload)", memberName, payloadDeserializerFn)
1011+
}
9841012
}
9851013

9861014
else ->
@@ -1061,3 +1089,18 @@ private fun renderNonBlankGuard(ctx: ProtocolGenerator.GenerationContext, member
10611089
private fun MemberShape.isNonBlankInStruct(ctx: ProtocolGenerator.GenerationContext): Boolean =
10621090
ctx.model.expectShape(target).isStringShape &&
10631091
getTrait<LengthTrait>()?.min?.getOrNull()?.takeIf { it > 0 } != null
1092+
1093+
private data class HttpSerdeMeta(val isStreaming: Boolean) {
1094+
/**
1095+
* The name of the HttpSerializer<T>/HttpDeserializer<T> variant
1096+
*/
1097+
val variantName: String
1098+
get() = if (isStreaming) "Streaming" else "NonStreaming"
1099+
}
1100+
1101+
private fun httpDeserializerInfo(ctx: ProtocolGenerator.GenerationContext, op: OperationShape): HttpSerdeMeta {
1102+
val isStreaming = ctx.model.expectShape<StructureShape>(op.output.get()).hasStreamingMember(ctx.model) ||
1103+
op.isOutputEventStream(ctx.model)
1104+
1105+
return HttpSerdeMeta(isStreaming)
1106+
}

0 commit comments

Comments
 (0)