diff --git a/.changes/119ee420-38a5-4974-922e-29cb11de02d0.json b/.changes/119ee420-38a5-4974-922e-29cb11de02d0.json new file mode 100644 index 00000000000..6b02874fee1 --- /dev/null +++ b/.changes/119ee420-38a5-4974-922e-29cb11de02d0.json @@ -0,0 +1,8 @@ +{ + "id": "119ee420-38a5-4974-922e-29cb11de02d0", + "type": "bugfix", + "description": "Refactor XML deserialization to handle flat collections", + "issues": [ + "awslabs/aws-sdk-kotlin#1220" + ] +} \ No newline at end of file diff --git a/codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/customization/s3/UnwrappedXmlOutputIntegration.kt b/codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/customization/s3/UnwrappedXmlOutputIntegration.kt index d51f70624f0..42cca196eb6 100644 --- a/codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/customization/s3/UnwrappedXmlOutputIntegration.kt +++ b/codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/customization/s3/UnwrappedXmlOutputIntegration.kt @@ -11,31 +11,32 @@ import software.amazon.smithy.kotlin.codegen.model.expectShape import software.amazon.smithy.kotlin.codegen.model.hasTrait import software.amazon.smithy.kotlin.codegen.model.traits.UnwrappedXmlOutput import software.amazon.smithy.model.Model +import software.amazon.smithy.model.shapes.OperationShape import software.amazon.smithy.model.shapes.ServiceShape -import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.model.transform.ModelTransformer /** - * Applies the [UnwrappedXmlOutput] custom-made [annotation trait](https://smithy.io/2.0/spec/model.html?highlight=annotation#annotation-traits) to structures - * whose operation is annotated with `S3UnwrappedXmlOutput` trait to mark when special unwrapped xml output deserialization is required. + * Applies the custom [UnwrappedXmlOutput] + * [annotation trait](https://smithy.io/2.0/spec/model.html?highlight=annotation#annotation-traits) to operations + * annotated with `S3UnwrappedXmlOutput` trait to mark when special unwrapped xml output deserialization is required. */ class UnwrappedXmlOutputIntegration : KotlinIntegration { override fun enabledForService(model: Model, settings: KotlinSettings): Boolean = model.expectShape(settings.service).isS3 override fun preprocessModel(model: Model, settings: KotlinSettings): Model { - val unwrappedStructures = model + val unwrappedOperations = model .operationShapes .filter { it.hasTrait() } - .map { it.outputShape } + .map { it.id } .toSet() return ModelTransformer .create() .mapShapes(model) { shape -> when { - shape.id in unwrappedStructures -> - (shape as StructureShape).toBuilder().addTrait(UnwrappedXmlOutput()).build() + shape.id in unwrappedOperations -> + (shape as OperationShape).toBuilder().addTrait(UnwrappedXmlOutput()).build() else -> shape } } diff --git a/codegen/aws-sdk-codegen/src/test/kotlin/aws/sdk/kotlin/codegen/customization/s3/UnwrappedXmlOutputIntegrationTest.kt b/codegen/aws-sdk-codegen/src/test/kotlin/aws/sdk/kotlin/codegen/customization/s3/UnwrappedXmlOutputIntegrationTest.kt deleted file mode 100644 index 7827fed4915..00000000000 --- a/codegen/aws-sdk-codegen/src/test/kotlin/aws/sdk/kotlin/codegen/customization/s3/UnwrappedXmlOutputIntegrationTest.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -package aws.sdk.kotlin.codegen.customization.s3 - -import aws.sdk.kotlin.codegen.testutil.model -import org.junit.jupiter.api.Assertions.assertFalse -import org.junit.jupiter.api.Test -import software.amazon.smithy.kotlin.codegen.test.defaultSettings -import kotlin.test.assertTrue - -/** - * Verify [UnwrappedXmlOutputIntegration] is enabled for proper service (S3) - */ -class UnwrappedXmlOutputIntegrationTest { - @Test - fun testNonS3Model() { - val model = model("NotS3") - val actual = UnwrappedXmlOutputIntegration().enabledForService(model, model.defaultSettings()) - assertFalse(actual) - } - - @Test - fun testS3Model() { - val model = model("S3") - val actual = UnwrappedXmlOutputIntegration().enabledForService(model, model.defaultSettings()) - assertTrue(actual) - } -} 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 5aed86cb8c2..027e9050f30 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 @@ -12,11 +12,10 @@ import software.amazon.smithy.kotlin.codegen.aws.protocols.core.AwsHttpBindingPr 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.* +import software.amazon.smithy.kotlin.codegen.lang.KotlinTypes import software.amazon.smithy.kotlin.codegen.model.* -import software.amazon.smithy.kotlin.codegen.model.traits.OperationOutput import software.amazon.smithy.kotlin.codegen.rendering.protocol.* import software.amazon.smithy.kotlin.codegen.rendering.serde.* -import software.amazon.smithy.kotlin.codegen.utils.dq import software.amazon.smithy.model.shapes.* import software.amazon.smithy.model.traits.* @@ -68,24 +67,6 @@ private class AwsQuerySerdeFormUrlDescriptorGenerator( member.hasTrait() } -private class AwsQuerySerdeXmlDescriptorGenerator( - ctx: RenderingContext, - memberShapes: List? = null, -) : XmlSerdeDescriptorGenerator(ctx, memberShapes) { - - override fun getObjectDescriptorTraits(): List { - val traits = super.getObjectDescriptorTraits().toMutableList() - - if (objectShape.hasTrait()) { - traits.removeIf { it.symbol == RuntimeTypes.Serde.SerdeXml.XmlSerialName } - val serialName = objectShape.changeNameSuffix("Response" to "Result") - traits.add(RuntimeTypes.Serde.SerdeXml.XmlSerialName, serialName.dq()) - } - - return traits - } -} - private class AwsQuerySerializerGenerator( private val protocolGenerator: AwsQuery, ) : AbstractQueryFormUrlSerializerGenerator(protocolGenerator, protocolGenerator.defaultTimestampFormat) { @@ -98,50 +79,76 @@ private class AwsQuerySerializerGenerator( } private class AwsQueryXmlParserGenerator( - private val protocolGenerator: AwsQuery, -) : XmlParserGenerator(protocolGenerator, protocolGenerator.defaultTimestampFormat) { - - override fun descriptorGenerator( - ctx: ProtocolGenerator.GenerationContext, - shape: Shape, - members: List, - writer: KotlinWriter, - ): XmlSerdeDescriptorGenerator = AwsQuerySerdeXmlDescriptorGenerator(ctx.toRenderingContext(protocolGenerator, shape, writer), members) - - override fun renderDeserializeOperationBody( - ctx: ProtocolGenerator.GenerationContext, - op: OperationShape, - documentMembers: List, - writer: KotlinWriter, - ) { - writer.write("val deserializer = #T(payload)", RuntimeTypes.Serde.SerdeXml.XmlDeserializer) - unwrapOperationResponseBody(op.id.name, writer) - val shape = ctx.model.expectShape(op.output.get()) - renderDeserializerBody(ctx, shape, documentMembers, writer) - } + protocolGenerator: AwsQuery, +) : XmlParserGenerator(protocolGenerator.defaultTimestampFormat) { /** * Unwraps the response body as specified by * https://awslabs.github.io/smithy/1.0/spec/aws/aws-query-protocol.html#response-serialization so that the * deserializer is in the correct state. + * + * ``` + * + * + * <-- SAME AS REST XML --> + * + * + * ``` */ - private fun unwrapOperationResponseBody( - operationName: String, + override fun unwrapOperationBody( + ctx: ProtocolGenerator.GenerationContext, + serdeCtx: SerdeCtx, + op: OperationShape, writer: KotlinWriter, - ) { - writer.write("// begin unwrap response wrapper") - .write("val resultDescriptor = #T(#T.Struct, #T(#S))", RuntimeTypes.Serde.SdkFieldDescriptor, RuntimeTypes.Serde.SerialKind, RuntimeTypes.Serde.SerdeXml.XmlSerialName, "${operationName}Result") - .withBlock("val wrapperDescriptor = #T.build {", "}", RuntimeTypes.Serde.SdkObjectDescriptor) { - write("trait(#T(#S))", RuntimeTypes.Serde.SerdeXml.XmlSerialName, "${operationName}Response") - write("#T(resultDescriptor)", RuntimeTypes.Serde.field) + ): SerdeCtx { + val operationName = op.id.getName(ctx.service) + + val unwrapAwsQueryOperation = buildSymbol { + name = "unwrapAwsQueryResponse" + namespace = ctx.settings.pkg.serde + definitionFile = "AwsQueryUtil.kt" + renderBy = { writer -> + + writer.withBlock( + "internal fun $name(root: #1T, operationName: #2T): #1T {", + "}", + RuntimeTypes.Serde.SerdeXml.XmlTagReader, + KotlinTypes.String, + ) { + write("val responseWrapperName = \"\${operationName}Response\"") + write("val resultWrapperName = \"\${operationName}Result\"") + withBlock( + "if (root.tagName != responseWrapperName) {", + "}", + ) { + write("throw #T(#S)", RuntimeTypes.Serde.DeserializationException, "invalid root, expected \$responseWrapperName; found `\${root.tag}`") + } + + write("val resultTag = ${serdeCtx.tagReader}.nextTag()") + withBlock( + "if (resultTag == null || resultTag.tagName != resultWrapperName) {", + "}", + ) { + write("throw #T(#S)", RuntimeTypes.Serde.DeserializationException, "invalid result, expected \$resultWrapperName; found `\${resultTag?.tag}`") + } + + write("return resultTag") + } } - .write("") - // abandon the iterator, this only occurs at the top level operational output - .write("val wrapper = deserializer.#T(wrapperDescriptor)", RuntimeTypes.Serde.deserializeStruct) - .withBlock("if (wrapper.findNextFieldIndex() != resultDescriptor.index) {", "}") { - write("throw #T(#S)", RuntimeTypes.Serde.DeserializationException, "failed to unwrap $operationName response") - } - .write("// end unwrap response wrapper") - .write("") + } + + writer.write("val unwrapped = #T(#L, #S)", unwrapAwsQueryOperation, serdeCtx.tagReader, operationName) + + return SerdeCtx("unwrapped") + } + + override fun unwrapOperationError( + ctx: ProtocolGenerator.GenerationContext, + serdeCtx: SerdeCtx, + errorShape: StructureShape, + writer: KotlinWriter, + ): SerdeCtx { + writer.write("val errReader = #T(${serdeCtx.tagReader})", RestXmlErrors.wrappedErrorResponseDeserializer(ctx)) + return SerdeCtx("errReader") } } 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 3db60123682..65751cefad6 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 @@ -6,20 +6,19 @@ package software.amazon.smithy.kotlin.codegen.aws.protocols import software.amazon.smithy.aws.traits.protocols.Ec2QueryNameTrait import software.amazon.smithy.aws.traits.protocols.Ec2QueryTrait +import software.amazon.smithy.codegen.core.Symbol import software.amazon.smithy.kotlin.codegen.aws.protocols.core.AbstractQueryFormUrlSerializerGenerator 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 import software.amazon.smithy.kotlin.codegen.core.RenderingContext import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes -import software.amazon.smithy.kotlin.codegen.model.changeNameSuffix +import software.amazon.smithy.kotlin.codegen.core.withBlock +import software.amazon.smithy.kotlin.codegen.model.buildSymbol import software.amazon.smithy.kotlin.codegen.model.getTrait -import software.amazon.smithy.kotlin.codegen.model.hasTrait -import software.amazon.smithy.kotlin.codegen.model.traits.OperationOutput import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolGenerator import software.amazon.smithy.kotlin.codegen.rendering.protocol.toRenderingContext import software.amazon.smithy.kotlin.codegen.rendering.serde.* -import software.amazon.smithy.kotlin.codegen.utils.dq import software.amazon.smithy.model.shapes.* import software.amazon.smithy.model.traits.XmlNameTrait @@ -73,24 +72,6 @@ private class Ec2QuerySerdeFormUrlDescriptorGenerator( targetShape.type == ShapeType.LIST } -private class Ec2QuerySerdeXmlDescriptorGenerator( - ctx: RenderingContext, - memberShapes: List? = null, -) : XmlSerdeDescriptorGenerator(ctx, memberShapes) { - - override fun getObjectDescriptorTraits(): List { - val traits = super.getObjectDescriptorTraits().toMutableList() - - if (objectShape.hasTrait()) { - traits.removeIf { it.symbol == RuntimeTypes.Serde.SerdeXml.XmlSerialName } - val serialName = objectShape.changeNameSuffix("Response" to "Result") - traits.add(RuntimeTypes.Serde.SerdeXml.XmlSerialName, serialName.dq()) - } - - return traits - } -} - private class Ec2QuerySerializerGenerator( private val protocolGenerator: Ec2Query, ) : AbstractQueryFormUrlSerializerGenerator(protocolGenerator, protocolGenerator.defaultTimestampFormat) { @@ -104,13 +85,73 @@ private class Ec2QuerySerializerGenerator( } private class Ec2QueryParserGenerator( - private val protocolGenerator: Ec2Query, -) : XmlParserGenerator(protocolGenerator, protocolGenerator.defaultTimestampFormat) { - - override fun descriptorGenerator( + protocolGenerator: Ec2Query, +) : XmlParserGenerator(protocolGenerator.defaultTimestampFormat) { + override fun unwrapOperationError( ctx: ProtocolGenerator.GenerationContext, - shape: Shape, - members: List, + serdeCtx: SerdeCtx, + errorShape: StructureShape, writer: KotlinWriter, - ): XmlSerdeDescriptorGenerator = Ec2QuerySerdeXmlDescriptorGenerator(ctx.toRenderingContext(protocolGenerator, shape, writer), members) + ): SerdeCtx { + val unwrapFn = unwrapErrorResponse(ctx) + writer.write("val errReader = #T(${serdeCtx.tagReader})", unwrapFn) + return SerdeCtx("errReader") + } + + /** + * Error deserializer for a wrapped error response + * + * ``` + * + * + * + * <-- DATA -->> + * + * + * + * ``` + * + * See https://smithy.io/2.0/aws/protocols/aws-ec2-query-protocol.html#operation-error-serialization + */ + private fun unwrapErrorResponse(ctx: ProtocolGenerator.GenerationContext): Symbol = buildSymbol { + name = "unwrapXmlErrorResponse" + namespace = ctx.settings.pkg.serde + definitionFile = "XmlErrorUtils.kt" + renderBy = { writer -> + writer.dokka("Handle [wrapped](https://smithy.io/2.0/aws/protocols/aws-ec2-query-protocol.html#operation-error-serialization) error responses") + writer.withBlock( + "internal fun $name(root: #1T): #1T {", + "}", + RuntimeTypes.Serde.SerdeXml.XmlTagReader, + ) { + withBlock( + "if (root.tagName != #S) {", + "}", + "Response", + ) { + write("throw #T(#S)", RuntimeTypes.Serde.DeserializationException, "invalid root, expected ; found `\${root.tag}`") + } + + write("val errorsTag = root.nextTag()") + withBlock( + "if (errorsTag == null || errorsTag.tagName != #S) {", + "}", + "Errors", + ) { + write("throw #T(#S)", RuntimeTypes.Serde.DeserializationException, "invalid error, expected ; found `\${errorsTag?.tag}`") + } + + write("val errTag = errorsTag.nextTag()") + withBlock( + "if (errTag == null || errTag.tagName != #S) {", + "}", + "Error", + ) { + write("throw #T(#S)", RuntimeTypes.Serde.DeserializationException, "invalid error, expected ; found `\${errTag?.tag}`") + } + + write("return errTag") + } + } + } } 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 158fc9c0262..c7c834c3175 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 @@ -52,7 +52,7 @@ open class RestXml : AwsHttpBindingProtocolGenerator() { } override fun structuredDataParser(ctx: ProtocolGenerator.GenerationContext): StructuredDataParserGenerator = - RestXmlParserGenerator(this, defaultTimestampFormat) + RestXmlParserGenerator(this) override fun structuredDataSerializer(ctx: ProtocolGenerator.GenerationContext): StructuredDataSerializerGenerator = RestXmlSerializerGenerator(this, defaultTimestampFormat) @@ -68,16 +68,8 @@ open class RestXml : AwsHttpBindingProtocolGenerator() { } class RestXmlParserGenerator( - private val protocolGenerator: RestXml, - defaultTimestampFormat: TimestampFormatTrait.Format, -) : XmlParserGenerator(protocolGenerator, defaultTimestampFormat) { - - override fun descriptorGenerator( - ctx: ProtocolGenerator.GenerationContext, - shape: Shape, - members: List, - writer: KotlinWriter, - ): XmlSerdeDescriptorGenerator = RestXmlSerdeDescriptorGenerator(ctx.toRenderingContext(protocolGenerator, shape, writer), members) + protocolGenerator: RestXml, +) : XmlParserGenerator(protocolGenerator.defaultTimestampFormat) { override fun payloadDeserializer( ctx: ProtocolGenerator.GenerationContext, @@ -112,14 +104,113 @@ class RestXmlParserGenerator( addNestedDocumentDeserializers(ctx, targetShape, writer) writer.dokka("Payload deserializer for ${memberSymbol.name} with a different XML name trait (${xmlNameTrait.value})") writer.withBlock("internal fun $name(payload: ByteArray): #T {", "}", memberSymbol) { - write("val deserializer = #T(payload)", RuntimeTypes.Serde.SerdeXml.XmlDeserializer) + writer.write("val root = #T(payload)", RuntimeTypes.Serde.SerdeXml.xmlRootTagReader) + val serdeCtx = SerdeCtx("root") write("val builder = #T.Builder()", memberSymbol) - renderDeserializerBody(ctx, copyWithMemberTraits, targetShape.members().toList(), writer) + renderDeserializerBody(ctx, serdeCtx, copyWithMemberTraits, targetShape.members().toList(), writer) write("return builder.build()") } } } } + + override fun unwrapOperationError( + ctx: ProtocolGenerator.GenerationContext, + serdeCtx: SerdeCtx, + errorShape: StructureShape, + writer: KotlinWriter, + ): SerdeCtx { + val unwrapFn = when (ctx.service.getTrait()?.isNoErrorWrapping == true) { + true -> RestXmlErrors.unwrappedErrorResponseDeserializer(ctx) + false -> RestXmlErrors.wrappedErrorResponseDeserializer(ctx) + } + writer.write("val errReader = #T(${serdeCtx.tagReader})", unwrapFn) + return SerdeCtx("errReader") + } +} + +object RestXmlErrors { + + /** + * Error deserializer for a wrapped error response + * + * ``` + * + * + * <-- DATA -->> + * + * + * ``` + * + * See https://smithy.io/2.0/aws/protocols/aws-restxml-protocol.html#error-response-serialization + */ + fun wrappedErrorResponseDeserializer(ctx: ProtocolGenerator.GenerationContext): Symbol = buildSymbol { + name = "unwrapWrappedXmlErrorResponse" + namespace = ctx.settings.pkg.serde + definitionFile = "XmlErrorUtils.kt" + renderBy = { writer -> + writer.dokka("Handle [wrapped](https://smithy.io/2.0/aws/protocols/aws-restxml-protocol.html#error-response-serialization) error responses") + writer.withBlock( + "internal fun $name(root: #1T): #1T {", + "}", + RuntimeTypes.Serde.SerdeXml.XmlTagReader, + ) { + withBlock( + "if (root.tagName != #S) {", + "}", + "ErrorResponse", + ) { + write("throw #T(#S)", RuntimeTypes.Serde.DeserializationException, "invalid root, expected ; found `\${root.tag}`") + } + + write("val errTag = root.nextTag()") + withBlock( + "if (errTag == null || errTag.tagName != #S) {", + "}", + "Error", + ) { + write("throw #T(#S)", RuntimeTypes.Serde.DeserializationException, "invalid error, expected ; found `\${errTag?.tag}`") + } + + write("return errTag") + } + } + } + + /** + * Error deserializer for an unwrapped error response + * + * ``` + * + * <-- DATA -->> + * + * ``` + * + * See https://smithy.io/2.0/aws/protocols/aws-restxml-protocol.html#error-response-serialization + */ + fun unwrappedErrorResponseDeserializer(ctx: ProtocolGenerator.GenerationContext): Symbol = buildSymbol { + name = "unwrapXmlErrorResponse" + namespace = ctx.settings.pkg.serde + definitionFile = "XmlErrorUtils.kt" + renderBy = { writer -> + writer.dokka("Handle [unwrapped](https://smithy.io/2.0/aws/protocols/aws-restxml-protocol.html#error-response-serialization) error responses (restXml.noErrorWrapping == true)") + writer.withBlock( + "internal fun $name(root: #1T): #1T {", + "}", + RuntimeTypes.Serde.SerdeXml.XmlTagReader, + ) { + withBlock( + "if (root.tagName != #S) {", + "}", + "Error", + ) { + write("throw #T(#S)", RuntimeTypes.Serde.DeserializationException, "invalid error, expected ; found `\${root.tag}`") + } + + write("return root") + } + } + } } class RestXmlSerializerGenerator( @@ -145,6 +236,7 @@ class RestXmlSerializerGenerator( else -> super.payloadSerializer(ctx, shape, members) } + // FIXME private fun explicitBoundStructureSerializer( ctx: ProtocolGenerator.GenerationContext, boundMember: MemberShape, diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 063cd76d368..f19bf376040 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,8 +9,8 @@ coroutines-version = "1.7.3" atomicfu-version = "0.23.1" # smithy-kotlin codegen and runtime are versioned separately -smithy-kotlin-runtime-version = "1.0.15" -smithy-kotlin-codegen-version = "0.30.16" +smithy-kotlin-runtime-version = "1.0.16" +smithy-kotlin-codegen-version = "0.30.17" # codegen smithy-version = "1.42.0" diff --git a/services/route53/common/src/aws/sdk/kotlin/services/route53/internal/ChangeResourceRecordSetsUnmarshalling.kt b/services/route53/common/src/aws/sdk/kotlin/services/route53/internal/ChangeResourceRecordSetsUnmarshalling.kt index 5b5af0d4748..e61e2a62021 100644 --- a/services/route53/common/src/aws/sdk/kotlin/services/route53/internal/ChangeResourceRecordSetsUnmarshalling.kt +++ b/services/route53/common/src/aws/sdk/kotlin/services/route53/internal/ChangeResourceRecordSetsUnmarshalling.kt @@ -6,50 +6,41 @@ package aws.sdk.kotlin.services.route53.internal import aws.sdk.kotlin.services.route53.model.InvalidChangeBatch import aws.smithy.kotlin.runtime.awsprotocol.ErrorDetails -import aws.smithy.kotlin.runtime.serde.* import aws.smithy.kotlin.runtime.serde.xml.* internal fun parseRestXmlInvalidChangeBatchResponse(payload: ByteArray): InvalidChangeBatchErrorResponse? = deserializeInvalidChangeBatchError(InvalidChangeBatch.Builder(), payload) internal fun deserializeInvalidChangeBatchError(builder: InvalidChangeBatch.Builder, payload: ByteArray): InvalidChangeBatchErrorResponse? { - val deserializer = XmlDeserializer(payload) - val MESSAGE_DESCRIPTOR = SdkFieldDescriptor(SerialKind.String, XmlSerialName("message"), XmlAliasName("Message")) - val MESSAGES_DESCRIPTOR = SdkFieldDescriptor(SerialKind.List, XmlSerialName("messages"), XmlAliasName("Messages"), XmlCollectionName("Message")) - val REQUESTID_DESCRIPTOR = SdkFieldDescriptor(SerialKind.String, XmlSerialName("RequestId")) - val OBJ_DESCRIPTOR = SdkObjectDescriptor.build { - trait(XmlSerialName("InvalidChangeBatch")) - trait(XmlNamespace("https://route53.amazonaws.com/doc/2013-04-01/")) - field(MESSAGE_DESCRIPTOR) - field(MESSAGES_DESCRIPTOR) - field(REQUESTID_DESCRIPTOR) - } + val root = xmlTagReader(payload) var requestId: String? = null - return try { - deserializer.deserializeStruct(OBJ_DESCRIPTOR) { - loop@while (true) { - when (findNextFieldIndex()) { - MESSAGE_DESCRIPTOR.index -> builder.message = deserializeString() - REQUESTID_DESCRIPTOR.index -> requestId = deserializeString() - MESSAGES_DESCRIPTOR.index -> - builder.messages = deserializer.deserializeList(MESSAGES_DESCRIPTOR) { - val col0 = mutableListOf() - while (hasNextElement()) { - val el0 = if (nextHasValue()) { deserializeString() } else { deserializeNull(); continue } - col0.add(el0) - } - col0 - } - null -> break@loop - else -> skipValue() - } + loop@while (true) { + val curr = root.nextTag() ?: break@loop + when (curr.tagName) { + "Message", "message" -> builder.message = curr.data() + "Messages", "messages" -> builder.messages = deserializeMessages(curr) + "RequestId" -> requestId = curr.data() + } + curr.drop() + } + + return InvalidChangeBatchErrorResponse(ErrorDetails("InvalidChangeBatch", builder.message, requestId), builder.build()) +} + +private fun deserializeMessages(root: XmlTagReader): List { + val result = mutableListOf() + loop@while (true) { + val curr = root.nextTag() ?: break@loop + when (curr.tagName) { + "Message" -> { + val el = curr.tryData().getOrNull() ?: continue@loop + result.add(el) } } - InvalidChangeBatchErrorResponse(ErrorDetails("InvalidChangeBatch", builder.message, requestId), builder.build()) - } catch (e: DeserializationException) { - null // return so an appropriate exception type can be instantiated above here. } + + return result } internal data class InvalidChangeBatchErrorResponse( diff --git a/services/route53/common/test/aws/sdk/kotlin/services/route53/internal/ChangeResourceRecordSetsUnmarshallingTest.kt b/services/route53/common/test/aws/sdk/kotlin/services/route53/internal/ChangeResourceRecordSetsUnmarshallingTest.kt index b6bd664a3d3..e8a3b2e5b3d 100644 --- a/services/route53/common/test/aws/sdk/kotlin/services/route53/internal/ChangeResourceRecordSetsUnmarshallingTest.kt +++ b/services/route53/common/test/aws/sdk/kotlin/services/route53/internal/ChangeResourceRecordSetsUnmarshallingTest.kt @@ -5,7 +5,6 @@ package aws.sdk.kotlin.services.route53.internal import aws.sdk.kotlin.services.route53.model.InvalidChangeBatch -import aws.sdk.kotlin.services.route53.model.Route53Exception import aws.sdk.kotlin.services.route53.serde.ChangeResourceRecordSetsOperationDeserializer import aws.smithy.kotlin.runtime.http.Headers import aws.smithy.kotlin.runtime.http.HttpBody @@ -32,7 +31,7 @@ class ChangeResourceRecordSetsUnmarshallingTest { """.trimIndent() - val response: HttpResponse = HttpResponse( + val response = HttpResponse( HttpStatusCode.BadRequest, Headers.Empty, HttpBody.fromBytes(bodyText.encodeToByteArray()), @@ -107,34 +106,4 @@ class ChangeResourceRecordSetsUnmarshallingTest { assertEquals(listOf("InvalidChangeBatch message 1", "InvalidChangeBatch message 2"), exception.messages) assertEquals("InvalidChangeBatch message 3", exception.message) } - - @Test - fun changeResourceRecordSetsError() { - val bodyText = """ - - - - Sender - MalformedXML - ChangeResourceRecordSets error message - - b25f48e8-84fd-11e6-80d9-574e0c4664cb - - """.trimIndent() - - val response: HttpResponse = HttpResponse( - HttpStatusCode.BadRequest, - Headers.Empty, - HttpBody.fromBytes(bodyText.encodeToByteArray()), - ) - - val call = HttpCall(HttpRequestBuilder().build(), response) - - val exception = assertFailsWith { - runBlocking { - ChangeResourceRecordSetsOperationDeserializer().deserialize(ExecutionContext(), call) - } - } - assertEquals("ChangeResourceRecordSets error message", exception.message) - } } diff --git a/services/s3/common/src/aws/sdk/kotlin/services/s3/internal/S3ErrorMetadata.kt b/services/s3/common/src/aws/sdk/kotlin/services/s3/internal/S3ErrorMetadata.kt index 68ad4342281..45074546fcd 100644 --- a/services/s3/common/src/aws/sdk/kotlin/services/s3/internal/S3ErrorMetadata.kt +++ b/services/s3/common/src/aws/sdk/kotlin/services/s3/internal/S3ErrorMetadata.kt @@ -5,7 +5,6 @@ package aws.sdk.kotlin.services.s3.internal import aws.sdk.kotlin.runtime.AwsServiceException -import aws.sdk.kotlin.runtime.http.* import aws.sdk.kotlin.services.s3.model.S3ErrorMetadata import aws.sdk.kotlin.services.s3.model.S3Exception import aws.smithy.kotlin.runtime.ServiceErrorMetadata @@ -13,9 +12,8 @@ import aws.smithy.kotlin.runtime.awsprotocol.AwsErrorDetails import aws.smithy.kotlin.runtime.awsprotocol.setAseErrorMetadata import aws.smithy.kotlin.runtime.collections.setIfValueNotNull import aws.smithy.kotlin.runtime.http.response.HttpResponse -import aws.smithy.kotlin.runtime.serde.* -import aws.smithy.kotlin.runtime.serde.xml.XmlDeserializer -import aws.smithy.kotlin.runtime.serde.xml.XmlSerialName +import aws.smithy.kotlin.runtime.serde.xml.data +import aws.smithy.kotlin.runtime.serde.xml.xmlTagReader /** * Default header name identifying secondary request ID @@ -45,35 +43,22 @@ internal fun setS3ErrorMetadata(exception: Any, response: HttpResponse, errorDet } internal fun parseS3ErrorResponse(payload: ByteArray): S3ErrorDetails { - val MESSAGE_DESCRIPTOR = SdkFieldDescriptor(SerialKind.String, XmlSerialName("Message")) - val CODE_DESCRIPTOR = SdkFieldDescriptor(SerialKind.String, XmlSerialName("Code")) - val REQUESTID_DESCRIPTOR = SdkFieldDescriptor(SerialKind.String, XmlSerialName("RequestId")) - val HOSTID_DESCRIPTOR = SdkFieldDescriptor(SerialKind.String, XmlSerialName("HostId")) - val OBJ_DESCRIPTOR = SdkObjectDescriptor.build { - trait(XmlSerialName("Error")) - field(MESSAGE_DESCRIPTOR) - field(CODE_DESCRIPTOR) - field(REQUESTID_DESCRIPTOR) - field(HOSTID_DESCRIPTOR) - } + val root = xmlTagReader(payload) var message: String? = null var code: String? = null var requestId: String? = null var requestId2: String? = null - val deserializer = XmlDeserializer(payload, true) - deserializer.deserializeStruct(OBJ_DESCRIPTOR) { - loop@ while (true) { - when (findNextFieldIndex()) { - MESSAGE_DESCRIPTOR.index -> message = deserializeString() - CODE_DESCRIPTOR.index -> code = deserializeString() - REQUESTID_DESCRIPTOR.index -> requestId = deserializeString() - HOSTID_DESCRIPTOR.index -> requestId2 = deserializeString() - null -> break@loop - else -> skipValue() - } + loop@ while (true) { + val curr = root.nextTag() ?: break@loop + when (curr.tagName) { + "Code" -> code = curr.data() + "Message", "message" -> message = curr.data() + "RequestId" -> requestId = curr.data() + "HostId" -> requestId2 = curr.data() } + curr.drop() } return S3ErrorDetails(code, message, requestId, requestId2)