diff --git a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/AwsJson1_0.kt b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/AwsJson1_0.kt index c2d1a51d28..1b1c7246e4 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/AwsJson1_0.kt +++ b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/AwsJson1_0.kt @@ -5,7 +5,6 @@ package software.amazon.smithy.kotlin.codegen.aws.protocols import software.amazon.smithy.aws.traits.protocols.AwsJson1_0Trait -import software.amazon.smithy.kotlin.codegen.aws.protocols.core.AwsHttpBindingProtocolGenerator import software.amazon.smithy.kotlin.codegen.aws.protocols.json.AwsJsonHttpBindingResolver import software.amazon.smithy.kotlin.codegen.aws.protocols.json.AwsJsonProtocolMiddleware import software.amazon.smithy.kotlin.codegen.aws.protocols.json.AwsJsonProtocolParserGenerator @@ -22,7 +21,7 @@ import software.amazon.smithy.model.shapes.ShapeId * Handles generating the aws.protocols#awsJson1_0 protocol for services. * * @inheritDoc - * @see AwsHttpBindingProtocolGenerator + * @see HttpBindingProtocolGenerator */ @Suppress("ktlint:standard:class-naming") class AwsJson1_0 : JsonHttpBindingProtocolGenerator() { diff --git a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/AwsJson1_1.kt b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/AwsJson1_1.kt index e8d829ac0a..bf9b2fcee1 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/AwsJson1_1.kt +++ b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/AwsJson1_1.kt @@ -6,7 +6,6 @@ package software.amazon.smithy.kotlin.codegen.aws.protocols import software.amazon.smithy.aws.traits.protocols.AwsJson1_1Trait -import software.amazon.smithy.kotlin.codegen.aws.protocols.core.AwsHttpBindingProtocolGenerator import software.amazon.smithy.kotlin.codegen.aws.protocols.json.AwsJsonHttpBindingResolver import software.amazon.smithy.kotlin.codegen.aws.protocols.json.AwsJsonProtocolMiddleware import software.amazon.smithy.kotlin.codegen.aws.protocols.json.AwsJsonProtocolParserGenerator @@ -23,7 +22,7 @@ import software.amazon.smithy.model.shapes.ShapeId * Handles generating the aws.protocols#awsJson1_1 protocol for services. * * @inheritDoc - * @see AwsHttpBindingProtocolGenerator + * @see HttpBindingProtocolGenerator */ @Suppress("ktlint:standard:class-naming") class AwsJson1_1 : JsonHttpBindingProtocolGenerator() { 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 853ed38a55..aab0ee871c 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 @@ -8,7 +8,6 @@ package software.amazon.smithy.kotlin.codegen.aws.protocols import software.amazon.smithy.aws.traits.protocols.AwsQueryErrorTrait import software.amazon.smithy.aws.traits.protocols.AwsQueryTrait import software.amazon.smithy.kotlin.codegen.aws.protocols.core.AbstractQueryFormUrlSerializerGenerator -import software.amazon.smithy.kotlin.codegen.aws.protocols.core.AwsHttpBindingProtocolGenerator import software.amazon.smithy.kotlin.codegen.aws.protocols.core.QueryHttpBindingProtocolGenerator import software.amazon.smithy.kotlin.codegen.aws.protocols.formurl.QuerySerdeFormUrlDescriptorGenerator import software.amazon.smithy.kotlin.codegen.core.KotlinWriter @@ -33,7 +32,7 @@ import software.amazon.smithy.model.traits.XmlNameTrait * Handles generating the aws.protocols#awsQuery protocol for services. * * @inheritDoc - * @see AwsHttpBindingProtocolGenerator + * @see HttpBindingProtocolGenerator */ class AwsQuery : QueryHttpBindingProtocolGenerator() { override val protocol: ShapeId = AwsQueryTrait.ID diff --git a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/RestJson1.kt b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/RestJson1.kt index dcf2f0a814..4c97a05980 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/RestJson1.kt +++ b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/RestJson1.kt @@ -5,7 +5,6 @@ package software.amazon.smithy.kotlin.codegen.aws.protocols import software.amazon.smithy.aws.traits.protocols.RestJson1Trait -import software.amazon.smithy.kotlin.codegen.aws.protocols.core.AwsHttpBindingProtocolGenerator import software.amazon.smithy.kotlin.codegen.aws.protocols.json.AwsJsonProtocolParserGenerator import software.amazon.smithy.kotlin.codegen.aws.protocols.json.JsonHttpBindingProtocolGenerator import software.amazon.smithy.kotlin.codegen.core.KotlinWriter @@ -25,7 +24,7 @@ import software.amazon.smithy.model.shapes.StructureShape * Handles generating the aws.protocols#restJson1 protocol for services. * * @inheritDoc - * @see AwsHttpBindingProtocolGenerator + * @see HttpBindingProtocolGenerator */ class RestJson1 : JsonHttpBindingProtocolGenerator() { 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 c7c834c317..185fa022a0 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 @@ -7,7 +7,6 @@ package software.amazon.smithy.kotlin.codegen.aws.protocols import software.amazon.smithy.aws.traits.protocols.RestXmlTrait import software.amazon.smithy.codegen.core.Symbol -import software.amazon.smithy.kotlin.codegen.aws.protocols.core.AwsHttpBindingProtocolGenerator import software.amazon.smithy.kotlin.codegen.aws.protocols.xml.RestXmlSerdeDescriptorGenerator import software.amazon.smithy.kotlin.codegen.core.KotlinWriter import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes @@ -26,9 +25,9 @@ import kotlin.contracts.contract * Handles generating the aws.protocols#restJson1 protocol for services. * * @inheritDoc - * @see AwsHttpBindingProtocolGenerator + * @see HttpBindingProtocolGenerator */ -open class RestXml : AwsHttpBindingProtocolGenerator() { +open class RestXml : HttpBindingProtocolGenerator() { override val protocol: ShapeId = RestXmlTrait.ID override val defaultTimestampFormat: TimestampFormatTrait.Format = TimestampFormatTrait.Format.DATE_TIME diff --git a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/RpcV2Cbor.kt b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/RpcV2Cbor.kt index 117574da40..ff580c9082 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/RpcV2Cbor.kt +++ b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/RpcV2Cbor.kt @@ -4,12 +4,11 @@ */ package software.amazon.smithy.kotlin.codegen.aws.protocols -import software.amazon.smithy.kotlin.codegen.aws.protocols.core.AwsHttpBindingProtocolGenerator -import software.amazon.smithy.kotlin.codegen.aws.protocols.core.StaticHttpBindingResolver import software.amazon.smithy.kotlin.codegen.core.KotlinWriter import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes import software.amazon.smithy.kotlin.codegen.model.* import software.amazon.smithy.kotlin.codegen.model.traits.SyntheticClone +import software.amazon.smithy.kotlin.codegen.protocols.core.StaticHttpBindingResolver import software.amazon.smithy.kotlin.codegen.rendering.protocol.* import software.amazon.smithy.kotlin.codegen.rendering.serde.CborParserGenerator import software.amazon.smithy.kotlin.codegen.rendering.serde.CborSerializerGenerator @@ -29,7 +28,7 @@ import software.amazon.smithy.protocol.traits.Rpcv2CborTrait private const val ACCEPT_HEADER = "application/cbor" private const val ACCEPT_HEADER_EVENT_STREAM = "application/vnd.amazon.eventstream" -class RpcV2Cbor : AwsHttpBindingProtocolGenerator() { +class RpcV2Cbor : HttpBindingProtocolGenerator() { override val protocol: ShapeId = Rpcv2CborTrait.ID // TODO Timestamp format is not used in RpcV2Cbor since it's a binary protocol. We seem to be missing an abstraction 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 deleted file mode 100644 index 5ed2453f14..0000000000 --- a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/core/AwsHttpBindingProtocolGenerator.kt +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -package software.amazon.smithy.kotlin.codegen.aws.protocols.core - -import software.amazon.smithy.aws.traits.protocols.AwsQueryCompatibleTrait -import software.amazon.smithy.codegen.core.Symbol -import software.amazon.smithy.kotlin.codegen.aws.protocols.eventstream.EventStreamParserGenerator -import software.amazon.smithy.kotlin.codegen.aws.protocols.eventstream.EventStreamSerializerGenerator -import software.amazon.smithy.kotlin.codegen.core.* -import software.amazon.smithy.kotlin.codegen.integration.SectionId -import software.amazon.smithy.kotlin.codegen.integration.SectionKey -import software.amazon.smithy.kotlin.codegen.lang.KotlinTypes -import software.amazon.smithy.kotlin.codegen.model.buildSymbol -import software.amazon.smithy.kotlin.codegen.model.hasTrait -import software.amazon.smithy.kotlin.codegen.rendering.ExceptionBaseClassGenerator -import software.amazon.smithy.kotlin.codegen.rendering.protocol.* -import software.amazon.smithy.model.shapes.OperationShape -import software.amazon.smithy.model.shapes.ShapeId - -/** - * Base class for all AWS HTTP protocol generators - */ -abstract class AwsHttpBindingProtocolGenerator : HttpBindingProtocolGenerator() { - - object Sections { - object ProtocolErrorDeserialization : SectionId { - val Operation = SectionKey("Operation") - } - - object RenderThrowOperationError : SectionId { - val Context = SectionKey("Context") - val Operation = SectionKey("Operation") - } - } - - override fun generateProtocolUnitTests(ctx: ProtocolGenerator.GenerationContext) { - // The following can be used to generate only a specific test by name. - // val targetedTest = TestMemberDelta(setOf("RestJsonComplexErrorWithNoMessage"), TestContainmentMode.RUN_TESTS) - - val ignoredTests = TestMemberDelta( - setOf( - // likely bug in Smithy's HTTP header traits spec - "RestJsonHttpEmptyPrefixHeadersRequestClient", - "HttpEmptyPrefixHeadersRequestClient", - ), - ) - - val requestTestBuilder = HttpProtocolUnitTestRequestGenerator.Builder() - val responseTestBuilder = HttpProtocolUnitTestResponseGenerator.Builder() - val errorTestBuilder = HttpProtocolUnitTestErrorGenerator.Builder() - - HttpProtocolTestGenerator( - ctx, - requestTestBuilder, - responseTestBuilder, - errorTestBuilder, - ignoredTests, - ).generateProtocolTests() - } - - /** - * Get the error "code" that uniquely identifies the AWS error. - */ - protected open fun getErrorCode(ctx: ProtocolGenerator.GenerationContext, errShapeId: ShapeId): String = errShapeId.name - - /** - * Render the code to parse the `ErrorDetails` from the HTTP response. - */ - abstract fun renderDeserializeErrorDetails(ctx: ProtocolGenerator.GenerationContext, op: OperationShape, writer: KotlinWriter) - - override fun eventStreamRequestHandler(ctx: ProtocolGenerator.GenerationContext, op: OperationShape): Symbol { - val resolver = getProtocolHttpBindingResolver(ctx.model, ctx.service) - val contentType = resolver.determineRequestContentType(op) ?: error("event streams must set a content-type") - val eventStreamSerializerGenerator = EventStreamSerializerGenerator(structuredDataSerializer(ctx), contentType) - return eventStreamSerializerGenerator.requestHandler(ctx, op) - } - - override fun eventStreamResponseHandler(ctx: ProtocolGenerator.GenerationContext, op: OperationShape): Symbol { - val eventStreamParserGenerator = EventStreamParserGenerator(ctx, structuredDataParser(ctx)) - return eventStreamParserGenerator.responseHandler(ctx, op) - } - - override fun operationErrorHandler(ctx: ProtocolGenerator.GenerationContext, op: OperationShape): Symbol = - op.errorHandler(ctx.settings) { writer -> - writer.withBlock( - "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) - } - } - - @Suppress("DEPRECATION") - private fun renderThrowOperationError( - ctx: ProtocolGenerator.GenerationContext, - op: OperationShape, - writer: KotlinWriter, - ) { - writer.declareSection( - Sections.RenderThrowOperationError, - mapOf( - Sections.RenderThrowOperationError.Context to ctx, - Sections.RenderThrowOperationError.Operation to op, - ), - ) { - val exceptionBaseSymbol = ExceptionBaseClassGenerator.baseExceptionSymbol(ctx.settings) - writer.write("val wrappedResponse = call.response.#T(payload)", RuntimeTypes.AwsProtocolCore.withPayload) - .write("val wrappedCall = call.copy(response = wrappedResponse)") - .write("") - .declareSection( - Sections.ProtocolErrorDeserialization, - mapOf( - Sections.ProtocolErrorDeserialization.Operation to op, - ), - ) - .write("val errorDetails = try {") - .indent() - .call { - renderDeserializeErrorDetails(ctx, op, writer) - } - .dedent() - .withBlock("} catch (ex: Exception) {", "}") { - withBlock("""throw #T("Failed to parse response as '${ctx.protocol.name}' error", ex).also {""", "}", exceptionBaseSymbol) { - write("#T(it, wrappedCall.response, null)", RuntimeTypes.AwsProtocolCore.setAseErrorMetadata) - } - } - .write("") - - if (ctx.service.hasTrait()) { - writer.write("var queryErrorDetails: #T? = null", RuntimeTypes.AwsProtocolCore.AwsQueryCompatibleErrorDetails) - writer.withBlock("call.response.headers[#T]?.let {", "}", RuntimeTypes.AwsProtocolCore.XAmznQueryErrorHeader) { - openBlock("queryErrorDetails = try {") - write("#T.parse(it)", RuntimeTypes.AwsProtocolCore.AwsQueryCompatibleErrorDetails) - closeAndOpenBlock("} catch (ex: Exception) {") - withBlock("""throw #T("Failed to parse awsQuery-compatible error", ex).also {""", "}", exceptionBaseSymbol) { - write("#T(it, wrappedResponse, errorDetails)", RuntimeTypes.AwsProtocolCore.setAseErrorMetadata) - } - closeBlock("}") - } - writer.write("") - } - - writer.withBlock("val ex = when(errorDetails.code) {", "}") { - op.errors.forEach { err -> - val errSymbol = ctx.symbolProvider.toSymbol(ctx.model.expectShape(err)) - val errDeserializerSymbol = buildSymbol { - name = "${errSymbol.name}Deserializer" - namespace = ctx.settings.pkg.serde - } - writer.write("#S -> #T().deserialize(context, wrappedCall, payload)", getErrorCode(ctx, err), errDeserializerSymbol) - } - write("else -> #T(errorDetails.message)", exceptionBaseSymbol) - } - - writer.write("") - writer.write("#T(ex, wrappedResponse, errorDetails)", RuntimeTypes.AwsProtocolCore.setAseErrorMetadata) - if (ctx.service.hasTrait()) { - writer.write("queryErrorDetails?.let { #T(ex, it) }", RuntimeTypes.AwsProtocolCore.setAwsQueryCompatibleErrorMetadata) - } - - writer.write("throw ex") - } - } -} diff --git a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/core/QueryHttpBindingProtocolGenerator.kt b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/core/QueryHttpBindingProtocolGenerator.kt index db97349f84..a5c5ac7a95 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/core/QueryHttpBindingProtocolGenerator.kt +++ b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/core/QueryHttpBindingProtocolGenerator.kt @@ -11,6 +11,8 @@ import software.amazon.smithy.kotlin.codegen.core.withBlock import software.amazon.smithy.kotlin.codegen.model.expectShape import software.amazon.smithy.kotlin.codegen.model.knowledge.SerdeIndex import software.amazon.smithy.kotlin.codegen.model.targetOrSelf +import software.amazon.smithy.kotlin.codegen.protocols.core.StaticHttpBindingResolver +import software.amazon.smithy.kotlin.codegen.rendering.protocol.HttpBindingProtocolGenerator import software.amazon.smithy.kotlin.codegen.rendering.protocol.HttpBindingResolver import software.amazon.smithy.kotlin.codegen.rendering.protocol.MutateHeadersMiddleware import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolGenerator @@ -24,7 +26,7 @@ import software.amazon.smithy.model.traits.TimestampFormatTrait private const val QUERY_CONTENT_TYPE: String = "application/x-www-form-urlencoded" -abstract class QueryHttpBindingProtocolGenerator : AwsHttpBindingProtocolGenerator() { +abstract class QueryHttpBindingProtocolGenerator : HttpBindingProtocolGenerator() { override val defaultTimestampFormat: TimestampFormatTrait.Format = TimestampFormatTrait.Format.DATE_TIME override fun getDefaultHttpMiddleware(ctx: ProtocolGenerator.GenerationContext): List { diff --git a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/json/AwsJsonHttpBindingResolver.kt b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/json/AwsJsonHttpBindingResolver.kt index eba5e59078..271d9c9a49 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/json/AwsJsonHttpBindingResolver.kt +++ b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/json/AwsJsonHttpBindingResolver.kt @@ -5,7 +5,7 @@ package software.amazon.smithy.kotlin.codegen.aws.protocols.json -import software.amazon.smithy.kotlin.codegen.aws.protocols.core.StaticHttpBindingResolver +import software.amazon.smithy.kotlin.codegen.protocols.core.StaticHttpBindingResolver import software.amazon.smithy.model.Model import software.amazon.smithy.model.pattern.UriPattern import software.amazon.smithy.model.shapes.ServiceShape diff --git a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/json/JsonHttpBindingProtocolGenerator.kt b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/json/JsonHttpBindingProtocolGenerator.kt index 7e82faae3d..0857e4a9b1 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/json/JsonHttpBindingProtocolGenerator.kt +++ b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/json/JsonHttpBindingProtocolGenerator.kt @@ -5,9 +5,9 @@ package software.amazon.smithy.kotlin.codegen.aws.protocols.json -import software.amazon.smithy.kotlin.codegen.aws.protocols.core.AwsHttpBindingProtocolGenerator import software.amazon.smithy.kotlin.codegen.core.KotlinWriter import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes +import software.amazon.smithy.kotlin.codegen.rendering.protocol.HttpBindingProtocolGenerator import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolGenerator import software.amazon.smithy.kotlin.codegen.rendering.serde.* import software.amazon.smithy.model.shapes.OperationShape @@ -16,7 +16,7 @@ import software.amazon.smithy.model.traits.TimestampFormatTrait /** * Abstract base class that all protocols using JSON as a document format can inherit from */ -abstract class JsonHttpBindingProtocolGenerator : AwsHttpBindingProtocolGenerator() { +abstract class JsonHttpBindingProtocolGenerator : HttpBindingProtocolGenerator() { override val defaultTimestampFormat: TimestampFormatTrait.Format = TimestampFormatTrait.Format.EPOCH_SECONDS diff --git a/codegen/smithy-aws-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/RpcV2CborTest.kt b/codegen/smithy-aws-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/RpcV2CborTest.kt index a52456d61a..4cd86311bd 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/RpcV2CborTest.kt +++ b/codegen/smithy-aws-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/RpcV2CborTest.kt @@ -2,10 +2,15 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ + package software.amazon.smithy.kotlin.codegen.aws.protocols import io.kotest.matchers.string.shouldNotContain -import software.amazon.smithy.kotlin.codegen.test.* +import software.amazon.smithy.kotlin.codegen.test.lines +import software.amazon.smithy.kotlin.codegen.test.newTestContext +import software.amazon.smithy.kotlin.codegen.test.shouldContainOnlyOnceWithDiff +import software.amazon.smithy.kotlin.codegen.test.shouldNotContainOnlyOnceWithDiff +import software.amazon.smithy.kotlin.codegen.test.toSmithyModel import kotlin.test.Test class RpcV2CborTest { diff --git a/codegen/smithy-aws-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/core/AwsHttpBindingProtocolGeneratorTest.kt b/codegen/smithy-aws-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/core/AwsHttpBindingProtocolGeneratorTest.kt index a0bcd03bad..8d0c5ea52e 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/core/AwsHttpBindingProtocolGeneratorTest.kt +++ b/codegen/smithy-aws-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/core/AwsHttpBindingProtocolGeneratorTest.kt @@ -8,6 +8,7 @@ import software.amazon.smithy.codegen.core.Symbol import software.amazon.smithy.kotlin.codegen.aws.protocols.json.AwsJsonHttpBindingResolver import software.amazon.smithy.kotlin.codegen.core.KotlinWriter import software.amazon.smithy.kotlin.codegen.model.expectShape +import software.amazon.smithy.kotlin.codegen.rendering.protocol.HttpBindingProtocolGenerator import software.amazon.smithy.kotlin.codegen.rendering.protocol.HttpBindingResolver import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolGenerator import software.amazon.smithy.kotlin.codegen.rendering.serde.* @@ -157,7 +158,7 @@ class AwsHttpBindingProtocolGeneratorTest { * renderThrowOperationError() * getProtocolHttpBindingResolver() */ - class TestableAwsHttpBindingProtocolGenerator : AwsHttpBindingProtocolGenerator() { + class TestableAwsHttpBindingProtocolGenerator : HttpBindingProtocolGenerator() { override fun renderDeserializeErrorDetails( ctx: ProtocolGenerator.GenerationContext, op: OperationShape, diff --git a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/core/StaticHttpBindingResolver.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/protocols/core/StaticHttpBindingResolver.kt similarity index 98% rename from codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/core/StaticHttpBindingResolver.kt rename to codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/protocols/core/StaticHttpBindingResolver.kt index f549d0ed4c..8e2c898aa1 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/core/StaticHttpBindingResolver.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/protocols/core/StaticHttpBindingResolver.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package software.amazon.smithy.kotlin.codegen.aws.protocols.core +package software.amazon.smithy.kotlin.codegen.protocols.core import software.amazon.smithy.kotlin.codegen.model.expectTrait import software.amazon.smithy.kotlin.codegen.model.hasTrait diff --git a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/eventstream/EventStreamParserGenerator.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/protocols/eventstream/EventStreamParserGenerator.kt similarity index 99% rename from codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/eventstream/EventStreamParserGenerator.kt rename to codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/protocols/eventstream/EventStreamParserGenerator.kt index acc14a044a..8c28c8b6b9 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/eventstream/EventStreamParserGenerator.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/protocols/eventstream/EventStreamParserGenerator.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package software.amazon.smithy.kotlin.codegen.aws.protocols.eventstream +package software.amazon.smithy.kotlin.codegen.protocols.eventstream import software.amazon.smithy.codegen.core.CodegenException import software.amazon.smithy.codegen.core.Symbol diff --git a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/eventstream/EventStreamSerializerGenerator.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/protocols/eventstream/EventStreamSerializerGenerator.kt similarity index 99% rename from codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/eventstream/EventStreamSerializerGenerator.kt rename to codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/protocols/eventstream/EventStreamSerializerGenerator.kt index d7e721e928..fd87d10819 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/eventstream/EventStreamSerializerGenerator.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/protocols/eventstream/EventStreamSerializerGenerator.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package software.amazon.smithy.kotlin.codegen.aws.protocols.eventstream +package software.amazon.smithy.kotlin.codegen.protocols.eventstream import software.amazon.smithy.codegen.core.CodegenException import software.amazon.smithy.codegen.core.Symbol 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 6ad33d511d..36720475dd 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 @@ -4,14 +4,21 @@ */ package software.amazon.smithy.kotlin.codegen.rendering.protocol +import software.amazon.smithy.aws.traits.protocols.AwsQueryCompatibleTrait import software.amazon.smithy.codegen.core.CodegenException import software.amazon.smithy.codegen.core.Symbol import software.amazon.smithy.codegen.core.SymbolReference import software.amazon.smithy.kotlin.codegen.KotlinSettings import software.amazon.smithy.kotlin.codegen.core.* +import software.amazon.smithy.kotlin.codegen.integration.SectionId +import software.amazon.smithy.kotlin.codegen.integration.SectionKey import software.amazon.smithy.kotlin.codegen.lang.KotlinTypes import software.amazon.smithy.kotlin.codegen.lang.toEscapedLiteral import software.amazon.smithy.kotlin.codegen.model.* +import software.amazon.smithy.kotlin.codegen.model.hasTrait +import software.amazon.smithy.kotlin.codegen.protocols.eventstream.EventStreamParserGenerator +import software.amazon.smithy.kotlin.codegen.protocols.eventstream.EventStreamSerializerGenerator +import software.amazon.smithy.kotlin.codegen.rendering.ExceptionBaseClassGenerator import software.amazon.smithy.kotlin.codegen.rendering.serde.deserializerName import software.amazon.smithy.kotlin.codegen.rendering.serde.formatInstant import software.amazon.smithy.kotlin.codegen.rendering.serde.parseInstantExpr @@ -27,6 +34,18 @@ import java.util.logging.Logger * Abstract implementation useful for all HTTP protocols */ abstract class HttpBindingProtocolGenerator : ProtocolGenerator { + + object Sections { + object ProtocolErrorDeserialization : SectionId { + val Operation = SectionKey("Operation") + } + + object RenderThrowOperationError : SectionId { + val Context = SectionKey("Context") + val Operation = SectionKey("Operation") + } + } + private val logger = Logger.getLogger(javaClass.name) override val applicationProtocol: ApplicationProtocol = ApplicationProtocol.createDefaultHttpApplicationProtocol() @@ -88,7 +107,99 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator { * @param ctx the protocol generator context * @param op the operation shape to render error matching */ - abstract fun operationErrorHandler(ctx: ProtocolGenerator.GenerationContext, op: OperationShape): Symbol + open fun operationErrorHandler(ctx: ProtocolGenerator.GenerationContext, op: OperationShape): Symbol = + op.errorHandler(ctx.settings) { writer -> + writer.withBlock( + "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) + } + } + + @Suppress("DEPRECATION") + open fun renderThrowOperationError(ctx: ProtocolGenerator.GenerationContext, op: OperationShape, writer: KotlinWriter) { + writer.declareSection( + Sections.RenderThrowOperationError, + mapOf( + Sections.RenderThrowOperationError.Context to ctx, + Sections.RenderThrowOperationError.Operation to op, + ), + ) { + val exceptionBaseSymbol = ExceptionBaseClassGenerator.baseExceptionSymbol(ctx.settings) + writer.write("val wrappedResponse = call.response.#T(payload)", RuntimeTypes.AwsProtocolCore.withPayload) + .write("val wrappedCall = call.copy(response = wrappedResponse)") + .write("") + .declareSection( + Sections.ProtocolErrorDeserialization, + mapOf( + Sections.ProtocolErrorDeserialization.Operation to op, + ), + ) + .write("val errorDetails = try {") + .indent() + .call { + renderDeserializeErrorDetails(ctx, op, writer) + } + .dedent() + .withBlock("} catch (ex: Exception) {", "}") { + withBlock("""throw #T("Failed to parse response as '${ctx.protocol.name}' error", ex).also {""", "}", exceptionBaseSymbol) { + write("#T(it, wrappedCall.response, null)", RuntimeTypes.AwsProtocolCore.setAseErrorMetadata) + } + } + .write("") + + if (ctx.service.hasTrait()) { + writer.write("var queryErrorDetails: #T? = null", RuntimeTypes.AwsProtocolCore.AwsQueryCompatibleErrorDetails) + writer.withBlock("call.response.headers[#T]?.let {", "}", RuntimeTypes.AwsProtocolCore.XAmznQueryErrorHeader) { + openBlock("queryErrorDetails = try {") + write("#T.parse(it)", RuntimeTypes.AwsProtocolCore.AwsQueryCompatibleErrorDetails) + closeAndOpenBlock("} catch (ex: Exception) {") + withBlock("""throw #T("Failed to parse awsQuery-compatible error", ex).also {""", "}", exceptionBaseSymbol) { + write("#T(it, wrappedResponse, errorDetails)", RuntimeTypes.AwsProtocolCore.setAseErrorMetadata) + } + closeBlock("}") + } + writer.write("") + } + + writer.withBlock("val ex = when(errorDetails.code) {", "}") { + op.errors.forEach { err -> + val errSymbol = ctx.symbolProvider.toSymbol(ctx.model.expectShape(err)) + val errDeserializerSymbol = buildSymbol { + name = "${errSymbol.name}Deserializer" + namespace = ctx.settings.pkg.serde + } + writer.write("#S -> #T().deserialize(context, wrappedCall, payload)", getErrorCode(ctx, err), errDeserializerSymbol) + } + write("else -> #T(errorDetails.message)", exceptionBaseSymbol) + } + + writer.write("") + writer.write("#T(ex, wrappedResponse, errorDetails)", RuntimeTypes.AwsProtocolCore.setAseErrorMetadata) + if (ctx.service.hasTrait()) { + writer.write("queryErrorDetails?.let { #T(ex, it) }", RuntimeTypes.AwsProtocolCore.setAwsQueryCompatibleErrorMetadata) + } + + writer.write("throw ex") + } + } + + /** + * Render the code to parse the `ErrorDetails` from the HTTP response. + */ + open fun renderDeserializeErrorDetails(ctx: ProtocolGenerator.GenerationContext, op: OperationShape, writer: KotlinWriter) { + writer.write("#T.deserialize(payload)", RuntimeTypes.SmithyRpcV2Protocols.Cbor.RpcV2CborErrorDeserializer) + } + + /** + * Get the error "code" that uniquely identifies the AWS error. + */ + open fun getErrorCode(ctx: ProtocolGenerator.GenerationContext, errShapeId: ShapeId): String = errShapeId.name // FIXME - probably make abstract and let individual protocols throw if they don't support event stream bindings @@ -100,7 +211,12 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator { * @param ctx the protocol generator context * @param op the operation shape to return event stream serializer for */ - open fun eventStreamRequestHandler(ctx: ProtocolGenerator.GenerationContext, op: OperationShape): Symbol = error("event streams are not supported by $this") + open fun eventStreamRequestHandler(ctx: ProtocolGenerator.GenerationContext, op: OperationShape): Symbol { + val resolver = getProtocolHttpBindingResolver(ctx.model, ctx.service) + val contentType = resolver.determineRequestContentType(op) ?: error("event streams must set a content-type") + val eventStreamSerializerGenerator = EventStreamSerializerGenerator(structuredDataSerializer(ctx), contentType) + return eventStreamSerializerGenerator.requestHandler(ctx, op) + } /** * @@ -111,7 +227,10 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator { * @param ctx the protocol generator context * @param op the operation shape to return event stream deserializer for */ - open fun eventStreamResponseHandler(ctx: ProtocolGenerator.GenerationContext, op: OperationShape): Symbol = error("event streams are not supported by $this") + open fun eventStreamResponseHandler(ctx: ProtocolGenerator.GenerationContext, op: OperationShape): Symbol { + val eventStreamParserGenerator = EventStreamParserGenerator(ctx, structuredDataParser(ctx)) + return eventStreamParserGenerator.responseHandler(ctx, op) + } private fun generateSerializers(ctx: ProtocolGenerator.GenerationContext) { val resolver = getProtocolHttpBindingResolver(ctx.model, ctx.service) @@ -146,6 +265,31 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator { generateDeserializers(ctx) } + override fun generateProtocolUnitTests(ctx: ProtocolGenerator.GenerationContext) { + // The following can be used to generate only a specific test by name. + // val targetedTest = TestMemberDelta(setOf("RestJsonComplexErrorWithNoMessage"), TestContainmentMode.RUN_TESTS) + + val ignoredTests = TestMemberDelta( + setOf( + // likely bug in Smithy's HTTP header traits spec + "RestJsonHttpEmptyPrefixHeadersRequestClient", + "HttpEmptyPrefixHeadersRequestClient", + ), + ) + + val requestTestBuilder = HttpProtocolUnitTestRequestGenerator.Builder() + val responseTestBuilder = HttpProtocolUnitTestResponseGenerator.Builder() + val errorTestBuilder = HttpProtocolUnitTestErrorGenerator.Builder() + + HttpProtocolTestGenerator( + ctx, + requestTestBuilder, + responseTestBuilder, + errorTestBuilder, + ignoredTests, + ).generateProtocolTests() + } + /** * Generate request serializer (HttpSerialize) for an operation */