From 1643126df260ca070e2abf69ab0aff8d87540aa6 Mon Sep 17 00:00:00 2001 From: Aaron J Todd Date: Wed, 20 Mar 2024 09:23:38 -0400 Subject: [PATCH 1/4] refactor: inline HTTP and URL builders where possible to reduce generated code size --- .../kotlin/runtime/http/request/HttpRequestBuilder.kt | 6 +++--- .../aws/smithy/kotlin/runtime/net/url/QueryParameters.kt | 6 +++--- .../common/src/aws/smithy/kotlin/runtime/net/url/Url.kt | 8 ++++---- .../src/aws/smithy/kotlin/runtime/net/url/UrlPath.kt | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/runtime/protocol/http/common/src/aws/smithy/kotlin/runtime/http/request/HttpRequestBuilder.kt b/runtime/protocol/http/common/src/aws/smithy/kotlin/runtime/http/request/HttpRequestBuilder.kt index 7cb2be2a2..a75aa0957 100644 --- a/runtime/protocol/http/common/src/aws/smithy/kotlin/runtime/http/request/HttpRequestBuilder.kt +++ b/runtime/protocol/http/common/src/aws/smithy/kotlin/runtime/http/request/HttpRequestBuilder.kt @@ -67,21 +67,21 @@ public fun HttpRequestBuilder.immutableView( /** * Modify the URL inside the block */ -public fun HttpRequestBuilder.url(block: Url.Builder.() -> Unit) { +public inline fun HttpRequestBuilder.url(block: Url.Builder.() -> Unit) { url.apply(block) } /** * Set values from an existing [Url] instance */ -public fun HttpRequestBuilder.url(value: Url) { +public inline fun HttpRequestBuilder.url(value: Url) { url.copyFrom(value) } /** * Modify the headers inside the given block */ -public fun HttpRequestBuilder.headers(block: HeadersBuilder.() -> Unit) { +public inline fun HttpRequestBuilder.headers(block: HeadersBuilder.() -> Unit) { headers.apply(block) } diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/QueryParameters.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/QueryParameters.kt index aaca48b73..3b3208c55 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/QueryParameters.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/QueryParameters.kt @@ -36,7 +36,7 @@ public class QueryParameters private constructor( * @param block The code to apply to the builder * @return A new [QueryParameters] instance */ - public operator fun invoke(block: Builder.() -> Unit): QueryParameters = Builder().apply(block).build() + public inline operator fun invoke(block: Builder.() -> Unit): QueryParameters = Builder().apply(block).build() private fun asDecoded(values: Sequence>, forceQuerySeparator: Boolean) = asString(values, forceQuerySeparator, Encodable::decoded) @@ -198,7 +198,7 @@ public class QueryParameters private constructor( * Applies the given DSL block to the decoded query parameters. Changes to this map are reflected in the * [encodedParameters] map as well. */ - public fun decodedParameters(block: MutableMultiMap.() -> Unit) { + public inline fun decodedParameters(block: MutableMultiMap.() -> Unit) { decodedParameters.apply(block) } @@ -231,7 +231,7 @@ public class QueryParameters private constructor( * Applies the given DSL block to the encoded query parameters. Changes to this map are reflected in the * [decodedParameters] map as well. */ - public fun encodedParameters(block: MutableMultiMap.() -> Unit) { + public inline fun encodedParameters(block: MutableMultiMap.() -> Unit) { encodedParameters.apply(block) } diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/Url.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/Url.kt index 637ef7870..e1f6292f1 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/Url.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/Url.kt @@ -36,7 +36,7 @@ public class Url private constructor( * @param block The code to apply to the builder * @return A new [Url] instance */ - public operator fun invoke(block: Builder.() -> Unit): Url = Builder().apply(block).build() + public inline operator fun invoke(block: Builder.() -> Unit): Url = Builder().apply(block).build() /** * Parse a URL string into a [Url] instance @@ -252,7 +252,7 @@ public class Url private constructor( * Update the [UrlPath] of this URL via a DSL builder block * @param block The code to apply to the [UrlPath] builder */ - public fun path(block: UrlPath.Builder.() -> Unit) { + public inline fun path(block: UrlPath.Builder.() -> Unit) { path.apply(block) } @@ -267,7 +267,7 @@ public class Url private constructor( * Update the [QueryParameters] of this URL via a DSL builder block * @param block The code to apply to the [QueryParameters] builder */ - public fun parameters(block: QueryParameters.Builder.() -> Unit) { + public inline fun parameters(block: QueryParameters.Builder.() -> Unit) { parameters.apply(block) } @@ -282,7 +282,7 @@ public class Url private constructor( * Set the user info in this URL via a DSL builder block * @param block The code to apply to the [UserInfo] builder */ - public fun userInfo(block: UserInfo.Builder.() -> Unit) { + public inline fun userInfo(block: UserInfo.Builder.() -> Unit) { userInfo.apply(block) } diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/UrlPath.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/UrlPath.kt index 561e6ca50..77e0c3c13 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/UrlPath.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/UrlPath.kt @@ -28,7 +28,7 @@ public class UrlPath private constructor( * @param block The code to apply to the builder * @return A new [UrlPath] instance */ - public operator fun invoke(block: Builder.() -> Unit): UrlPath = Builder().apply(block).build() + public inline operator fun invoke(block: Builder.() -> Unit): UrlPath = Builder().apply(block).build() private fun asDecoded(segments: List, trailingSlash: Boolean) = asString(segments, trailingSlash, Encodable::decoded) From dbd52ec6abb6f323ed2218dd63eb7df4586ed042 Mon Sep 17 00:00:00 2001 From: Aaron J Todd Date: Wed, 20 Mar 2024 12:04:34 -0400 Subject: [PATCH 2/4] refactor: remove operation serde suspend where possible --- .../core/AwsHttpBindingProtocolGenerator.kt | 8 +- .../kotlin/codegen/core/RuntimeTypes.kt | 4 +- .../protocol/HttpBindingProtocolGenerator.kt | 123 ++++++++++++------ .../protocol/HttpProtocolClientGenerator.kt | 8 +- .../codegen/IdempotentTokenGeneratorTest.kt | 12 +- .../HttpBindingProtocolGeneratorTest.kt | 55 ++++---- .../HttpProtocolClientGeneratorTest.kt | 40 +++--- .../xml/Ec2QueryErrorDeserializer.kt | 2 +- .../xml/RestXmlErrorDeserializer.kt | 2 +- .../runtime/http/operation/HttpSerde.kt | 62 +++++++++ .../http/operation/SdkHttpOperation.kt | 35 ++++- .../http/operation/SdkOperationExecution.kt | 27 ++-- .../protocols/SerdeProtocolGenerator.kt | 4 +- .../codegen/util/CodegenTestIntegration.kt | 4 +- 14 files changed, 264 insertions(+), 122 deletions(-) diff --git a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/core/AwsHttpBindingProtocolGenerator.kt b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/core/AwsHttpBindingProtocolGenerator.kt index af7436732..f4afc9942 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/core/AwsHttpBindingProtocolGenerator.kt +++ b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/core/AwsHttpBindingProtocolGenerator.kt @@ -84,10 +84,11 @@ abstract class AwsHttpBindingProtocolGenerator : HttpBindingProtocolGenerator() override fun operationErrorHandler(ctx: ProtocolGenerator.GenerationContext, op: OperationShape): Symbol = op.errorHandler(ctx.settings) { writer -> writer.withBlock( - "private suspend fun ${op.errorHandlerName()}(context: #T, call: #T): #Q {", + "private fun ${op.errorHandlerName()}(context: #T, call: #T, payload: #T?): #Q {", "}", RuntimeTypes.Core.ExecutionContext, RuntimeTypes.Http.HttpCall, + KotlinTypes.ByteArray, KotlinTypes.Nothing, ) { renderThrowOperationError(ctx, op, writer) @@ -107,8 +108,7 @@ abstract class AwsHttpBindingProtocolGenerator : HttpBindingProtocolGenerator() ), ) { val exceptionBaseSymbol = ExceptionBaseClassGenerator.baseExceptionSymbol(ctx.settings) - writer.write("val payload = call.response.body.#T()", RuntimeTypes.Http.readAll) - .write("val wrappedResponse = call.response.#T(payload)", RuntimeTypes.AwsProtocolCore.withPayload) + writer.write("val wrappedResponse = call.response.#T(payload)", RuntimeTypes.AwsProtocolCore.withPayload) .write("val wrappedCall = call.copy(response = wrappedResponse)") .write("") .declareSection( @@ -151,7 +151,7 @@ abstract class AwsHttpBindingProtocolGenerator : HttpBindingProtocolGenerator() name = "${errSymbol.name}Deserializer" namespace = ctx.settings.pkg.serde } - writer.write("#S -> #T().deserialize(context, wrappedCall)", getErrorCode(ctx, err), errDeserializerSymbol) + writer.write("#S -> #T().deserialize(context, wrappedCall, payload)", getErrorCode(ctx, err), errDeserializerSymbol) } write("else -> #T(errorDetails.message)", exceptionBaseSymbol) } diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt index a22a1f0f7..bba0b1936 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt @@ -55,9 +55,9 @@ object RuntimeTypes { val EndpointResolver = symbol("EndpointResolver") val ResolveEndpointRequest = symbol("ResolveEndpointRequest") val execute = symbol("execute") - val HttpDeserialize = symbol("HttpDeserialize") + val HttpDeserializer = symbol("HttpDeserializer") val HttpOperationContext = symbol("HttpOperationContext") - val HttpSerialize = symbol("HttpSerialize") + val HttpSerializer = symbol("HttpSerializer") val OperationAuthConfig = symbol("OperationAuthConfig") val OperationMetrics = symbol("OperationMetrics") val OperationRequest = symbol("OperationRequest") diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGenerator.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGenerator.kt index e74474ba3..a8d4c5de7 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGenerator.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGenerator.kt @@ -77,7 +77,7 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator { * The function should have the following signature: * * ``` - * suspend fun throwFooOperationError(context: ExecutionContext, call: HttpCall): Nothing { + * fun throwFooOperationError(context: ExecutionContext, call: HttpCall, payload: HttpByteArray?): Nothing { * <-- CURRENT WRITER CONTEXT --> * } * ``` @@ -169,20 +169,25 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator { val operationSerializerSymbols = setOf( RuntimeTypes.Http.HttpBody, RuntimeTypes.Http.HttpMethod, - RuntimeTypes.HttpClient.Operation.HttpSerialize, - RuntimeTypes.Http.Request.HttpRequestBuilder, RuntimeTypes.Http.Request.url, ) + + val serdeMeta = HttpSerdeMeta(op.isInputEventStream(ctx.model)) + ctx.delegator.useSymbolWriter(serializerSymbol) { writer -> - // import all of http, http.request, and serde packages. All serializers requires one or more of the symbols - // and most require quite a few. Rather than try and figure out which specific ones are used just take them - // all to ensure all the various DSL builders are available, etc writer .addImport(operationSerializerSymbols) .write("") - .openBlock("internal class #T: #T<#T> {", serializerSymbol, RuntimeTypes.HttpClient.Operation.HttpSerialize, inputSymbol) + .openBlock("internal class #T: #T.#L<#T> {", serializerSymbol, RuntimeTypes.HttpClient.Operation.HttpSerializer, serdeMeta.variantName, inputSymbol) .call { - writer.openBlock("override suspend fun serialize(context: #T, input: #T): #T {", RuntimeTypes.Core.ExecutionContext, inputSymbol, RuntimeTypes.Http.Request.HttpRequestBuilder) + val modifier = if (serdeMeta.isStreaming) "suspend " else "" + writer.openBlock( + "override #Lfun serialize(context: #T, input: #T): #T {", + modifier, + RuntimeTypes.Core.ExecutionContext, + inputSymbol, + RuntimeTypes.Http.Request.HttpRequestBuilder, + ) .write("val builder = #T()", RuntimeTypes.Http.Request.HttpRequestBuilder) .call { renderHttpSerialize(ctx, op, writer) @@ -546,18 +551,22 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator { val resolver = getProtocolHttpBindingResolver(ctx.model, ctx.service) val responseBindings = resolver.responseBindings(op) + + val serdeMeta = httpDeserializerInfo(ctx, op) + ctx.delegator.useSymbolWriter(deserializerSymbol) { writer -> writer .write("") .openBlock( - "internal class #T: #T<#T> {", + "internal class #T: #T.#L<#T> {", deserializerSymbol, - RuntimeTypes.HttpClient.Operation.HttpDeserialize, + RuntimeTypes.HttpClient.Operation.HttpDeserializer, + serdeMeta.variantName, outputSymbol, ) .write("") .call { - renderHttpDeserialize(ctx, outputSymbol, responseBindings, op, writer) + renderHttpDeserialize(ctx, outputSymbol, responseBindings, serdeMeta, op, writer) } .closeBlock("}") } @@ -569,8 +578,12 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator { protected open fun renderIsHttpError(ctx: ProtocolGenerator.GenerationContext, op: OperationShape, writer: KotlinWriter) { writer.addImport(RuntimeTypes.Http.isSuccess) writer.withBlock("if (!response.status.#T()) {", "}", RuntimeTypes.Http.isSuccess) { + val serdeMeta = httpDeserializerInfo(ctx, op) + if (serdeMeta.isStreaming) { + writer.write("val payload = response.body.#T()", RuntimeTypes.Http.readAll) + } val errorHandlerFn = operationErrorHandler(ctx, op) - write("#T(context, call)", errorHandlerFn) + write("#T(context, call, payload)", errorHandlerFn) } } @@ -587,7 +600,6 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator { RuntimeTypes.Serde.SerialKind, RuntimeTypes.Serde.deserializeStruct, RuntimeTypes.Http.Response.HttpResponse, - RuntimeTypes.HttpClient.Operation.HttpDeserialize, ) val deserializerSymbol = buildSymbol { @@ -598,16 +610,19 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator { reference(outputSymbol, SymbolReference.ContextOption.DECLARE) } + // exception deserializers are never streaming + val serdeMeta = HttpSerdeMeta(false) + ctx.delegator.useSymbolWriter(deserializerSymbol) { writer -> val resolver = getProtocolHttpBindingResolver(ctx.model, ctx.service) val responseBindings = resolver.responseBindings(shape) writer .addImport(exceptionDeserializerSymbols) .write("") - .openBlock("internal class #T: #T<#T> {", deserializerSymbol, RuntimeTypes.HttpClient.Operation.HttpDeserialize, outputSymbol) + .openBlock("internal class #T: #T.NonStreaming<#T> {", deserializerSymbol, RuntimeTypes.HttpClient.Operation.HttpDeserializer, outputSymbol) .write("") .call { - renderHttpDeserialize(ctx, outputSymbol, responseBindings, null, writer) + renderHttpDeserialize(ctx, outputSymbol, responseBindings, serdeMeta, null, writer) } .closeBlock("}") } @@ -617,18 +632,31 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator { ctx: ProtocolGenerator.GenerationContext, outputSymbol: Symbol, responseBindings: List, + serdeMeta: HttpSerdeMeta, // this method is shared between operation and exception deserialization. In the case of operations this MUST be set op: OperationShape?, writer: KotlinWriter, ) { - writer - .openBlock( - "override suspend fun deserialize(context: #T, call: #T): #T {", - RuntimeTypes.Core.ExecutionContext, - RuntimeTypes.Http.HttpCall, - outputSymbol, - ) - .write("val response = call.response") + if (serdeMeta.isStreaming) { + writer + .openBlock( + "override suspend fun deserialize(context: #T, call: #T): #T {", + RuntimeTypes.Core.ExecutionContext, + RuntimeTypes.Http.HttpCall, + outputSymbol, + ) + } else { + writer + .openBlock( + "override fun deserialize(context: #T, call: #T, payload: #T?): #T {", + RuntimeTypes.Core.ExecutionContext, + RuntimeTypes.Http.HttpCall, + KotlinTypes.ByteArray, + outputSymbol, + ) + } + + writer.write("val response = call.response") .call { if (outputSymbol.shape?.isError == false && op != null) { // handle operation errors @@ -657,7 +685,7 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator { if (op != null && op.isOutputEventStream(ctx.model)) { deserializeViaEventStream(ctx, op, writer) } else { - deserializeViaPayload(ctx, outputSymbol, responseBindings, op, writer) + deserializeViaPayload(ctx, outputSymbol, responseBindings, serdeMeta, op, writer) } } .call { @@ -681,6 +709,7 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator { ctx: ProtocolGenerator.GenerationContext, outputSymbol: Symbol, responseBindings: List, + serdeMeta: HttpSerdeMeta, // this method is shared between operation and exception deserialization. In the case of operations this MUST be set op: OperationShape?, writer: KotlinWriter, @@ -707,10 +736,11 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator { sdg.errorDeserializer(ctx, outputSymbol.shape as StructureShape, documentMembers) } - writer.write("val payload = response.body.#T()", RuntimeTypes.Http.readAll) - .withBlock("if (payload != null) {", "}") { + if (!serdeMeta.isStreaming) { + writer.withBlock("if (payload != null) {", "}") { write("#T(builder, payload)", bodyDeserializerFn) } + } } } } @@ -872,7 +902,6 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator { "" } - // writer.addImport("${KotlinDependency.CLIENT_RT_HTTP.namespace}.util", splitFn) writer .addImport(splitFn, KotlinDependency.HTTP, subpackage = "util") .write("builder.#L = response.headers.getAll(#S)?.flatMap(::$splitFn)$mapFn", memberName, headerName) @@ -940,9 +969,12 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator { val memberName = binding.member.defaultName() val target = ctx.model.expectShape(binding.member.target) val targetSymbol = ctx.symbolProvider.toSymbol(target) + + // NOTE: we don't need serde metadata to know what to do here. Everything is non-streaming except streaming + // blob payloads. when (target.type) { ShapeType.STRING -> { - writer.write("val contents = response.body.#T()?.decodeToString()", RuntimeTypes.Http.readAll) + writer.write("val contents = payload?.decodeToString()") if (target.isEnum) { writer.write("builder.$memberName = contents?.let { #T.fromValue(it) }", targetSymbol) } else { @@ -951,25 +983,22 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator { } ShapeType.ENUM -> { - writer.write("val contents = response.body.#T()?.decodeToString()", RuntimeTypes.Http.readAll) + writer.write("val contents = payload?.decodeToString()") writer.write("builder.#L = contents?.let { #T.fromValue(it) }", memberName, targetSymbol) } ShapeType.INT_ENUM -> { - writer.write("val contents = response.body.#T()?.decodeToString()", RuntimeTypes.Http.readAll) + writer.write("val contents = payload?.decodeToString()") writer.write("builder.#L = contents?.let { #T.fromValue(it.toInt()) }", memberName, targetSymbol) } ShapeType.BLOB -> { val isBinaryStream = target.hasTrait() - val conversion = if (isBinaryStream) { - writer.addImport(RuntimeTypes.Http.toByteStream) - "toByteStream()" + if (isBinaryStream) { + writer.write("builder.#L = response.body.#T()", memberName, RuntimeTypes.Http.toByteStream) } else { - writer.addImport(RuntimeTypes.Http.readAll) - "readAll()" + writer.write("builder.#L = payload", memberName) } - writer.write("builder.$memberName = response.body.$conversion") } ShapeType.STRUCTURE, ShapeType.UNION, ShapeType.DOCUMENT -> { @@ -977,10 +1006,9 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator { val sdg = structuredDataParser(ctx) val payloadDeserializerFn = sdg.payloadDeserializer(ctx, binding.member) - writer.write("val payload = response.body.#T()", RuntimeTypes.Http.readAll) - .withBlock("if (payload != null) {", "}") { - write("builder.#L = #T(payload)", memberName, payloadDeserializerFn) - } + writer.withBlock("if (payload != null) {", "}") { + write("builder.#L = #T(payload)", memberName, payloadDeserializerFn) + } } else -> @@ -1061,3 +1089,18 @@ private fun renderNonBlankGuard(ctx: ProtocolGenerator.GenerationContext, member private fun MemberShape.isNonBlankInStruct(ctx: ProtocolGenerator.GenerationContext): Boolean = ctx.model.expectShape(target).isStringShape && getTrait()?.min?.getOrNull()?.takeIf { it > 0 } != null + +private data class HttpSerdeMeta(val isStreaming: Boolean) { + /** + * The name of the HttpSerializer/HttpDeserializer variant + */ + val variantName: String + get() = if (isStreaming) "Streaming" else "NonStreaming" +} + +private fun httpDeserializerInfo(ctx: ProtocolGenerator.GenerationContext, op: OperationShape): HttpSerdeMeta { + val isStreaming = ctx.model.expectShape(op.output.get()).hasStreamingMember(ctx.model) || + op.isOutputEventStream(ctx.model) + + return HttpSerdeMeta(isStreaming) +} diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpProtocolClientGenerator.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpProtocolClientGenerator.kt index a25332260..519957cbe 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpProtocolClientGenerator.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpProtocolClientGenerator.kt @@ -192,16 +192,16 @@ open class HttpProtocolClientGenerator( outputSymbolName, ) { if (inputShape.isPresent) { - writer.write("serializer = ${op.serializerName()}()") + writer.write("serializeWith = ${op.serializerName()}()") } else { // no serializer implementation is generated for operations with no input, inline the HTTP // protocol request from the operation itself // NOTE: this will never be triggered for AWS models where we preprocess operations to always have inputs/outputs writer.addImport(RuntimeTypes.Http.Request.HttpRequestBuilder) writer.addImport(RuntimeTypes.Core.ExecutionContext) - writer.openBlock("serializer = object : HttpSerialize<#Q> {", "}", KotlinTypes.Unit) { + writer.openBlock("serializer = object : #T.NonStreaming<#Q> {", "}", RuntimeTypes.HttpClient.Operation.HttpSerializer, KotlinTypes.Unit) { writer.openBlock( - "override suspend fun serialize(context: ExecutionContext, input: #Q): HttpRequestBuilder {", + "override fun serialize(context: ExecutionContext, input: #Q): HttpRequestBuilder {", "}", KotlinTypes.Unit, ) { @@ -215,7 +215,7 @@ open class HttpProtocolClientGenerator( } if (outputShape.isPresent) { - writer.write("deserializer = ${op.deserializerName()}()") + writer.write("deserializeWith = ${op.deserializerName()}()") } else { writer.write("deserializer = UnitDeserializer") } diff --git a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/IdempotentTokenGeneratorTest.kt b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/IdempotentTokenGeneratorTest.kt index 431cf08f4..f7b3a5568 100644 --- a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/IdempotentTokenGeneratorTest.kt +++ b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/IdempotentTokenGeneratorTest.kt @@ -31,8 +31,8 @@ class IdempotentTokenGeneratorTest { val contents = getSerdeFileContents("AllocateWidgetOperationSerializer.kt") contents.assertBalancedBracesAndParens() val expectedContents = """ - internal class AllocateWidgetOperationSerializer: HttpSerialize { - override suspend fun serialize(context: ExecutionContext, input: AllocateWidgetRequest): HttpRequestBuilder { + internal class AllocateWidgetOperationSerializer: HttpSerializer.NonStreaming { + override fun serialize(context: ExecutionContext, input: AllocateWidgetRequest): HttpRequestBuilder { val builder = HttpRequestBuilder() builder.method = HttpMethod.POST @@ -57,8 +57,8 @@ class IdempotentTokenGeneratorTest { val contents = getSerdeFileContents("AllocateWidgetQueryOperationSerializer.kt") contents.assertBalancedBracesAndParens() val expectedContents = """ -internal class AllocateWidgetQueryOperationSerializer: HttpSerialize { - override suspend fun serialize(context: ExecutionContext, input: AllocateWidgetQueryRequest): HttpRequestBuilder { +internal class AllocateWidgetQueryOperationSerializer: HttpSerializer.NonStreaming { + override fun serialize(context: ExecutionContext, input: AllocateWidgetQueryRequest): HttpRequestBuilder { val builder = HttpRequestBuilder() builder.method = HttpMethod.POST @@ -81,8 +81,8 @@ internal class AllocateWidgetQueryOperationSerializer: HttpSerialize { - override suspend fun serialize(context: ExecutionContext, input: AllocateWidgetHeaderRequest): HttpRequestBuilder { +internal class AllocateWidgetHeaderOperationSerializer: HttpSerializer.NonStreaming { + override fun serialize(context: ExecutionContext, input: AllocateWidgetHeaderRequest): HttpRequestBuilder { val builder = HttpRequestBuilder() builder.method = HttpMethod.POST diff --git a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGeneratorTest.kt b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGeneratorTest.kt index ea5715625..731c090df 100644 --- a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGeneratorTest.kt +++ b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGeneratorTest.kt @@ -41,8 +41,8 @@ class HttpBindingProtocolGeneratorTest { contents.assertBalancedBracesAndParens() val label1 = "\${input.label1}" // workaround for raw strings not being able to contain escapes val expectedContents = """ -internal class SmokeTestOperationSerializer: HttpSerialize { - override suspend fun serialize(context: ExecutionContext, input: SmokeTestRequest): HttpRequestBuilder { +internal class SmokeTestOperationSerializer: HttpSerializer.NonStreaming { + override fun serialize(context: ExecutionContext, input: SmokeTestRequest): HttpRequestBuilder { val builder = HttpRequestBuilder() builder.method = HttpMethod.POST @@ -81,8 +81,8 @@ internal class SmokeTestOperationSerializer: HttpSerialize { val contents = getSerdeFileContents("ExplicitStringOperationSerializer.kt") contents.assertBalancedBracesAndParens() val expectedContents = """ -internal class ExplicitStringOperationSerializer: HttpSerialize { - override suspend fun serialize(context: ExecutionContext, input: ExplicitStringRequest): HttpRequestBuilder { +internal class ExplicitStringOperationSerializer: HttpSerializer.NonStreaming { + override fun serialize(context: ExecutionContext, input: ExplicitStringRequest): HttpRequestBuilder { val builder = HttpRequestBuilder() builder.method = HttpMethod.POST @@ -108,8 +108,8 @@ internal class ExplicitStringOperationSerializer: HttpSerialize { - override suspend fun serialize(context: ExecutionContext, input: ExplicitBlobRequest): HttpRequestBuilder { +internal class ExplicitBlobOperationSerializer: HttpSerializer.NonStreaming { + override fun serialize(context: ExecutionContext, input: ExplicitBlobRequest): HttpRequestBuilder { val builder = HttpRequestBuilder() builder.method = HttpMethod.POST @@ -135,8 +135,8 @@ internal class ExplicitBlobOperationSerializer: HttpSerialize { - override suspend fun serialize(context: ExecutionContext, input: ExplicitBlobStreamRequest): HttpRequestBuilder { +internal class ExplicitBlobStreamOperationSerializer: HttpSerializer.NonStreaming { + override fun serialize(context: ExecutionContext, input: ExplicitBlobStreamRequest): HttpRequestBuilder { val builder = HttpRequestBuilder() builder.method = HttpMethod.POST @@ -162,8 +162,8 @@ internal class ExplicitBlobStreamOperationSerializer: HttpSerialize { - override suspend fun serialize(context: ExecutionContext, input: ExplicitStructRequest): HttpRequestBuilder { +internal class ExplicitStructOperationSerializer: HttpSerializer.NonStreaming { + override fun serialize(context: ExecutionContext, input: ExplicitStructRequest): HttpRequestBuilder { val builder = HttpRequestBuilder() builder.method = HttpMethod.POST @@ -190,8 +190,8 @@ internal class ExplicitStructOperationSerializer: HttpSerialize { - override suspend fun serialize(context: ExecutionContext, input: ExplicitDocumentRequest): HttpRequestBuilder { +internal class ExplicitDocumentOperationSerializer: HttpSerializer.NonStreaming { + override fun serialize(context: ExecutionContext, input: ExplicitDocumentRequest): HttpRequestBuilder { val builder = HttpRequestBuilder() builder.method = HttpMethod.POST @@ -218,8 +218,8 @@ internal class ExplicitDocumentOperationSerializer: HttpSerialize { - override suspend fun serialize(context: ExecutionContext, input: EnumInputRequest): HttpRequestBuilder { +internal class EnumInputOperationSerializer: HttpSerializer.NonStreaming { + override fun serialize(context: ExecutionContext, input: EnumInputRequest): HttpRequestBuilder { val builder = HttpRequestBuilder() builder.method = HttpMethod.POST @@ -249,8 +249,8 @@ internal class EnumInputOperationSerializer: HttpSerialize { contents.assertBalancedBracesAndParens() val tsLabel = "\${input.tsLabel.format(TimestampFormat.ISO_8601)}" // workaround for raw strings not being able to contain escapes val expectedContents = """ -internal class TimestampInputOperationSerializer: HttpSerialize { - override suspend fun serialize(context: ExecutionContext, input: TimestampInputRequest): HttpRequestBuilder { +internal class TimestampInputOperationSerializer: HttpSerializer.NonStreaming { + override fun serialize(context: ExecutionContext, input: TimestampInputRequest): HttpRequestBuilder { val builder = HttpRequestBuilder() builder.method = HttpMethod.POST @@ -292,8 +292,8 @@ internal class TimestampInputOperationSerializer: HttpSerialize { - override suspend fun serialize(context: ExecutionContext, input: BlobInputRequest): HttpRequestBuilder { +internal class BlobInputOperationSerializer: HttpSerializer.NonStreaming { + override fun serialize(context: ExecutionContext, input: BlobInputRequest): HttpRequestBuilder { val builder = HttpRequestBuilder() builder.method = HttpMethod.POST @@ -325,8 +325,8 @@ internal class BlobInputOperationSerializer: HttpSerialize { contents.assertBalancedBracesAndParens() val label1 = "\${input.hello}" // workaround for raw strings not being able to contain escapes val expectedContents = """ -internal class ConstantQueryStringOperationSerializer: HttpSerialize { - override suspend fun serialize(context: ExecutionContext, input: ConstantQueryStringRequest): HttpRequestBuilder { +internal class ConstantQueryStringOperationSerializer: HttpSerializer.NonStreaming { + override fun serialize(context: ExecutionContext, input: ConstantQueryStringRequest): HttpRequestBuilder { val builder = HttpRequestBuilder() builder.method = HttpMethod.GET @@ -353,12 +353,12 @@ internal class ConstantQueryStringOperationSerializer: HttpSerialize { +internal class SmokeTestOperationDeserializer: HttpDeserializer.NonStreaming { - override suspend fun deserialize(context: ExecutionContext, call: HttpCall): SmokeTestResponse { + override fun deserialize(context: ExecutionContext, call: HttpCall, payload: ByteArray?): SmokeTestResponse { val response = call.response if (!response.status.isSuccess()) { - throwSmokeTestError(context, call) + throwSmokeTestError(context, call, payload) } val builder = SmokeTestResponse.Builder() @@ -366,7 +366,6 @@ internal class SmokeTestOperationDeserializer: HttpDeserialize { - serializer = GetFooOperationSerializer() - deserializer = GetFooOperationDeserializer() + serializeWith = GetFooOperationSerializer() + deserializeWith = GetFooOperationDeserializer() operationName = "GetFoo" serviceName = ServiceId telemetry { @@ -101,8 +101,8 @@ class HttpProtocolClientGeneratorTest { """ override suspend fun getFooNoInput(input: GetFooNoInputRequest): GetFooNoInputResponse { val op = SdkHttpOperation.build { - serializer = GetFooNoInputOperationSerializer() - deserializer = GetFooNoInputOperationDeserializer() + serializeWith = GetFooNoInputOperationSerializer() + deserializeWith = GetFooNoInputOperationDeserializer() operationName = "GetFooNoInput" serviceName = ServiceId telemetry { @@ -124,8 +124,8 @@ class HttpProtocolClientGeneratorTest { """ override suspend fun getFooNoOutput(input: GetFooNoOutputRequest): GetFooNoOutputResponse { val op = SdkHttpOperation.build { - serializer = GetFooNoOutputOperationSerializer() - deserializer = GetFooNoOutputOperationDeserializer() + serializeWith = GetFooNoOutputOperationSerializer() + deserializeWith = GetFooNoOutputOperationDeserializer() operationName = "GetFooNoOutput" serviceName = ServiceId telemetry { @@ -147,8 +147,8 @@ class HttpProtocolClientGeneratorTest { """ override suspend fun getFooStreamingInput(input: GetFooStreamingInputRequest): GetFooStreamingInputResponse { val op = SdkHttpOperation.build { - serializer = GetFooStreamingInputOperationSerializer() - deserializer = GetFooStreamingInputOperationDeserializer() + serializeWith = GetFooStreamingInputOperationSerializer() + deserializeWith = GetFooStreamingInputOperationDeserializer() operationName = "GetFooStreamingInput" serviceName = ServiceId telemetry { @@ -170,8 +170,8 @@ class HttpProtocolClientGeneratorTest { """ override suspend fun getFooStreamingOutput(input: GetFooStreamingOutputRequest, block: suspend (GetFooStreamingOutputResponse) -> T): T { val op = SdkHttpOperation.build { - serializer = GetFooStreamingOutputOperationSerializer() - deserializer = GetFooStreamingOutputOperationDeserializer() + serializeWith = GetFooStreamingOutputOperationSerializer() + deserializeWith = GetFooStreamingOutputOperationDeserializer() operationName = "GetFooStreamingOutput" serviceName = ServiceId telemetry { @@ -193,8 +193,8 @@ class HttpProtocolClientGeneratorTest { """ override suspend fun getFooStreamingOutputNoInput(input: GetFooStreamingOutputNoInputRequest, block: suspend (GetFooStreamingOutputNoInputResponse) -> T): T { val op = SdkHttpOperation.build { - serializer = GetFooStreamingOutputNoInputOperationSerializer() - deserializer = GetFooStreamingOutputNoInputOperationDeserializer() + serializeWith = GetFooStreamingOutputNoInputOperationSerializer() + deserializeWith = GetFooStreamingOutputNoInputOperationDeserializer() operationName = "GetFooStreamingOutputNoInput" serviceName = ServiceId telemetry { @@ -216,8 +216,8 @@ class HttpProtocolClientGeneratorTest { """ override suspend fun getFooStreamingInputNoOutput(input: GetFooStreamingInputNoOutputRequest): GetFooStreamingInputNoOutputResponse { val op = SdkHttpOperation.build { - serializer = GetFooStreamingInputNoOutputOperationSerializer() - deserializer = GetFooStreamingInputNoOutputOperationDeserializer() + serializeWith = GetFooStreamingInputNoOutputOperationSerializer() + deserializeWith = GetFooStreamingInputNoOutputOperationDeserializer() operationName = "GetFooStreamingInputNoOutput" serviceName = ServiceId telemetry { @@ -239,8 +239,8 @@ class HttpProtocolClientGeneratorTest { """ override suspend fun getFooNoRequired(input: GetFooNoRequiredRequest): GetFooNoRequiredResponse { val op = SdkHttpOperation.build { - serializer = GetFooNoRequiredOperationSerializer() - deserializer = GetFooNoRequiredOperationDeserializer() + serializeWith = GetFooNoRequiredOperationSerializer() + deserializeWith = GetFooNoRequiredOperationDeserializer() operationName = "GetFooNoRequired" serviceName = ServiceId telemetry { @@ -262,8 +262,8 @@ class HttpProtocolClientGeneratorTest { """ override suspend fun getFooSomeRequired(input: GetFooSomeRequiredRequest): GetFooSomeRequiredResponse { val op = SdkHttpOperation.build { - serializer = GetFooSomeRequiredOperationSerializer() - deserializer = GetFooSomeRequiredOperationDeserializer() + serializeWith = GetFooSomeRequiredOperationSerializer() + deserializeWith = GetFooSomeRequiredOperationDeserializer() operationName = "GetFooSomeRequired" serviceName = ServiceId telemetry { @@ -341,8 +341,8 @@ class HttpProtocolClientGeneratorTest { val prefix = "\${input.foo}.data." val expectedFragment = """ val op = SdkHttpOperation.build { - serializer = GetStatusOperationSerializer() - deserializer = GetStatusOperationDeserializer() + serializeWith = GetStatusOperationSerializer() + deserializeWith = GetStatusOperationDeserializer() operationName = "GetStatus" serviceName = ServiceId hostPrefix = "$prefix" diff --git a/runtime/protocol/aws-xml-protocols/common/src/aws/smithy/kotlin/runtime/awsprotocol/xml/Ec2QueryErrorDeserializer.kt b/runtime/protocol/aws-xml-protocols/common/src/aws/smithy/kotlin/runtime/awsprotocol/xml/Ec2QueryErrorDeserializer.kt index 4ce0e7c98..fdf5916ec 100644 --- a/runtime/protocol/aws-xml-protocols/common/src/aws/smithy/kotlin/runtime/awsprotocol/xml/Ec2QueryErrorDeserializer.kt +++ b/runtime/protocol/aws-xml-protocols/common/src/aws/smithy/kotlin/runtime/awsprotocol/xml/Ec2QueryErrorDeserializer.kt @@ -14,7 +14,7 @@ internal data class Ec2QueryErrorResponse(val errors: List, val r internal data class Ec2QueryError(val code: String?, val message: String?) @InternalApi -public suspend fun parseEc2QueryErrorResponse(payload: ByteArray): ErrorDetails { +public fun parseEc2QueryErrorResponse(payload: ByteArray): ErrorDetails { val response = Ec2QueryErrorResponseDeserializer.deserialize(xmlTagReader(payload)) val firstError = response.errors.firstOrNull() return ErrorDetails(firstError?.code, firstError?.message, response.requestId) diff --git a/runtime/protocol/aws-xml-protocols/common/src/aws/smithy/kotlin/runtime/awsprotocol/xml/RestXmlErrorDeserializer.kt b/runtime/protocol/aws-xml-protocols/common/src/aws/smithy/kotlin/runtime/awsprotocol/xml/RestXmlErrorDeserializer.kt index 60e494425..8a2e56e9d 100644 --- a/runtime/protocol/aws-xml-protocols/common/src/aws/smithy/kotlin/runtime/awsprotocol/xml/RestXmlErrorDeserializer.kt +++ b/runtime/protocol/aws-xml-protocols/common/src/aws/smithy/kotlin/runtime/awsprotocol/xml/RestXmlErrorDeserializer.kt @@ -33,7 +33,7 @@ internal data class XmlError( * Returns parsed data in normalized form or throws [DeserializationException] if response cannot be parsed. */ @InternalApi -public suspend fun parseRestXmlErrorResponse(payload: ByteArray): ErrorDetails { +public fun parseRestXmlErrorResponse(payload: ByteArray): ErrorDetails { val details = XmlErrorDeserializer.deserialize(xmlTagReader(payload)) return ErrorDetails(details.code, details.message, details.requestId) } diff --git a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/operation/HttpSerde.kt b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/operation/HttpSerde.kt index 7c5cc60bc..394dd3182 100644 --- a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/operation/HttpSerde.kt +++ b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/operation/HttpSerde.kt @@ -14,18 +14,80 @@ import aws.smithy.kotlin.runtime.operation.ExecutionContext * Implemented by types that know how to serialize to the HTTP protocol. */ @InternalApi +public sealed interface HttpSerializer { + + /** + * Serializer for streaming operations that need full control over serialization of the body + */ + @InternalApi + public interface Streaming : HttpSerializer { + public suspend fun serialize(context: ExecutionContext, input: T): HttpRequestBuilder + } + + /** + * Serializer for non-streaming (simple) operations that don't need to ever suspend. + */ + @InternalApi + public interface NonStreaming : HttpSerializer { + public fun serialize(context: ExecutionContext, input: T): HttpRequestBuilder + } +} + +/** + * Implemented by types that know how to deserialize from the HTTP protocol. + */ +@InternalApi +public sealed interface HttpDeserializer { + + /** + * Deserializer for streaming operations that need full control over deserialization of the body + */ + @InternalApi + public interface Streaming : HttpDeserializer { + public suspend fun deserialize(context: ExecutionContext, call: HttpCall): T + } + + /** + * Deserializer for non-streaming (simple) operations that don't need to ever suspend. These + * operations are handed the full payload if it exists. + */ + @InternalApi + public interface NonStreaming : HttpDeserializer { + public fun deserialize(context: ExecutionContext, call: HttpCall, payload: ByteArray?): T + } +} + +/** + * Implemented by types that know how to serialize to the HTTP protocol. + */ +@Deprecated("use HttpSerializer.Streaming") +@InternalApi public fun interface HttpSerialize { public suspend fun serialize(context: ExecutionContext, input: T): HttpRequestBuilder } +private class LegacyHttpSerializeAdapter(val serializer: HttpSerialize) : HttpSerializer.Streaming { + override suspend fun serialize(context: ExecutionContext, input: T): HttpRequestBuilder = + serializer.serialize(context, input) +} +internal fun HttpSerialize.intoSerializer(): HttpSerializer = LegacyHttpSerializeAdapter(this) + /** * Implemented by types that know how to deserialize from the HTTP protocol. */ +@Deprecated("use HttpDeserializer.Streaming") @InternalApi public fun interface HttpDeserialize { public suspend fun deserialize(context: ExecutionContext, call: HttpCall): T } +private class LegacyHttpDeserializeAdapter(val deserializer: HttpDeserialize) : HttpDeserializer.Streaming { + override suspend fun deserialize(context: ExecutionContext, call: HttpCall): T = + deserializer.deserialize(context, call) +} + +internal fun HttpDeserialize.intoDeserializer(): HttpDeserializer = LegacyHttpDeserializeAdapter(this) + /** * Convenience deserialize implementation for a type with no output type */ diff --git a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/operation/SdkHttpOperation.kt b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/operation/SdkHttpOperation.kt index 19523a83f..959c95565 100644 --- a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/operation/SdkHttpOperation.kt +++ b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/operation/SdkHttpOperation.kt @@ -31,11 +31,21 @@ import kotlin.reflect.KClass public class SdkHttpOperation internal constructor( public val execution: SdkOperationExecution, public val context: ExecutionContext, - internal val serializer: HttpSerialize, - internal val deserializer: HttpDeserialize, + internal val serializer: HttpSerializer, + internal val deserializer: HttpDeserializer, internal val typeInfo: OperationTypeInfo, internal val telemetry: SdkOperationTelemetry, ) { + + internal constructor( + execution: SdkOperationExecution, + context: ExecutionContext, + serializer: HttpSerialize, + deserializer: HttpDeserialize, + typeInfo: OperationTypeInfo, + telemetry: SdkOperationTelemetry, + ) : this(execution, context, serializer.intoSerializer(), deserializer.intoDeserializer(), typeInfo, telemetry) + init { context[HttpOperationContext.SdkInvocationId] = Uuid.random().toString() } @@ -121,8 +131,25 @@ public class SdkHttpOperationBuilder ( private val outputType: KClass<*>, ) { public val telemetry: SdkOperationTelemetry = SdkOperationTelemetry() + + @Deprecated("use serializeWith") public var serializer: HttpSerialize? = null + set(value) { + field = value + serializeWith = value?.intoSerializer() + } + + public var serializeWith: HttpSerializer? = null + + @Deprecated("use deserializeWith") public var deserializer: HttpDeserialize? = null + set(value) { + field = value + deserializeWith = value?.intoDeserializer() + } + + public var deserializeWith: HttpDeserializer? = null + public val execution: SdkOperationExecution = SdkOperationExecution() public val context: ExecutionContext = ExecutionContext() @@ -142,8 +169,8 @@ public class SdkHttpOperationBuilder ( public var hostPrefix: String? = null public fun build(): SdkHttpOperation { - val opSerializer = requireNotNull(serializer) { "SdkHttpOperation.serializer must not be null" } - val opDeserializer = requireNotNull(deserializer) { "SdkHttpOperation.deserializer must not be null" } + val opSerializer = requireNotNull(serializeWith) { "SdkHttpOperation.serializeWith must not be null" } + val opDeserializer = requireNotNull(deserializeWith) { "SdkHttpOperation.deserializeWith must not be null" } requireNotNull(operationName) { "operationName is a required HTTP execution attribute" } requireNotNull(serviceName) { "serviceName is a required HTTP execution attribute" } context[SdkClientOption.OperationName] = operationName!! diff --git a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/operation/SdkOperationExecution.kt b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/operation/SdkOperationExecution.kt index 29deb381c..ee1763117 100644 --- a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/operation/SdkOperationExecution.kt +++ b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/operation/SdkOperationExecution.kt @@ -18,6 +18,7 @@ import aws.smithy.kotlin.runtime.http.auth.SignHttpRequest import aws.smithy.kotlin.runtime.http.complete import aws.smithy.kotlin.runtime.http.interceptors.InterceptorExecutor import aws.smithy.kotlin.runtime.http.middleware.RetryMiddleware +import aws.smithy.kotlin.runtime.http.readAll import aws.smithy.kotlin.runtime.http.request.HttpRequestBuilder import aws.smithy.kotlin.runtime.http.request.dumpRequest import aws.smithy.kotlin.runtime.http.request.immutableView @@ -26,7 +27,6 @@ import aws.smithy.kotlin.runtime.http.response.dumpResponse import aws.smithy.kotlin.runtime.io.Handler import aws.smithy.kotlin.runtime.io.middleware.Middleware import aws.smithy.kotlin.runtime.io.middleware.Phase -import aws.smithy.kotlin.runtime.operation.ExecutionContext import aws.smithy.kotlin.runtime.retries.RetryStrategy import aws.smithy.kotlin.runtime.retries.StandardRetryStrategy import aws.smithy.kotlin.runtime.retries.policy.RetryPolicy @@ -181,15 +181,15 @@ internal fun SdkOperationExecution.decora return OperationHandler(initializeHandler, interceptors) } -private fun HttpSerialize.decorate( +private fun HttpSerializer.decorate( inner: Handler, interceptors: InterceptorExecutor, -): Handler, O> = SerializeHandler(inner, ::serialize, interceptors) +): Handler, O> = SerializeHandler(inner, this, interceptors) -private fun HttpDeserialize.decorate( +private fun HttpDeserializer.decorate( inner: Handler, interceptors: InterceptorExecutor, -): Handler = DeserializeHandler(inner, ::deserialize, interceptors) +): Handler = DeserializeHandler(inner, this, interceptors) // internal glue used to marry one phase to another @@ -230,7 +230,7 @@ private class InitializeHandler( private class SerializeHandler ( private val inner: Handler, - private val mapRequest: suspend (ExecutionContext, Input) -> HttpRequestBuilder, + private val serializer: HttpSerializer, private val interceptors: InterceptorExecutor, ) : Handler, Output> { @@ -243,7 +243,10 @@ private class SerializeHandler ( // store finalized operation input for later middleware to read if needed request.context[HttpOperationContext.OperationInput] = modified.subject as Any - val requestBuilder = mapRequest(modified.context, modified.subject) + val requestBuilder = when (serializer) { + is HttpSerializer.NonStreaming -> serializer.serialize(modified.context, modified.subject) + is HttpSerializer.Streaming -> serializer.serialize(modified.context, modified.subject) + } interceptors.readAfterSerialization(requestBuilder.immutableView()) return inner.call(SdkHttpRequest(modified.context, requestBuilder)) @@ -317,7 +320,7 @@ internal class AuthHandler( private class DeserializeHandler( private val inner: Handler, - private val mapResponse: suspend (ExecutionContext, HttpCall) -> Output, + private val deserializer: HttpDeserializer, private val interceptors: InterceptorExecutor, ) : Handler { @@ -329,7 +332,13 @@ private class DeserializeHandler( interceptors.readBeforeDeserialization(modified) - val output = mapResponse(request.context, modified) + val output = when (deserializer) { + is HttpDeserializer.NonStreaming -> { + val payload = modified.response.body.readAll() + deserializer.deserialize(request.context, modified, payload) + } + is HttpDeserializer.Streaming -> deserializer.deserialize(request.context, modified) + } interceptors.readAfterDeserialization(output, modified) return output diff --git a/tests/codegen/serde-codegen-support/src/main/kotlin/software/amazon/smithy/kotlin/codegen/protocols/SerdeProtocolGenerator.kt b/tests/codegen/serde-codegen-support/src/main/kotlin/software/amazon/smithy/kotlin/codegen/protocols/SerdeProtocolGenerator.kt index a1d85f729..1de8ac1f6 100644 --- a/tests/codegen/serde-codegen-support/src/main/kotlin/software/amazon/smithy/kotlin/codegen/protocols/SerdeProtocolGenerator.kt +++ b/tests/codegen/serde-codegen-support/src/main/kotlin/software/amazon/smithy/kotlin/codegen/protocols/SerdeProtocolGenerator.kt @@ -7,6 +7,7 @@ package software.amazon.smithy.kotlin.codegen.protocols import software.amazon.smithy.codegen.core.Symbol import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes import software.amazon.smithy.kotlin.codegen.core.withBlock +import software.amazon.smithy.kotlin.codegen.lang.KotlinTypes import software.amazon.smithy.kotlin.codegen.rendering.protocol.* import software.amazon.smithy.model.Model import software.amazon.smithy.model.shapes.OperationShape @@ -33,10 +34,11 @@ abstract class SerdeProtocolGenerator : HttpBindingProtocolGenerator() { override fun operationErrorHandler(ctx: ProtocolGenerator.GenerationContext, op: OperationShape): Symbol = op.errorHandler(ctx.settings) { writer -> writer.withBlock( - "private suspend fun ${op.errorHandlerName()}(context: #T, call: #T): Nothing {", + "private fun ${op.errorHandlerName()}(context: #T, call: #T, payload: #T?): Nothing {", "}", RuntimeTypes.Core.ExecutionContext, RuntimeTypes.Http.HttpCall, + KotlinTypes.ByteArray, ) { write("error(\"not needed for codegen related tests\")") } diff --git a/tests/compile/src/test/kotlin/software/amazon/smithy/kotlin/codegen/util/CodegenTestIntegration.kt b/tests/compile/src/test/kotlin/software/amazon/smithy/kotlin/codegen/util/CodegenTestIntegration.kt index dd651ff9d..0ded3a3d8 100644 --- a/tests/compile/src/test/kotlin/software/amazon/smithy/kotlin/codegen/util/CodegenTestIntegration.kt +++ b/tests/compile/src/test/kotlin/software/amazon/smithy/kotlin/codegen/util/CodegenTestIntegration.kt @@ -9,6 +9,7 @@ import software.amazon.smithy.codegen.core.Symbol import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes import software.amazon.smithy.kotlin.codegen.core.withBlock import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration +import software.amazon.smithy.kotlin.codegen.lang.KotlinTypes import software.amazon.smithy.kotlin.codegen.rendering.protocol.* import software.amazon.smithy.kotlin.codegen.rendering.serde.* import software.amazon.smithy.model.Model @@ -53,10 +54,11 @@ class RestJsonTestProtocolGenerator( override fun operationErrorHandler(ctx: ProtocolGenerator.GenerationContext, op: OperationShape): Symbol = op.errorHandler(ctx.settings) { writer -> writer.withBlock( - "private suspend fun ${op.errorHandlerName()}(context: #T, call: #T): Nothing {", + "private fun ${op.errorHandlerName()}(context: #T, call: #T, payload: #T?): Nothing {", "}", RuntimeTypes.Core.ExecutionContext, RuntimeTypes.Http.HttpCall, + KotlinTypes.ByteArray, ) { write("error(\"not needed for compile tests\")") } From a2d30f13a23e7dfd2105b5ac64e796f753bf797a Mon Sep 17 00:00:00 2001 From: Aaron J Todd Date: Thu, 28 Mar 2024 12:56:39 -0400 Subject: [PATCH 3/4] cleanup --- .../1332be89-09d8-4b30-9e42-6f7a353c4c72.json | 5 ++++ .../kotlin/codegen/aws/protocols/AwsQuery.kt | 2 +- .../kotlin/codegen/aws/protocols/Ec2Query.kt | 2 +- .../kotlin/codegen/aws/protocols/RestXml.kt | 2 +- .../kotlin/codegen/core/RuntimeTypes.kt | 4 +-- .../tests/SigningSuiteTestBaseJVM.kt | 5 ++-- .../http/auth/AwsHttpSignerTestBase.kt | 1 + .../awsprotocol/json/AwsJsonProtocolTest.kt | 3 +++ .../common/test/ClockSkewInterceptorTest.kt | 15 ++++++----- .../api/aws-xml-protocols.api | 2 ++ .../xml/Ec2QueryErrorDeserializer.kt | 6 ++++- .../xml/RestXmlErrorDeserializer.kt | 6 ++++- .../xml/Ec2QueryErrorDeserializerTest.kt | 6 ++--- .../xml/RestXmlErrorDeserializerTest.kt | 6 ++--- .../protocol/http-client/api/http-client.api | 26 +++++++++++++++++++ .../runtime/http/operation/HttpSerde.kt | 8 ++++++ .../http/operation/SdkHttpOperation.kt | 3 +++ ...lexibleChecksumsResponseInterceptorTest.kt | 6 ++--- ...ResponseLengthValidationInterceptorTest.kt | 19 +++++++++++--- .../http/operation/SdkHttpOperationTest.kt | 1 + .../runtime/http/operation/TestOperation.kt | 10 +++++-- .../http/request/HttpRequestBuilder.kt | 2 +- 22 files changed, 109 insertions(+), 31 deletions(-) create mode 100644 .changes/1332be89-09d8-4b30-9e42-6f7a353c4c72.json diff --git a/.changes/1332be89-09d8-4b30-9e42-6f7a353c4c72.json b/.changes/1332be89-09d8-4b30-9e42-6f7a353c4c72.json new file mode 100644 index 000000000..9cebb2a2f --- /dev/null +++ b/.changes/1332be89-09d8-4b30-9e42-6f7a353c4c72.json @@ -0,0 +1,5 @@ +{ + "id": "1332be89-09d8-4b30-9e42-6f7a353c4c72", + "type": "misc", + "description": "Decrease generated client artifact sizes by reducing the number of suspension points for operations and inlining commonly used HTTP builders" +} \ No newline at end of file diff --git a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/AwsQuery.kt b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/AwsQuery.kt index 027e9050f..48d47a7c1 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/AwsQuery.kt +++ b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/AwsQuery.kt @@ -45,7 +45,7 @@ class AwsQuery : QueryHttpBindingProtocolGenerator() { writer: KotlinWriter, ) { writer.write("""checkNotNull(payload){ "unable to parse error from empty response" }""") - writer.write("#T(payload)", RuntimeTypes.AwsXmlProtocols.parseRestXmlErrorResponse) + writer.write("#T(payload)", RuntimeTypes.AwsXmlProtocols.parseRestXmlErrorResponseNoSuspend) } } diff --git a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/Ec2Query.kt b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/Ec2Query.kt index 65751cefa..e31880432 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/Ec2Query.kt +++ b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/Ec2Query.kt @@ -40,7 +40,7 @@ class Ec2Query : QueryHttpBindingProtocolGenerator() { writer: KotlinWriter, ) { writer.write("""checkNotNull(payload){ "unable to parse error from empty response" }""") - writer.write("#T(payload)", RuntimeTypes.AwsXmlProtocols.parseEc2QueryErrorResponse) + writer.write("#T(payload)", RuntimeTypes.AwsXmlProtocols.parseEc2QueryErrorResponseNoSuspend) } } diff --git a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/RestXml.kt b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/RestXml.kt index c7c834c31..7c1f45b92 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/RestXml.kt +++ b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/RestXml.kt @@ -63,7 +63,7 @@ open class RestXml : AwsHttpBindingProtocolGenerator() { writer: KotlinWriter, ) { writer.write("""checkNotNull(payload){ "unable to parse error from empty response" }""") - writer.write("#T(payload)", RuntimeTypes.AwsXmlProtocols.parseRestXmlErrorResponse) + writer.write("#T(payload)", RuntimeTypes.AwsXmlProtocols.parseRestXmlErrorResponseNoSuspend) } } diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt index bba0b1936..62eb4d455 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt @@ -407,8 +407,8 @@ object RuntimeTypes { val RestJsonErrorDeserializer = symbol("RestJsonErrorDeserializer") } object AwsXmlProtocols : RuntimeTypePackage(KotlinDependency.AWS_XML_PROTOCOLS) { - val parseRestXmlErrorResponse = symbol("parseRestXmlErrorResponse") - val parseEc2QueryErrorResponse = symbol("parseEc2QueryErrorResponse") + val parseRestXmlErrorResponseNoSuspend = symbol("parseRestXmlErrorResponseNoSuspend") + val parseEc2QueryErrorResponseNoSuspend = symbol("parseEc2QueryErrorResponseNoSuspend") } object AwsEventStream : RuntimeTypePackage(KotlinDependency.AWS_EVENT_STREAM) { diff --git a/runtime/auth/aws-signing-tests/jvm/src/aws/smithy/kotlin/runtime/auth/awssigning/tests/SigningSuiteTestBaseJVM.kt b/runtime/auth/aws-signing-tests/jvm/src/aws/smithy/kotlin/runtime/auth/awssigning/tests/SigningSuiteTestBaseJVM.kt index 935426785..2b006bed6 100644 --- a/runtime/auth/aws-signing-tests/jvm/src/aws/smithy/kotlin/runtime/auth/awssigning/tests/SigningSuiteTestBaseJVM.kt +++ b/runtime/auth/aws-signing-tests/jvm/src/aws/smithy/kotlin/runtime/auth/awssigning/tests/SigningSuiteTestBaseJVM.kt @@ -416,9 +416,10 @@ private fun buildOperation( serialized: HttpRequestBuilder, ): SdkHttpOperation { val op = SdkHttpOperation.build { - serializer = object : HttpSerialize { - override suspend fun serialize(context: ExecutionContext, input: Unit): HttpRequestBuilder = serialized + serializeWith = object : HttpSerializer.NonStreaming { + override fun serialize(context: ExecutionContext, input: Unit): HttpRequestBuilder = serialized } + @Suppress("DEPRECATION") deserializer = IdentityDeserializer context { diff --git a/runtime/auth/http-auth-aws/common/test/aws/smithy/kotlin/runtime/http/auth/AwsHttpSignerTestBase.kt b/runtime/auth/http-auth-aws/common/test/aws/smithy/kotlin/runtime/http/auth/AwsHttpSignerTestBase.kt index 5a8b1a3e2..2ce308df8 100644 --- a/runtime/auth/http-auth-aws/common/test/aws/smithy/kotlin/runtime/http/auth/AwsHttpSignerTestBase.kt +++ b/runtime/auth/http-auth-aws/common/test/aws/smithy/kotlin/runtime/http/auth/AwsHttpSignerTestBase.kt @@ -41,6 +41,7 @@ public abstract class AwsHttpSignerTestBase( ) { private val testCredentials = Credentials("AKID", "SECRET", "SESSION") + @Suppress("DEPRECATION") private fun buildOperation( requestBody: String = "{\"TableName\": \"foo\"}", streaming: Boolean = false, diff --git a/runtime/protocol/aws-json-protocols/common/test/aws/smithy/kotlin/runtime/awsprotocol/json/AwsJsonProtocolTest.kt b/runtime/protocol/aws-json-protocols/common/test/aws/smithy/kotlin/runtime/awsprotocol/json/AwsJsonProtocolTest.kt index 17bdcab89..dacf2c4aa 100644 --- a/runtime/protocol/aws-json-protocols/common/test/aws/smithy/kotlin/runtime/awsprotocol/json/AwsJsonProtocolTest.kt +++ b/runtime/protocol/aws-json-protocols/common/test/aws/smithy/kotlin/runtime/awsprotocol/json/AwsJsonProtocolTest.kt @@ -20,6 +20,7 @@ class AwsJsonProtocolTest { @Test fun testSetJsonProtocolHeaders() = runTest { + @Suppress("DEPRECATION") val op = SdkHttpOperation.build { serializer = UnitSerializer deserializer = IdentityDeserializer @@ -41,6 +42,7 @@ class AwsJsonProtocolTest { @Test fun testEmptyBody() = runTest { + @Suppress("DEPRECATION") val op = SdkHttpOperation.build { serializer = UnitSerializer deserializer = IdentityDeserializer @@ -59,6 +61,7 @@ class AwsJsonProtocolTest { @Test fun testDoesNotOverride() = runTest { + @Suppress("DEPRECATION") val op = SdkHttpOperation.build { serializer = object : HttpSerialize { override suspend fun serialize(context: ExecutionContext, input: Unit): HttpRequestBuilder = diff --git a/runtime/protocol/aws-protocol-core/common/test/ClockSkewInterceptorTest.kt b/runtime/protocol/aws-protocol-core/common/test/ClockSkewInterceptorTest.kt index 4e67b12e1..d01d3c238 100644 --- a/runtime/protocol/aws-protocol-core/common/test/ClockSkewInterceptorTest.kt +++ b/runtime/protocol/aws-protocol-core/common/test/ClockSkewInterceptorTest.kt @@ -11,17 +11,14 @@ import aws.smithy.kotlin.runtime.awsprotocol.ClockSkewInterceptor.Companion.isSk import aws.smithy.kotlin.runtime.client.ResponseInterceptorContext import aws.smithy.kotlin.runtime.http.* import aws.smithy.kotlin.runtime.http.interceptors.HttpInterceptor -import aws.smithy.kotlin.runtime.http.operation.HttpDeserialize -import aws.smithy.kotlin.runtime.http.operation.HttpOperationContext -import aws.smithy.kotlin.runtime.http.operation.HttpSerialize -import aws.smithy.kotlin.runtime.http.operation.SdkHttpOperation -import aws.smithy.kotlin.runtime.http.operation.roundTrip +import aws.smithy.kotlin.runtime.http.operation.* import aws.smithy.kotlin.runtime.http.request.HttpRequest import aws.smithy.kotlin.runtime.http.request.HttpRequestBuilder import aws.smithy.kotlin.runtime.http.response.HttpResponse import aws.smithy.kotlin.runtime.httptest.TestEngine import aws.smithy.kotlin.runtime.io.SdkSource import aws.smithy.kotlin.runtime.io.source +import aws.smithy.kotlin.runtime.operation.ExecutionContext import aws.smithy.kotlin.runtime.time.Instant import aws.smithy.kotlin.runtime.time.until import kotlinx.coroutines.test.runTest @@ -188,8 +185,12 @@ class ClockSkewInterceptorTest { */ inline fun newTestOperation(serialized: HttpRequestBuilder, deserialized: O): SdkHttpOperation = SdkHttpOperation.build { - serializer = HttpSerialize { _, _ -> serialized } - deserializer = HttpDeserialize { _, _ -> deserialized } + serializeWith = object : HttpSerializer.NonStreaming { + override fun serialize(context: ExecutionContext, input: I): HttpRequestBuilder = serialized + } + deserializeWith = object : HttpDeserializer.NonStreaming { + override fun deserialize(context: ExecutionContext, call: HttpCall, payload: ByteArray?): O = deserialized + } // required operation context operationName = "TestOperation" diff --git a/runtime/protocol/aws-xml-protocols/api/aws-xml-protocols.api b/runtime/protocol/aws-xml-protocols/api/aws-xml-protocols.api index d92c49cc8..732543272 100644 --- a/runtime/protocol/aws-xml-protocols/api/aws-xml-protocols.api +++ b/runtime/protocol/aws-xml-protocols/api/aws-xml-protocols.api @@ -1,8 +1,10 @@ public final class aws/smithy/kotlin/runtime/awsprotocol/xml/Ec2QueryErrorDeserializerKt { public static final fun parseEc2QueryErrorResponse ([BLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun parseEc2QueryErrorResponseNoSuspend ([B)Laws/smithy/kotlin/runtime/awsprotocol/ErrorDetails; } public final class aws/smithy/kotlin/runtime/awsprotocol/xml/RestXmlErrorDeserializerKt { public static final fun parseRestXmlErrorResponse ([BLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun parseRestXmlErrorResponseNoSuspend ([B)Laws/smithy/kotlin/runtime/awsprotocol/ErrorDetails; } diff --git a/runtime/protocol/aws-xml-protocols/common/src/aws/smithy/kotlin/runtime/awsprotocol/xml/Ec2QueryErrorDeserializer.kt b/runtime/protocol/aws-xml-protocols/common/src/aws/smithy/kotlin/runtime/awsprotocol/xml/Ec2QueryErrorDeserializer.kt index fdf5916ec..63391e1b1 100644 --- a/runtime/protocol/aws-xml-protocols/common/src/aws/smithy/kotlin/runtime/awsprotocol/xml/Ec2QueryErrorDeserializer.kt +++ b/runtime/protocol/aws-xml-protocols/common/src/aws/smithy/kotlin/runtime/awsprotocol/xml/Ec2QueryErrorDeserializer.kt @@ -13,8 +13,12 @@ internal data class Ec2QueryErrorResponse(val errors: List, val r internal data class Ec2QueryError(val code: String?, val message: String?) +@Deprecated("use parseEc2QueryErrorResponseNoSuspend") @InternalApi -public fun parseEc2QueryErrorResponse(payload: ByteArray): ErrorDetails { +public suspend fun parseEc2QueryErrorResponse(payload: ByteArray): ErrorDetails = + parseEc2QueryErrorResponseNoSuspend(payload) + +public fun parseEc2QueryErrorResponseNoSuspend(payload: ByteArray): ErrorDetails { val response = Ec2QueryErrorResponseDeserializer.deserialize(xmlTagReader(payload)) val firstError = response.errors.firstOrNull() return ErrorDetails(firstError?.code, firstError?.message, response.requestId) diff --git a/runtime/protocol/aws-xml-protocols/common/src/aws/smithy/kotlin/runtime/awsprotocol/xml/RestXmlErrorDeserializer.kt b/runtime/protocol/aws-xml-protocols/common/src/aws/smithy/kotlin/runtime/awsprotocol/xml/RestXmlErrorDeserializer.kt index 8a2e56e9d..99925c0dc 100644 --- a/runtime/protocol/aws-xml-protocols/common/src/aws/smithy/kotlin/runtime/awsprotocol/xml/RestXmlErrorDeserializer.kt +++ b/runtime/protocol/aws-xml-protocols/common/src/aws/smithy/kotlin/runtime/awsprotocol/xml/RestXmlErrorDeserializer.kt @@ -32,8 +32,12 @@ internal data class XmlError( * * Returns parsed data in normalized form or throws [DeserializationException] if response cannot be parsed. */ +@Deprecated("use parseRestXmlErrorResponseNoSuspend") @InternalApi -public fun parseRestXmlErrorResponse(payload: ByteArray): ErrorDetails { +public suspend fun parseRestXmlErrorResponse(payload: ByteArray): ErrorDetails = + parseRestXmlErrorResponseNoSuspend(payload) + +public fun parseRestXmlErrorResponseNoSuspend(payload: ByteArray): ErrorDetails { val details = XmlErrorDeserializer.deserialize(xmlTagReader(payload)) return ErrorDetails(details.code, details.message, details.requestId) } diff --git a/runtime/protocol/aws-xml-protocols/common/test/aws/smithy/kotlin/runtime/awsprotocol/xml/Ec2QueryErrorDeserializerTest.kt b/runtime/protocol/aws-xml-protocols/common/test/aws/smithy/kotlin/runtime/awsprotocol/xml/Ec2QueryErrorDeserializerTest.kt index d2b4a6972..6dfb531a5 100644 --- a/runtime/protocol/aws-xml-protocols/common/test/aws/smithy/kotlin/runtime/awsprotocol/xml/Ec2QueryErrorDeserializerTest.kt +++ b/runtime/protocol/aws-xml-protocols/common/test/aws/smithy/kotlin/runtime/awsprotocol/xml/Ec2QueryErrorDeserializerTest.kt @@ -26,7 +26,7 @@ class Ec2QueryErrorDeserializerTest { foo-request """.trimIndent().encodeToByteArray() - val actual = parseEc2QueryErrorResponse(payload) + val actual = parseEc2QueryErrorResponseNoSuspend(payload) assertEquals("InvalidGreeting", actual.code) assertEquals("Hi", actual.message) assertEquals("foo-request", actual.requestId) @@ -61,7 +61,7 @@ class Ec2QueryErrorDeserializerTest { for (payload in tests) { assertFailsWith { - parseEc2QueryErrorResponse(payload) + parseEc2QueryErrorResponseNoSuspend(payload) } } } @@ -90,7 +90,7 @@ class Ec2QueryErrorDeserializerTest { ).map { it.trimIndent().encodeToByteArray() } for (payload in tests) { - val actual = parseEc2QueryErrorResponse(payload) + val actual = parseEc2QueryErrorResponseNoSuspend(payload) assertNull(actual.code) assertNull(actual.message) assertEquals("foo-request", actual.requestId) diff --git a/runtime/protocol/aws-xml-protocols/common/test/aws/smithy/kotlin/runtime/awsprotocol/xml/RestXmlErrorDeserializerTest.kt b/runtime/protocol/aws-xml-protocols/common/test/aws/smithy/kotlin/runtime/awsprotocol/xml/RestXmlErrorDeserializerTest.kt index 8aa487f1a..e091891b5 100644 --- a/runtime/protocol/aws-xml-protocols/common/test/aws/smithy/kotlin/runtime/awsprotocol/xml/RestXmlErrorDeserializerTest.kt +++ b/runtime/protocol/aws-xml-protocols/common/test/aws/smithy/kotlin/runtime/awsprotocol/xml/RestXmlErrorDeserializerTest.kt @@ -36,7 +36,7 @@ class RestXmlErrorDeserializerTest { ) for (payload in tests) { - val actual = parseRestXmlErrorResponse(payload) + val actual = parseRestXmlErrorResponseNoSuspend(payload) assertEquals("InvalidGreeting", actual.code) assertEquals("Hi", actual.message) assertEquals("foo-id", actual.requestId) @@ -70,7 +70,7 @@ class RestXmlErrorDeserializerTest { for (payload in tests) { assertFailsWith() { - parseRestXmlErrorResponse(payload) + parseRestXmlErrorResponseNoSuspend(payload) } } } @@ -92,7 +92,7 @@ class RestXmlErrorDeserializerTest { ) for (payload in tests) { - val error = parseRestXmlErrorResponse(payload) + val error = parseRestXmlErrorResponseNoSuspend(payload) assertEquals("foo-id", error.requestId) assertNull(error.code) assertNull(error.message) diff --git a/runtime/protocol/http-client/api/http-client.api b/runtime/protocol/http-client/api/http-client.api index 7c28406ec..9927ae030 100644 --- a/runtime/protocol/http-client/api/http-client.api +++ b/runtime/protocol/http-client/api/http-client.api @@ -470,6 +470,17 @@ public abstract interface class aws/smithy/kotlin/runtime/http/operation/HttpDes public abstract fun deserialize (Laws/smithy/kotlin/runtime/operation/ExecutionContext;Laws/smithy/kotlin/runtime/http/HttpCall;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } +public abstract interface class aws/smithy/kotlin/runtime/http/operation/HttpDeserializer { +} + +public abstract interface class aws/smithy/kotlin/runtime/http/operation/HttpDeserializer$NonStreaming : aws/smithy/kotlin/runtime/http/operation/HttpDeserializer { + public abstract fun deserialize (Laws/smithy/kotlin/runtime/operation/ExecutionContext;Laws/smithy/kotlin/runtime/http/HttpCall;[B)Ljava/lang/Object; +} + +public abstract interface class aws/smithy/kotlin/runtime/http/operation/HttpDeserializer$Streaming : aws/smithy/kotlin/runtime/http/operation/HttpDeserializer { + public abstract fun deserialize (Laws/smithy/kotlin/runtime/operation/ExecutionContext;Laws/smithy/kotlin/runtime/http/HttpCall;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + public final class aws/smithy/kotlin/runtime/http/operation/HttpOperationContext { public static final field INSTANCE Laws/smithy/kotlin/runtime/http/operation/HttpOperationContext; public final fun getChecksumAlgorithm ()Laws/smithy/kotlin/runtime/collections/AttributeKey; @@ -487,6 +498,17 @@ public abstract interface class aws/smithy/kotlin/runtime/http/operation/HttpSer public abstract fun serialize (Laws/smithy/kotlin/runtime/operation/ExecutionContext;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } +public abstract interface class aws/smithy/kotlin/runtime/http/operation/HttpSerializer { +} + +public abstract interface class aws/smithy/kotlin/runtime/http/operation/HttpSerializer$NonStreaming : aws/smithy/kotlin/runtime/http/operation/HttpSerializer { + public abstract fun serialize (Laws/smithy/kotlin/runtime/operation/ExecutionContext;Ljava/lang/Object;)Laws/smithy/kotlin/runtime/http/request/HttpRequestBuilder; +} + +public abstract interface class aws/smithy/kotlin/runtime/http/operation/HttpSerializer$Streaming : aws/smithy/kotlin/runtime/http/operation/HttpSerializer { + public abstract fun serialize (Laws/smithy/kotlin/runtime/operation/ExecutionContext;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + public final class aws/smithy/kotlin/runtime/http/operation/IdentityDeserializer : aws/smithy/kotlin/runtime/http/operation/HttpDeserialize { public static final field INSTANCE Laws/smithy/kotlin/runtime/http/operation/IdentityDeserializer; public fun deserialize (Laws/smithy/kotlin/runtime/operation/ExecutionContext;Laws/smithy/kotlin/runtime/http/HttpCall;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -621,16 +643,20 @@ public final class aws/smithy/kotlin/runtime/http/operation/SdkHttpOperationBuil public fun (Lkotlin/reflect/KClass;Lkotlin/reflect/KClass;)V public final fun build ()Laws/smithy/kotlin/runtime/http/operation/SdkHttpOperation; public final fun getContext ()Laws/smithy/kotlin/runtime/operation/ExecutionContext; + public final fun getDeserializeWith ()Laws/smithy/kotlin/runtime/http/operation/HttpDeserializer; public final fun getDeserializer ()Laws/smithy/kotlin/runtime/http/operation/HttpDeserialize; public final fun getExecution ()Laws/smithy/kotlin/runtime/http/operation/SdkOperationExecution; public final fun getHostPrefix ()Ljava/lang/String; public final fun getOperationName ()Ljava/lang/String; + public final fun getSerializeWith ()Laws/smithy/kotlin/runtime/http/operation/HttpSerializer; public final fun getSerializer ()Laws/smithy/kotlin/runtime/http/operation/HttpSerialize; public final fun getServiceName ()Ljava/lang/String; public final fun getTelemetry ()Laws/smithy/kotlin/runtime/http/operation/SdkOperationTelemetry; + public final fun setDeserializeWith (Laws/smithy/kotlin/runtime/http/operation/HttpDeserializer;)V public final fun setDeserializer (Laws/smithy/kotlin/runtime/http/operation/HttpDeserialize;)V public final fun setHostPrefix (Ljava/lang/String;)V public final fun setOperationName (Ljava/lang/String;)V + public final fun setSerializeWith (Laws/smithy/kotlin/runtime/http/operation/HttpSerializer;)V public final fun setSerializer (Laws/smithy/kotlin/runtime/http/operation/HttpSerialize;)V public final fun setServiceName (Ljava/lang/String;)V } diff --git a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/operation/HttpSerde.kt b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/operation/HttpSerde.kt index 394dd3182..5e50a3cca 100644 --- a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/operation/HttpSerde.kt +++ b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/operation/HttpSerde.kt @@ -66,10 +66,13 @@ public fun interface HttpSerialize { public suspend fun serialize(context: ExecutionContext, input: T): HttpRequestBuilder } +@Suppress("DEPRECATION") private class LegacyHttpSerializeAdapter(val serializer: HttpSerialize) : HttpSerializer.Streaming { override suspend fun serialize(context: ExecutionContext, input: T): HttpRequestBuilder = serializer.serialize(context, input) } + +@Suppress("DEPRECATION") internal fun HttpSerialize.intoSerializer(): HttpSerializer = LegacyHttpSerializeAdapter(this) /** @@ -81,16 +84,19 @@ public fun interface HttpDeserialize { public suspend fun deserialize(context: ExecutionContext, call: HttpCall): T } +@Suppress("DEPRECATION") private class LegacyHttpDeserializeAdapter(val deserializer: HttpDeserialize) : HttpDeserializer.Streaming { override suspend fun deserialize(context: ExecutionContext, call: HttpCall): T = deserializer.deserialize(context, call) } +@Suppress("DEPRECATION") internal fun HttpDeserialize.intoDeserializer(): HttpDeserializer = LegacyHttpDeserializeAdapter(this) /** * Convenience deserialize implementation for a type with no output type */ +@Suppress("DEPRECATION") @InternalApi public object UnitDeserializer : HttpDeserialize { override suspend fun deserialize(context: ExecutionContext, call: HttpCall) {} @@ -99,6 +105,7 @@ public object UnitDeserializer : HttpDeserialize { /** * Convenience serialize implementation for a type with no input type */ +@Suppress("DEPRECATION") @InternalApi public object UnitSerializer : HttpSerialize { override suspend fun serialize(context: ExecutionContext, input: Unit): HttpRequestBuilder = HttpRequestBuilder() @@ -107,6 +114,7 @@ public object UnitSerializer : HttpSerialize { /** * Convenience deserialize implementation that returns the response without modification */ +@Suppress("DEPRECATION") @InternalApi public object IdentityDeserializer : HttpDeserialize { override suspend fun deserialize(context: ExecutionContext, call: HttpCall): HttpResponse = call.response diff --git a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/operation/SdkHttpOperation.kt b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/operation/SdkHttpOperation.kt index 959c95565..ce7371296 100644 --- a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/operation/SdkHttpOperation.kt +++ b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/operation/SdkHttpOperation.kt @@ -37,6 +37,7 @@ public class SdkHttpOperation internal constructor( internal val telemetry: SdkOperationTelemetry, ) { + @Suppress("DEPRECATION") internal constructor( execution: SdkOperationExecution, context: ExecutionContext, @@ -132,6 +133,7 @@ public class SdkHttpOperationBuilder ( ) { public val telemetry: SdkOperationTelemetry = SdkOperationTelemetry() + @Suppress("DEPRECATION") @Deprecated("use serializeWith") public var serializer: HttpSerialize? = null set(value) { @@ -141,6 +143,7 @@ public class SdkHttpOperationBuilder ( public var serializeWith: HttpSerializer? = null + @Suppress("DEPRECATION") @Deprecated("use deserializeWith") public var deserializer: HttpDeserialize? = null set(value) { diff --git a/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/interceptors/FlexibleChecksumsResponseInterceptorTest.kt b/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/interceptors/FlexibleChecksumsResponseInterceptorTest.kt index 521db6cd9..2c04ee680 100644 --- a/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/interceptors/FlexibleChecksumsResponseInterceptorTest.kt +++ b/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/interceptors/FlexibleChecksumsResponseInterceptorTest.kt @@ -25,11 +25,11 @@ data class TestOutput(val body: HttpBody) inline fun newTestOperation(serialized: HttpRequestBuilder): SdkHttpOperation = SdkHttpOperation.build { - serializer = object : HttpSerialize { - override suspend fun serialize(context: ExecutionContext, input: I): HttpRequestBuilder = serialized + serializeWith = object : HttpSerializer.NonStreaming { + override fun serialize(context: ExecutionContext, input: I): HttpRequestBuilder = serialized } - deserializer = object : HttpDeserialize { + deserializeWith = object : HttpDeserializer.Streaming { override suspend fun deserialize(context: ExecutionContext, call: HttpCall): TestOutput = TestOutput(call.response.body) } diff --git a/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/interceptors/ResponseLengthValidationInterceptorTest.kt b/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/interceptors/ResponseLengthValidationInterceptorTest.kt index b48b47190..e9730f726 100644 --- a/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/interceptors/ResponseLengthValidationInterceptorTest.kt +++ b/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/interceptors/ResponseLengthValidationInterceptorTest.kt @@ -14,6 +14,7 @@ import aws.smithy.kotlin.runtime.io.EOFException import aws.smithy.kotlin.runtime.io.SdkByteReadChannel import aws.smithy.kotlin.runtime.io.SdkSource import aws.smithy.kotlin.runtime.io.source +import aws.smithy.kotlin.runtime.operation.ExecutionContext import aws.smithy.kotlin.runtime.time.Instant import kotlinx.coroutines.test.runTest import kotlin.test.* @@ -25,9 +26,21 @@ private const val CONTENT_LENGTH_HEADER_NAME = "Content-Length" private val RESPONSE = "a".repeat(500).encodeToByteArray() fun op() = - SdkHttpOperation.build { - serializer = HttpSerialize { _, _ -> HttpRequestBuilder() } - deserializer = HttpDeserialize { _, call -> ResponseLengthValidationTestOutput(call.response.body) } + SdkHttpOperation.build { + serializeWith = object : HttpSerializer.NonStreaming { + override fun serialize( + context: ExecutionContext, + input: ResponseLengthValidationTestInput, + ): HttpRequestBuilder = HttpRequestBuilder() + } + + deserializeWith = object : HttpDeserializer.Streaming { + override suspend fun deserialize( + context: ExecutionContext, + call: HttpCall, + ): ResponseLengthValidationTestOutput = ResponseLengthValidationTestOutput(call.response.body) + } + operationName = "TestOperation" serviceName = "TestService" }.also { diff --git a/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/operation/SdkHttpOperationTest.kt b/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/operation/SdkHttpOperationTest.kt index 751958204..22e36d1cf 100644 --- a/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/operation/SdkHttpOperationTest.kt +++ b/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/operation/SdkHttpOperationTest.kt @@ -34,6 +34,7 @@ class SdkHttpOperationTest { @Test fun testMissingRequiredProperties() = runTest { val ex = assertFailsWith { + @Suppress("DEPRECATION") SdkHttpOperation.build { serializer = UnitSerializer deserializer = UnitDeserializer diff --git a/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/operation/TestOperation.kt b/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/operation/TestOperation.kt index 362a38163..54e587079 100644 --- a/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/operation/TestOperation.kt +++ b/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/operation/TestOperation.kt @@ -8,7 +8,9 @@ package aws.smithy.kotlin.runtime.http.operation import aws.smithy.kotlin.runtime.ErrorMetadata import aws.smithy.kotlin.runtime.ServiceErrorMetadata import aws.smithy.kotlin.runtime.ServiceException +import aws.smithy.kotlin.runtime.http.HttpCall import aws.smithy.kotlin.runtime.http.request.HttpRequestBuilder +import aws.smithy.kotlin.runtime.operation.ExecutionContext /** * Create a new test operation using [serialized] as the already serialized version of the input type [I] @@ -16,8 +18,12 @@ import aws.smithy.kotlin.runtime.http.request.HttpRequestBuilder */ inline fun newTestOperation(serialized: HttpRequestBuilder, deserialized: O): SdkHttpOperation = SdkHttpOperation.build { - serializer = HttpSerialize { _, _ -> serialized } - deserializer = HttpDeserialize { _, _ -> deserialized } + serializeWith = object : HttpSerializer.NonStreaming { + override fun serialize(context: ExecutionContext, input: I): HttpRequestBuilder = serialized + } + deserializeWith = object : HttpDeserializer.NonStreaming { + override fun deserialize(context: ExecutionContext, call: HttpCall, payload: ByteArray?): O = deserialized + } // required operation context operationName = "TestOperation" diff --git a/runtime/protocol/http/common/src/aws/smithy/kotlin/runtime/http/request/HttpRequestBuilder.kt b/runtime/protocol/http/common/src/aws/smithy/kotlin/runtime/http/request/HttpRequestBuilder.kt index a75aa0957..e25d516ac 100644 --- a/runtime/protocol/http/common/src/aws/smithy/kotlin/runtime/http/request/HttpRequestBuilder.kt +++ b/runtime/protocol/http/common/src/aws/smithy/kotlin/runtime/http/request/HttpRequestBuilder.kt @@ -74,7 +74,7 @@ public inline fun HttpRequestBuilder.url(block: Url.Builder.() -> Unit) { /** * Set values from an existing [Url] instance */ -public inline fun HttpRequestBuilder.url(value: Url) { +public fun HttpRequestBuilder.url(value: Url) { url.copyFrom(value) } From 11307cce16e96256d43601d0a157b3c9d7372261 Mon Sep 17 00:00:00 2001 From: Aaron J Todd Date: Fri, 29 Mar 2024 12:21:29 -0400 Subject: [PATCH 4/4] fix docs --- .../codegen/rendering/protocol/HttpBindingProtocolGenerator.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGenerator.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGenerator.kt index a8d4c5de7..6d0e71e37 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGenerator.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGenerator.kt @@ -77,7 +77,7 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator { * The function should have the following signature: * * ``` - * fun throwFooOperationError(context: ExecutionContext, call: HttpCall, payload: HttpByteArray?): Nothing { + * fun throwFooOperationError(context: ExecutionContext, call: HttpCall, payload: ByteArray?): Nothing { * <-- CURRENT WRITER CONTEXT --> * } * ```