diff --git a/clients/client-sqs/src/protocols/Aws_json1_0.ts b/clients/client-sqs/src/protocols/Aws_json1_0.ts index 61855c0b3ba5..33150181a900 100644 --- a/clients/client-sqs/src/protocols/Aws_json1_0.ts +++ b/clients/client-sqs/src/protocols/Aws_json1_0.ts @@ -883,33 +883,40 @@ const de_CommandError = async (output: __HttpResponse, context: __SerdeContext): throw await de_OverLimitRes(parsedOutput, context); case "QueueDoesNotExist": case "com.amazonaws.sqs#QueueDoesNotExist": + case "AWS.SimpleQueueService.NonExistentQueue": throw await de_QueueDoesNotExistRes(parsedOutput, context); case "RequestThrottled": case "com.amazonaws.sqs#RequestThrottled": throw await de_RequestThrottledRes(parsedOutput, context); case "UnsupportedOperation": case "com.amazonaws.sqs#UnsupportedOperation": + case "AWS.SimpleQueueService.UnsupportedOperation": throw await de_UnsupportedOperationRes(parsedOutput, context); case "ResourceNotFoundException": case "com.amazonaws.sqs#ResourceNotFoundException": throw await de_ResourceNotFoundExceptionRes(parsedOutput, context); case "MessageNotInflight": case "com.amazonaws.sqs#MessageNotInflight": + case "AWS.SimpleQueueService.MessageNotInflight": throw await de_MessageNotInflightRes(parsedOutput, context); case "ReceiptHandleIsInvalid": case "com.amazonaws.sqs#ReceiptHandleIsInvalid": throw await de_ReceiptHandleIsInvalidRes(parsedOutput, context); case "BatchEntryIdsNotDistinct": case "com.amazonaws.sqs#BatchEntryIdsNotDistinct": + case "AWS.SimpleQueueService.BatchEntryIdsNotDistinct": throw await de_BatchEntryIdsNotDistinctRes(parsedOutput, context); case "EmptyBatchRequest": case "com.amazonaws.sqs#EmptyBatchRequest": + case "AWS.SimpleQueueService.EmptyBatchRequest": throw await de_EmptyBatchRequestRes(parsedOutput, context); case "InvalidBatchEntryId": case "com.amazonaws.sqs#InvalidBatchEntryId": + case "AWS.SimpleQueueService.InvalidBatchEntryId": throw await de_InvalidBatchEntryIdRes(parsedOutput, context); case "TooManyEntriesInBatchRequest": case "com.amazonaws.sqs#TooManyEntriesInBatchRequest": + case "AWS.SimpleQueueService.TooManyEntriesInBatchRequest": throw await de_TooManyEntriesInBatchRequestRes(parsedOutput, context); case "InvalidAttributeName": case "com.amazonaws.sqs#InvalidAttributeName": @@ -919,42 +926,53 @@ const de_CommandError = async (output: __HttpResponse, context: __SerdeContext): throw await de_InvalidAttributeValueRes(parsedOutput, context); case "QueueDeletedRecently": case "com.amazonaws.sqs#QueueDeletedRecently": + case "AWS.SimpleQueueService.QueueDeletedRecently": throw await de_QueueDeletedRecentlyRes(parsedOutput, context); case "QueueNameExists": case "com.amazonaws.sqs#QueueNameExists": + case "QueueAlreadyExists": throw await de_QueueNameExistsRes(parsedOutput, context); case "InvalidIdFormat": case "com.amazonaws.sqs#InvalidIdFormat": throw await de_InvalidIdFormatRes(parsedOutput, context); case "PurgeQueueInProgress": case "com.amazonaws.sqs#PurgeQueueInProgress": + case "AWS.SimpleQueueService.PurgeQueueInProgress": throw await de_PurgeQueueInProgressRes(parsedOutput, context); case "KmsAccessDenied": case "com.amazonaws.sqs#KmsAccessDenied": + case "KMS.AccessDeniedException": throw await de_KmsAccessDeniedRes(parsedOutput, context); case "KmsDisabled": case "com.amazonaws.sqs#KmsDisabled": + case "KMS.DisabledException": throw await de_KmsDisabledRes(parsedOutput, context); case "KmsInvalidKeyUsage": case "com.amazonaws.sqs#KmsInvalidKeyUsage": + case "KMS.InvalidKeyUsageException": throw await de_KmsInvalidKeyUsageRes(parsedOutput, context); case "KmsInvalidState": case "com.amazonaws.sqs#KmsInvalidState": + case "KMS.InvalidStateException": throw await de_KmsInvalidStateRes(parsedOutput, context); case "KmsNotFound": case "com.amazonaws.sqs#KmsNotFound": + case "KMS.NotFoundException": throw await de_KmsNotFoundRes(parsedOutput, context); case "KmsOptInRequired": case "com.amazonaws.sqs#KmsOptInRequired": + case "KMS.OptInRequired": throw await de_KmsOptInRequiredRes(parsedOutput, context); case "KmsThrottled": case "com.amazonaws.sqs#KmsThrottled": + case "KMS.ThrottlingException": throw await de_KmsThrottledRes(parsedOutput, context); case "InvalidMessageContents": case "com.amazonaws.sqs#InvalidMessageContents": throw await de_InvalidMessageContentsRes(parsedOutput, context); case "BatchRequestTooLong": case "com.amazonaws.sqs#BatchRequestTooLong": + case "AWS.SimpleQueueService.BatchRequestTooLong": throw await de_BatchRequestTooLongRes(parsedOutput, context); default: const parsedBody = parsedOutput.body; @@ -2094,8 +2112,17 @@ function sharedHeaders(operation: string): __HeaderBag { const populateBodyWithQueryCompatibility = (parsedOutput: any, headers: __HeaderBag) => { const queryErrorHeader = headers["x-amzn-query-error"]; if (parsedOutput.body !== undefined && queryErrorHeader != null) { - const codeAndType = queryErrorHeader.split(";"); - parsedOutput.body.Code = codeAndType[0]; - parsedOutput.body.Type = codeAndType[1]; + const [Code, Type] = queryErrorHeader.split(";"); + const entries = Object.entries(parsedOutput.body); + const Error = { + Type, + Code, + } as any; + Object.assign(parsedOutput.body, Error); + for (const [k, v] of entries) { + Error[k] = v; + } + delete Error.__type; + parsedOutput.body.Error = Error; } }; diff --git a/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AwsProtocolUtils.java b/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AwsProtocolUtils.java index dad49a7dea5c..6169ccc7613a 100644 --- a/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AwsProtocolUtils.java +++ b/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AwsProtocolUtils.java @@ -18,11 +18,16 @@ import static software.amazon.smithy.model.knowledge.HttpBinding.Location.DOCUMENT; import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.TreeSet; import software.amazon.smithy.aws.traits.auth.UnsignedPayloadTrait; +import software.amazon.smithy.aws.traits.protocols.AwsQueryCompatibleTrait; +import software.amazon.smithy.aws.traits.protocols.AwsQueryErrorTrait; import software.amazon.smithy.model.knowledge.HttpBindingIndex; import software.amazon.smithy.model.knowledge.NeighborProviderIndex; import software.amazon.smithy.model.neighbor.Walker; @@ -30,6 +35,7 @@ import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.model.shapes.Shape; +import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.model.shapes.ShapeVisitor; import software.amazon.smithy.model.traits.IdempotencyTokenTrait; import software.amazon.smithy.model.traits.TimestampFormatTrait; @@ -300,6 +306,29 @@ static void generateProtocolTests(ProtocolGenerator generator, GenerationContext AwsProtocolUtils::filterMalformedRequestTests).run(); } + /** + * @return map of error full shape id to alias strings having AwsQueryCompat error code. + */ + static Map> getErrorAliases(GenerationContext context, + Collection operations) { + Map> aliases = new HashMap<>(); + ServiceShape service = context.getService(); + boolean awsQueryCompatible = service.hasTrait(AwsQueryCompatibleTrait.class); + if (awsQueryCompatible) { + for (OperationShape operation : operations) { + List errors = operation.getErrors(); + for (ShapeId error : errors) { + Shape errorShape = context.getModel().expectShape(error); + if (errorShape.hasTrait(AwsQueryErrorTrait.class)) { + String alias = errorShape.expectTrait(AwsQueryErrorTrait.class).getCode(); + aliases.computeIfAbsent(error.toString(), k -> new TreeSet<>()).add(alias); + } + } + } + } + return aliases; + } + private static boolean filterProtocolTests( ServiceShape service, OperationShape operation, diff --git a/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AwsSmithyRpcV2Cbor.java b/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AwsSmithyRpcV2Cbor.java index 6cc4200bffb9..88ea0fb71c00 100644 --- a/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AwsSmithyRpcV2Cbor.java +++ b/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AwsSmithyRpcV2Cbor.java @@ -5,12 +5,17 @@ package software.amazon.smithy.aws.typescript.codegen; +import java.util.Collection; +import java.util.Map; +import java.util.TreeSet; import software.amazon.smithy.aws.traits.protocols.AwsQueryCompatibleTrait; +import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.typescript.codegen.SmithyCoreSubmodules; import software.amazon.smithy.typescript.codegen.TypeScriptDependency; import software.amazon.smithy.typescript.codegen.TypeScriptWriter; import software.amazon.smithy.typescript.codegen.integration.ProtocolGenerator; import software.amazon.smithy.typescript.codegen.protocols.cbor.SmithyRpcV2Cbor; +import software.amazon.smithy.utils.IoUtils; /** * Extension of the Smithy RPCv2 CBOR protocol generator, adding @@ -23,19 +28,17 @@ public void generateSharedComponents(GenerationContext context) { if (context.getService().hasTrait(AwsQueryCompatibleTrait.class)) { TypeScriptWriter writer = context.getWriter(); writer.addImport("HeaderBag", "__HeaderBag", TypeScriptDependency.SMITHY_TYPES); - writer.write(""" - const populateBodyWithQueryCompatibility = (parsedOutput: any, headers: __HeaderBag) => { - const queryErrorHeader = headers["x-amzn-query-error"]; - if (parsedOutput.body !== undefined && queryErrorHeader != null) { - const codeAndType = queryErrorHeader.split(";"); - parsedOutput.body.Code = codeAndType[0]; - parsedOutput.body.Type = codeAndType[1]; - } - }; - """); + writer.write(IoUtils.readUtf8Resource( + AwsProtocolUtils.class, "populate-body-with-query-compatibility-code-stub.ts")); } } + @Override + public Map> getErrorAliases(GenerationContext context, + Collection operations) { + return AwsProtocolUtils.getErrorAliases(context, operations); + } + @Override protected void writeSharedRequestHeaders(ProtocolGenerator.GenerationContext context) { TypeScriptWriter writer = context.getWriter(); diff --git a/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/JsonRpcProtocolGenerator.java b/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/JsonRpcProtocolGenerator.java index 81a156cbce1d..5ea5c21d3da5 100644 --- a/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/JsonRpcProtocolGenerator.java +++ b/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/JsonRpcProtocolGenerator.java @@ -15,7 +15,10 @@ package software.amazon.smithy.aws.typescript.codegen; +import java.util.Collection; +import java.util.Map; import java.util.Set; +import java.util.TreeSet; import software.amazon.smithy.aws.traits.ServiceTrait; import software.amazon.smithy.aws.traits.protocols.AwsQueryCompatibleTrait; import software.amazon.smithy.aws.typescript.codegen.protocols.DeserializerElisionDenyList; @@ -56,6 +59,37 @@ abstract class JsonRpcProtocolGenerator extends HttpRpcProtocolGenerator { super(true); } + @Override + public void generateSharedComponents(GenerationContext context) { + super.generateSharedComponents(context); + AwsProtocolUtils.generateJsonParseBody(context); + AwsProtocolUtils.generateJsonParseErrorBody(context); + AwsProtocolUtils.addItempotencyAutofillImport(context); + + TypeScriptWriter writer = context.getWriter(); + writer.addUseImports(getApplicationProtocol().getResponseType()); + writer.addDependency(AwsDependency.AWS_SDK_CORE); + writer.addImport("loadRestJsonErrorCode", null, AwsDependency.AWS_SDK_CORE); + + if (context.getService().hasTrait(AwsQueryCompatibleTrait.class)) { + AwsProtocolUtils.generateJsonParseBodyWithQueryHeader(context); + } + writer.write( + context.getStringStore().flushVariableDeclarationCode() + ); + } + + @Override + public void generateProtocolTests(GenerationContext context) { + AwsProtocolUtils.generateProtocolTests(this, context); + } + + @Override + public Map> getErrorAliases(GenerationContext context, + Collection operations) { + return AwsProtocolUtils.getErrorAliases(context, operations); + } + @Override protected String getOperationPath(GenerationContext context, OperationShape operationShape) { return "/"; @@ -97,26 +131,6 @@ protected void generateDocumentBodyShapeDeserializers(GenerationContext context, ); } - @Override - public void generateSharedComponents(GenerationContext context) { - super.generateSharedComponents(context); - AwsProtocolUtils.generateJsonParseBody(context); - AwsProtocolUtils.generateJsonParseErrorBody(context); - AwsProtocolUtils.addItempotencyAutofillImport(context); - - TypeScriptWriter writer = context.getWriter(); - writer.addUseImports(getApplicationProtocol().getResponseType()); - writer.addDependency(AwsDependency.AWS_SDK_CORE); - writer.addImport("loadRestJsonErrorCode", null, AwsDependency.AWS_SDK_CORE); - - if (context.getService().hasTrait(AwsQueryCompatibleTrait.class)) { - AwsProtocolUtils.generateJsonParseBodyWithQueryHeader(context); - } - writer.write( - context.getStringStore().flushVariableDeclarationCode() - ); - } - @Override protected void writeRequestHeaders(GenerationContext context, OperationShape operation) { TypeScriptWriter writer = context.getWriter(); @@ -164,10 +178,6 @@ protected void serializeInputDocument( writer.write("body = JSON.stringify($L);", inputStructure.accept(getMemberSerVisitor(context, "input"))); } - private DocumentMemberSerVisitor getMemberSerVisitor(GenerationContext context, String dataSource) { - return new JsonMemberSerVisitor(context, dataSource, getDocumentTimestampFormat()); - } - @Override protected boolean writeUndefinedInputBody(GenerationContext context, OperationShape operation) { TypeScriptWriter writer = context.getWriter(); @@ -203,17 +213,16 @@ protected void deserializeOutputDocument( writer.write("contents = $L;", outputStructure.accept(getMemberDeserVisitor(context, "data"))); } - private ShapeVisitor getMemberDeserVisitor(GenerationContext context, String dataSource) { - return new JsonMemberDeserVisitor(context, dataSource, getDocumentTimestampFormat()); + @Override + protected boolean enableSerdeElision() { + return true; } - @Override - public void generateProtocolTests(GenerationContext context) { - AwsProtocolUtils.generateProtocolTests(this, context); + private DocumentMemberSerVisitor getMemberSerVisitor(GenerationContext context, String dataSource) { + return new JsonMemberSerVisitor(context, dataSource, getDocumentTimestampFormat()); } - @Override - protected boolean enableSerdeElision() { - return true; + private ShapeVisitor getMemberDeserVisitor(GenerationContext context, String dataSource) { + return new JsonMemberDeserVisitor(context, dataSource, getDocumentTimestampFormat()); } } diff --git a/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/RestJsonProtocolGenerator.java b/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/RestJsonProtocolGenerator.java index e4be95154e9d..bf50abed13ba 100644 --- a/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/RestJsonProtocolGenerator.java +++ b/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/RestJsonProtocolGenerator.java @@ -15,8 +15,11 @@ package software.amazon.smithy.aws.typescript.codegen; +import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.TreeSet; import software.amazon.smithy.aws.traits.ServiceTrait; import software.amazon.smithy.aws.traits.protocols.AwsQueryCompatibleTrait; import software.amazon.smithy.aws.typescript.codegen.protocols.DeserializerElisionDenyList; @@ -64,6 +67,35 @@ abstract class RestJsonProtocolGenerator extends HttpBindingProtocolGenerator { super(true); } + @Override + public Map> getErrorAliases(GenerationContext context, + Collection operations) { + return AwsProtocolUtils.getErrorAliases(context, operations); + } + + @Override + public void generateProtocolTests(GenerationContext context) { + AwsProtocolUtils.generateProtocolTests(this, context); + } + + @Override + public void generateSharedComponents(GenerationContext context) { + super.generateSharedComponents(context); + AwsProtocolUtils.generateJsonParseBody(context); + AwsProtocolUtils.generateJsonParseErrorBody(context); + AwsProtocolUtils.addItempotencyAutofillImport(context); + + TypeScriptWriter writer = context.getWriter(); + writer.addUseImports(getApplicationProtocol().getResponseType()); + writer.addImport("take", null, TypeScriptDependency.AWS_SMITHY_CLIENT); + writer.addDependency(AwsDependency.AWS_SDK_CORE); + writer.addImport("loadRestJsonErrorCode", null, AwsDependency.AWS_SDK_CORE); + + writer.write( + context.getStringStore().flushVariableDeclarationCode() + ); + } + @Override protected TimestampFormatTrait.Format getDocumentTimestampFormat() { return TimestampFormatTrait.Format.EPOCH_SECONDS; @@ -101,24 +133,6 @@ protected void generateDocumentBodyShapeDeserializers(GenerationContext context, ); } - @Override - public void generateSharedComponents(GenerationContext context) { - super.generateSharedComponents(context); - AwsProtocolUtils.generateJsonParseBody(context); - AwsProtocolUtils.generateJsonParseErrorBody(context); - AwsProtocolUtils.addItempotencyAutofillImport(context); - - TypeScriptWriter writer = context.getWriter(); - writer.addUseImports(getApplicationProtocol().getResponseType()); - writer.addImport("take", null, TypeScriptDependency.AWS_SMITHY_CLIENT); - writer.addDependency(AwsDependency.AWS_SDK_CORE); - writer.addImport("loadRestJsonErrorCode", null, AwsDependency.AWS_SDK_CORE); - - writer.write( - context.getStringStore().flushVariableDeclarationCode() - ); - } - @Override protected void importUnionDeserializer(TypeScriptWriter writer) { writer.addDependency(AwsDependency.AWS_SDK_CORE); @@ -180,55 +194,6 @@ protected void serializeErrorDocumentBody( serializeDocumentBody(context, documentBindings); } - private void serializeDocumentBody(GenerationContext context, List documentBindings) { - TypeScriptWriter writer = context.getWriter(); - SymbolProvider symbolProvider = context.getSymbolProvider(); - writer.addImport("take", null, TypeScriptDependency.AWS_SMITHY_CLIENT); - writer.openBlock("body = JSON.stringify(take(input, {", "}));", () -> { - for (HttpBinding binding : documentBindings) { - MemberShape memberShape = binding.getMember(); - // The name of the member to get from the input shape. - String memberName = symbolProvider.toMemberName(memberShape); - String inputLocation = "input." + memberName; - // Use the jsonName trait value if present, otherwise use the member name. - String wireName = memberShape.getTrait(JsonNameTrait.class) - .map(JsonNameTrait::getValue) - .orElseGet(binding::getLocationName); - boolean hasJsonName = memberShape.hasTrait(JsonNameTrait.class); - Shape target = context.getModel().expectShape(memberShape.getTarget()); - - // Handle @timestampFormat on members not just the targeted shape. - String valueProvider = "_ => " + (memberShape.hasTrait(TimestampFormatTrait.class) - ? AwsProtocolUtils.getInputTimestampValueProvider(context, memberShape, - getDocumentTimestampFormat(), "_") - : target.accept(getMemberSerVisitor(context, "_"))); - - if (hasJsonName) { - if (memberShape.hasTrait(IdempotencyTokenTrait.class)) { - writer.write("'$L': [true,_ => _ ?? generateIdempotencyToken(),`$L`],", - wireName, memberName); - } else { - if (valueProvider.equals("_ => _")) { - writer.write("'$L': [,,`$L`],", wireName, memberName); - } else { - writer.write("'$1L': [, $2L, `$3L`],", wireName, valueProvider, memberName); - } - } - } else { - if (memberShape.hasTrait(IdempotencyTokenTrait.class)) { - writer.write("'$L': [true, _ => _ ?? generateIdempotencyToken()],", wireName); - } else { - if (valueProvider.equals("_ => _")) { - writer.write("'$1L': [],", wireName); - } else { - writer.write("'$1L': $2L,", wireName, valueProvider); - } - } - } - } - }); - } - @Override protected void serializeInputPayload( GenerationContext context, @@ -265,32 +230,6 @@ protected void serializeErrorPayload( serializePayload(context, payloadBinding); } - private void serializePayload( - GenerationContext context, - HttpBinding payloadBinding - ) { - TypeScriptWriter writer = context.getWriter(); - MemberShape payloadMember = payloadBinding.getMember(); - Shape target = context.getModel().expectShape(payloadMember.getTarget()); - - // When payload target is a structure or union but payload is not a stream, default - // to an empty JSON body instead of an undefined body and make sure any structure or union - // content ends up as a JSON string. - if (target instanceof StructureShape - || (target instanceof UnionShape && !target.hasTrait(StreamingTrait.class)) - ) { - writer.openBlock("if (body === undefined) {", "}", () -> writer.write("body = {};")); - writer.write("body = JSON.stringify(body);"); - } else if (target instanceof DocumentShape) { - // Contents of documents need to be JSON encoded as well. - writer.write("body = JSON.stringify(body);"); - } - } - - private DocumentMemberSerVisitor getMemberSerVisitor(GenerationContext context, String dataSource) { - return new JsonMemberSerVisitor(context, dataSource, getDocumentTimestampFormat()); - } - protected boolean shouldWriteDefaultOutputBody(GenerationContext context, OperationShape operation) { // Operations that have any defined output shape should always send a default body. return operation.getOutput().isPresent(); @@ -331,47 +270,6 @@ protected void deserializeErrorDocumentBody( deserializeDocumentBody(context, documentBindings); } - private void deserializeDocumentBody( - GenerationContext context, - List documentBindings - ) { - TypeScriptWriter writer = context.getWriter(); - SymbolProvider symbolProvider = context.getSymbolProvider(); - writer.addImport("take", null, TypeScriptDependency.AWS_SMITHY_CLIENT); - - writer.openBlock("const doc = take(data, {", "});", () -> { - for (HttpBinding binding : documentBindings) { - Shape target = context.getModel().expectShape(binding.getMember().getTarget()); - // The name of the member to get from the input shape. - String memberName = symbolProvider.toMemberName(binding.getMember()); - // Use the jsonName trait value if present, otherwise use the member name. - String wireName = binding.getMember().getTrait(JsonNameTrait.class) - .map(JsonNameTrait::getValue) - .orElseGet(binding::getLocationName); - boolean hasJsonName = binding.getMember().hasTrait(JsonNameTrait.class); - - String valueExpression = target.accept( - getMemberDeserVisitor(context, binding.getMember(), "_")); - boolean isUnaryCall = UnaryFunctionCall.check(valueExpression); - if (hasJsonName) { - if (isUnaryCall) { - writer.write("'$L': [, $L, `$L`],", - memberName, UnaryFunctionCall.toRef(valueExpression), wireName); - } else { - writer.write("'$L': [, _ => $L, `$L`],", memberName, valueExpression, wireName); - } - } else { - if (isUnaryCall) { - writer.write("'$L': $L,", wireName, UnaryFunctionCall.toRef(valueExpression)); - } else { - writer.write("'$L': _ => $L,", wireName, valueExpression); - } - } - } - }); - writer.write("Object.assign(contents, doc);"); - } - @Override protected HttpBinding deserializeInputPayload( GenerationContext context, @@ -418,24 +316,135 @@ protected void readPayload( } } + @Override + protected boolean requiresNumericEpochSecondsInPayload() { + return true; + } + + @Override + protected boolean enableSerdeElision() { + return true; + } + + private void serializeDocumentBody(GenerationContext context, List documentBindings) { + TypeScriptWriter writer = context.getWriter(); + SymbolProvider symbolProvider = context.getSymbolProvider(); + writer.addImport("take", null, TypeScriptDependency.AWS_SMITHY_CLIENT); + writer.openBlock("body = JSON.stringify(take(input, {", "}));", () -> { + for (HttpBinding binding : documentBindings) { + MemberShape memberShape = binding.getMember(); + // The name of the member to get from the input shape. + String memberName = symbolProvider.toMemberName(memberShape); + String inputLocation = "input." + memberName; + // Use the jsonName trait value if present, otherwise use the member name. + String wireName = memberShape.getTrait(JsonNameTrait.class) + .map(JsonNameTrait::getValue) + .orElseGet(binding::getLocationName); + boolean hasJsonName = memberShape.hasTrait(JsonNameTrait.class); + Shape target = context.getModel().expectShape(memberShape.getTarget()); + + // Handle @timestampFormat on members not just the targeted shape. + String valueProvider = "_ => " + (memberShape.hasTrait(TimestampFormatTrait.class) + ? AwsProtocolUtils.getInputTimestampValueProvider(context, memberShape, + getDocumentTimestampFormat(), "_") + : target.accept(getMemberSerVisitor(context, "_"))); + + if (hasJsonName) { + if (memberShape.hasTrait(IdempotencyTokenTrait.class)) { + writer.write("'$L': [true,_ => _ ?? generateIdempotencyToken(),`$L`],", + wireName, memberName); + } else { + if (valueProvider.equals("_ => _")) { + writer.write("'$L': [,,`$L`],", wireName, memberName); + } else { + writer.write("'$1L': [, $2L, `$3L`],", wireName, valueProvider, memberName); + } + } + } else { + if (memberShape.hasTrait(IdempotencyTokenTrait.class)) { + writer.write("'$L': [true, _ => _ ?? generateIdempotencyToken()],", wireName); + } else { + if (valueProvider.equals("_ => _")) { + writer.write("'$1L': [],", wireName); + } else { + writer.write("'$1L': $2L,", wireName, valueProvider); + } + } + } + } + }); + } + private ShapeVisitor getMemberDeserVisitor(GenerationContext context, MemberShape memberShape, String dataSource) { return new JsonMemberDeserVisitor(context, memberShape, dataSource, getDocumentTimestampFormat()); } - @Override - public void generateProtocolTests(GenerationContext context) { - AwsProtocolUtils.generateProtocolTests(this, context); + private void serializePayload( + GenerationContext context, + HttpBinding payloadBinding + ) { + TypeScriptWriter writer = context.getWriter(); + MemberShape payloadMember = payloadBinding.getMember(); + Shape target = context.getModel().expectShape(payloadMember.getTarget()); + + // When payload target is a structure or union but payload is not a stream, default + // to an empty JSON body instead of an undefined body and make sure any structure or union + // content ends up as a JSON string. + if (target instanceof StructureShape + || (target instanceof UnionShape && !target.hasTrait(StreamingTrait.class)) + ) { + writer.openBlock("if (body === undefined) {", "}", () -> writer.write("body = {};")); + writer.write("body = JSON.stringify(body);"); + } else if (target instanceof DocumentShape) { + // Contents of documents need to be JSON encoded as well. + writer.write("body = JSON.stringify(body);"); + } } - @Override - protected boolean requiresNumericEpochSecondsInPayload() { - return true; + private DocumentMemberSerVisitor getMemberSerVisitor(GenerationContext context, String dataSource) { + return new JsonMemberSerVisitor(context, dataSource, getDocumentTimestampFormat()); } - @Override - protected boolean enableSerdeElision() { - return true; + private void deserializeDocumentBody( + GenerationContext context, + List documentBindings + ) { + TypeScriptWriter writer = context.getWriter(); + SymbolProvider symbolProvider = context.getSymbolProvider(); + writer.addImport("take", null, TypeScriptDependency.AWS_SMITHY_CLIENT); + + writer.openBlock("const doc = take(data, {", "});", () -> { + for (HttpBinding binding : documentBindings) { + Shape target = context.getModel().expectShape(binding.getMember().getTarget()); + // The name of the member to get from the input shape. + String memberName = symbolProvider.toMemberName(binding.getMember()); + // Use the jsonName trait value if present, otherwise use the member name. + String wireName = binding.getMember().getTrait(JsonNameTrait.class) + .map(JsonNameTrait::getValue) + .orElseGet(binding::getLocationName); + boolean hasJsonName = binding.getMember().hasTrait(JsonNameTrait.class); + + String valueExpression = target.accept( + getMemberDeserVisitor(context, binding.getMember(), "_")); + boolean isUnaryCall = UnaryFunctionCall.check(valueExpression); + if (hasJsonName) { + if (isUnaryCall) { + writer.write("'$L': [, $L, `$L`],", + memberName, UnaryFunctionCall.toRef(valueExpression), wireName); + } else { + writer.write("'$L': [, _ => $L, `$L`],", memberName, valueExpression, wireName); + } + } else { + if (isUnaryCall) { + writer.write("'$L': $L,", wireName, UnaryFunctionCall.toRef(valueExpression)); + } else { + writer.write("'$L': _ => $L,", wireName, valueExpression); + } + } + } + }); + writer.write("Object.assign(contents, doc);"); } } diff --git a/codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/populate-body-with-query-compatibility-code-stub.ts b/codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/populate-body-with-query-compatibility-code-stub.ts index e66725f360d9..c5e9b27b06f5 100644 --- a/codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/populate-body-with-query-compatibility-code-stub.ts +++ b/codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/populate-body-with-query-compatibility-code-stub.ts @@ -1,8 +1,17 @@ const populateBodyWithQueryCompatibility = (parsedOutput: any, headers: __HeaderBag) => { const queryErrorHeader = headers["x-amzn-query-error"]; if (parsedOutput.body !== undefined && queryErrorHeader != null) { - const codeAndType = queryErrorHeader.split(";"); - parsedOutput.body.Code = codeAndType[0]; - parsedOutput.body.Type = codeAndType[1]; + const [Code, Type] = queryErrorHeader.split(";"); + const entries = Object.entries(parsedOutput.body); + const Error = { + Type, + Code, + } as any; + Object.assign(parsedOutput.body, Error); + for (const [k, v] of entries) { + Error[k] = v; + } + delete Error.__type; + parsedOutput.body.Error = Error; } }; diff --git a/scripts/generate-clients/config.js b/scripts/generate-clients/config.js index 251fceb98c25..6defd1b3a8f4 100644 --- a/scripts/generate-clients/config.js +++ b/scripts/generate-clients/config.js @@ -1,7 +1,7 @@ // Update this commit when taking up new changes from smithy-typescript. module.exports = { // Use full commit hash as we explicitly fetch it. - SMITHY_TS_COMMIT: "d468edf23d0ee9f3966feb909f2f2f06ae4d81d7", + SMITHY_TS_COMMIT: "90d03e4eee59b345b48bd1916d232e1ce8d6b2f1", }; if (module.exports.SMITHY_TS_COMMIT.length < 40) {