diff --git a/codegen/sdk-codegen/build.gradle.kts b/codegen/sdk-codegen/build.gradle.kts index 7178688d35ef9..8ca85d8790709 100644 --- a/codegen/sdk-codegen/build.gradle.kts +++ b/codegen/sdk-codegen/build.gradle.kts @@ -110,7 +110,7 @@ tasks.register("generate-smithy-build") { // e.g. "S3" - use this as exclusion list if needed. ) val useSchemaSerde = setOf( - // "CloudWatch Logs" + // "S3" ) val projectionContents = Node.objectNodeBuilder() .withMember("imports", Node.fromStrings("${models.getAbsolutePath()}${File.separator}${file.name}")) diff --git a/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddProtocolConfig.java b/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddProtocolConfig.java index 9ba8ccafb3f29..e82e5e30b8837 100644 --- a/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddProtocolConfig.java +++ b/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddProtocolConfig.java @@ -38,6 +38,7 @@ public AddProtocolConfig() { SchemaGenerationAllowlist.allow("com.amazonaws.dynamodb#DynamoDB_20120810"); SchemaGenerationAllowlist.allow("com.amazonaws.lambda#AWSGirApiService"); SchemaGenerationAllowlist.allow("com.amazonaws.cloudwatchlogs#Logs_20140328"); + SchemaGenerationAllowlist.allow("com.amazonaws.sts#AWSSecurityTokenServiceV20110615"); // protocol tests SchemaGenerationAllowlist.allow("aws.protocoltests.json10#JsonRpc10"); diff --git a/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddS3Config.java b/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddS3Config.java index cd75b4baaa529..2fdfb3c76937b 100644 --- a/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddS3Config.java +++ b/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddS3Config.java @@ -34,6 +34,7 @@ import software.amazon.smithy.model.knowledge.OperationIndex; import software.amazon.smithy.model.knowledge.TopDownIndex; import software.amazon.smithy.model.pattern.SmithyPattern; +import software.amazon.smithy.model.pattern.UriPattern; import software.amazon.smithy.model.shapes.MemberShape; import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.shapes.ServiceShape; @@ -47,9 +48,11 @@ import software.amazon.smithy.model.traits.EndpointTrait; import software.amazon.smithy.model.traits.HttpHeaderTrait; import software.amazon.smithy.model.traits.HttpPayloadTrait; +import software.amazon.smithy.model.traits.HttpTrait; import software.amazon.smithy.model.traits.StreamingTrait; import software.amazon.smithy.model.traits.Trait; import software.amazon.smithy.model.transform.ModelTransformer; +import software.amazon.smithy.rulesengine.traits.ContextParamTrait; import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait; import software.amazon.smithy.typescript.codegen.LanguageTarget; import software.amazon.smithy.typescript.codegen.TypeScriptDependency; @@ -58,6 +61,7 @@ import software.amazon.smithy.typescript.codegen.auth.http.integration.AddHttpSigningPlugin; import software.amazon.smithy.typescript.codegen.integration.RuntimeClientPlugin; import software.amazon.smithy.typescript.codegen.integration.TypeScriptIntegration; +import software.amazon.smithy.typescript.codegen.schema.SchemaGenerationAllowlist; import software.amazon.smithy.utils.ListUtils; import software.amazon.smithy.utils.MapUtils; import software.amazon.smithy.utils.SetUtils; @@ -113,6 +117,53 @@ public static Shape removeHostPrefixTrait(Shape shape) { .orElse(shape); } + /** + * Remove `/{Bucket}` from the operation endpoint URI IFF + * - it is in a prefix position. + * - input has a member called "Bucket". + * - "Bucket" input member is a contextParam. + */ + public static Shape removeUriBucketPrefix(Shape shape, Model model) { + return shape.asOperationShape() + .map(OperationShape::shapeToBuilder) + .map((Object object) -> { + OperationShape.Builder builder = (OperationShape.Builder) object; + Trait trait = builder.getAllTraits().get(HttpTrait.ID); + if (trait instanceof HttpTrait httpTrait) { + String uri = httpTrait.getUri().toString(); + + StructureShape input = model.expectShape( + shape.asOperationShape().get().getInputShape() + ).asStructureShape().orElseThrow( + () -> new RuntimeException("operation must have input structure") + ); + + boolean hasBucketPrefix = uri.startsWith("/{Bucket}"); + Optional bucket = input.getMember("Bucket"); + boolean inputHasBucketMember = bucket.isPresent(); + boolean bucketIsContextParam = bucket + .map(ms -> ms.getTrait(ContextParamTrait.class)) + .isPresent(); + + if (hasBucketPrefix && inputHasBucketMember && bucketIsContextParam) { + String replaced = uri + .replace("/{Bucket}/", "/") + .replace("/{Bucket}", "/"); + builder.addTrait( + httpTrait + .toBuilder() + .uri(UriPattern.parse(replaced)) + .build() + ); + } + } + return builder; + }) + .map(OperationShape.Builder::build) + .map(s -> (Shape) s) + .orElse(shape); + } + @Override public List runAfter() { return List.of( @@ -243,7 +294,12 @@ public Model preprocessModel(Model model, TypeScriptSettings settings) { Model builtModel = modelBuilder.addShapes(inputShapes).build(); if (hasRuleset) { return ModelTransformer.create().mapShapes( - builtModel, AddS3Config::removeHostPrefixTrait + builtModel, (shape) -> { + if (SchemaGenerationAllowlist.allows(serviceShape.getId(), settings)) { + return removeUriBucketPrefix(shape, model); + } + return shape; + } ); } return builtModel; diff --git a/packages/core/src/submodules/protocols/json/AwsJsonRpcProtocol.ts b/packages/core/src/submodules/protocols/json/AwsJsonRpcProtocol.ts index ff561878cbf1a..e309c1a5043b2 100644 --- a/packages/core/src/submodules/protocols/json/AwsJsonRpcProtocol.ts +++ b/packages/core/src/submodules/protocols/json/AwsJsonRpcProtocol.ts @@ -85,17 +85,26 @@ export abstract class AwsJsonRpcProtocol extends RpcProtocol { [namespace, errorName] = errorIdentifier.split("#"); } + const errorMetadata = { + $metadata: metadata, + $response: response, + $fault: response.statusCode <= 500 ? ("client" as const) : ("server" as const), + }; + const registry = TypeRegistry.for(namespace); let errorSchema: ErrorSchema; try { errorSchema = registry.getSchema(errorIdentifier) as ErrorSchema; } catch (e) { + if (dataObject.Message) { + dataObject.message = dataObject.Message; + } const baseExceptionSchema = TypeRegistry.for("smithy.ts.sdk.synthetic." + namespace).getBaseException(); if (baseExceptionSchema) { const ErrorCtor = baseExceptionSchema.ctor; - throw Object.assign(new ErrorCtor(errorName), dataObject); + throw Object.assign(new ErrorCtor({ name: errorName }), errorMetadata, dataObject); } - throw new Error(errorName); + throw Object.assign(new Error(errorName), errorMetadata, dataObject); } const ns = NormalizedSchema.of(errorSchema); @@ -109,14 +118,14 @@ export abstract class AwsJsonRpcProtocol extends RpcProtocol { output[name] = this.codec.createDeserializer().readObject(member, dataObject[target]); } - Object.assign(exception, { - $metadata: metadata, - $response: response, - $fault: ns.getMergedTraits().error, - message, - ...output, - }); - - throw exception; + throw Object.assign( + exception, + errorMetadata, + { + $fault: ns.getMergedTraits().error, + message, + }, + output + ); } } diff --git a/packages/core/src/submodules/protocols/json/AwsRestJsonProtocol.ts b/packages/core/src/submodules/protocols/json/AwsRestJsonProtocol.ts index 0b89f40148f41..03b36be59a734 100644 --- a/packages/core/src/submodules/protocols/json/AwsRestJsonProtocol.ts +++ b/packages/core/src/submodules/protocols/json/AwsRestJsonProtocol.ts @@ -123,17 +123,26 @@ export class AwsRestJsonProtocol extends HttpBindingProtocol { [namespace, errorName] = errorIdentifier.split("#"); } + const errorMetadata = { + $metadata: metadata, + $response: response, + $fault: response.statusCode <= 500 ? ("client" as const) : ("server" as const), + }; + const registry = TypeRegistry.for(namespace); let errorSchema: ErrorSchema; try { errorSchema = registry.getSchema(errorIdentifier) as ErrorSchema; } catch (e) { + if (dataObject.Message) { + dataObject.message = dataObject.Message; + } const baseExceptionSchema = TypeRegistry.for("smithy.ts.sdk.synthetic." + namespace).getBaseException(); if (baseExceptionSchema) { const ErrorCtor = baseExceptionSchema.ctor; - throw Object.assign(new ErrorCtor(errorName), dataObject); + throw Object.assign(new ErrorCtor({ name: errorName }), errorMetadata, dataObject); } - throw new Error(errorName); + throw Object.assign(new Error(errorName), errorMetadata, dataObject); } const ns = NormalizedSchema.of(errorSchema); @@ -147,15 +156,15 @@ export class AwsRestJsonProtocol extends HttpBindingProtocol { output[name] = this.codec.createDeserializer().readObject(member, dataObject[target]); } - Object.assign(exception, { - $metadata: metadata, - $response: response, - $fault: ns.getMergedTraits().error, - message, - ...output, - }); - - throw exception; + throw Object.assign( + exception, + errorMetadata, + { + $fault: ns.getMergedTraits().error, + message, + }, + output + ); } /** diff --git a/packages/core/src/submodules/protocols/query/AwsQueryProtocol.ts b/packages/core/src/submodules/protocols/query/AwsQueryProtocol.ts index 9c66bfb0d4a52..f695500dde550 100644 --- a/packages/core/src/submodules/protocols/query/AwsQueryProtocol.ts +++ b/packages/core/src/submodules/protocols/query/AwsQueryProtocol.ts @@ -146,7 +146,12 @@ export class AwsQueryProtocol extends RpcProtocol { [namespace, errorName] = errorIdentifier.split("#"); } - const errorDataSource = this.loadQueryError(dataObject); + const errorData = this.loadQueryError(dataObject); + const errorMetadata = { + $metadata: metadata, + $response: response, + $fault: response.statusCode <= 500 ? ("client" as const) : ("server" as const), + }; const registry = TypeRegistry.for(namespace); let errorSchema: ErrorSchema; @@ -159,12 +164,15 @@ export class AwsQueryProtocol extends RpcProtocol { errorSchema = registry.getSchema(errorIdentifier) as ErrorSchema; } } catch (e) { + if (errorData.Message) { + errorData.message = errorData.Message; + } const baseExceptionSchema = TypeRegistry.for("smithy.ts.sdk.synthetic." + namespace).getBaseException(); if (baseExceptionSchema) { const ErrorCtor = baseExceptionSchema.ctor; - throw Object.assign(new ErrorCtor(errorName), errorDataSource); + throw Object.assign(new ErrorCtor({ name: errorName }), errorMetadata, dataObject); } - throw new Error(errorName); + throw Object.assign(new Error(errorName), errorMetadata, errorData); } const ns = NormalizedSchema.of(errorSchema); @@ -175,19 +183,19 @@ export class AwsQueryProtocol extends RpcProtocol { for (const [name, member] of ns.structIterator()) { const target = member.getMergedTraits().xmlName ?? name; - const value = errorDataSource[target] ?? dataObject[target]; + const value = errorData[target] ?? dataObject[target]; output[name] = this.deserializer.readSchema(member, value); } - Object.assign(exception, { - $metadata: metadata, - $response: response, - $fault: ns.getMergedTraits().error, - message, - ...output, - }); - - throw exception; + throw Object.assign( + exception, + errorMetadata, + { + $fault: ns.getMergedTraits().error, + message, + }, + output + ); } /** diff --git a/packages/core/src/submodules/protocols/xml/AwsRestXmlProtocol.spec.ts b/packages/core/src/submodules/protocols/xml/AwsRestXmlProtocol.spec.ts index 0d27f8e109d4f..5fdbd8863c381 100644 --- a/packages/core/src/submodules/protocols/xml/AwsRestXmlProtocol.spec.ts +++ b/packages/core/src/submodules/protocols/xml/AwsRestXmlProtocol.spec.ts @@ -30,7 +30,10 @@ describe(AwsRestXmlProtocol.name, () => { }, expected: { request: { - path: "/", + // S3 customization not active here since this is a mock. + // customization does model preprocessing to remove /{Bucket} prefix + // when it is a contextParam. + path: "/{Bucket}", method: "POST", headers: { "content-type": "application/xml", diff --git a/packages/core/src/submodules/protocols/xml/AwsRestXmlProtocol.ts b/packages/core/src/submodules/protocols/xml/AwsRestXmlProtocol.ts index a0a57aa4ec771..2d26a0c15ef86 100644 --- a/packages/core/src/submodules/protocols/xml/AwsRestXmlProtocol.ts +++ b/packages/core/src/submodules/protocols/xml/AwsRestXmlProtocol.ts @@ -61,16 +61,6 @@ export class AwsRestXmlProtocol extends HttpBindingProtocol { const ns = NormalizedSchema.of(operationSchema.input); const members = ns.getMemberSchemas(); - request.path = - String(request.path) - .split("/") - .filter((segment) => { - // for legacy reasons, - // Bucket is in the http trait but is handled by endpoints ruleset. - return segment !== "{Bucket}"; - }) - .join("/") || "/"; - if (!request.headers["content-type"]) { const httpPayloadMember = Object.values(members).find((m) => { return !!m.getMergedTraits().httpPayload; @@ -136,17 +126,27 @@ export class AwsRestXmlProtocol extends HttpBindingProtocol { [namespace, errorName] = errorIdentifier.split("#"); } + const errorMetadata = { + $metadata: metadata, + $response: response, + $fault: response.statusCode <= 500 ? ("client" as const) : ("server" as const), + }; + const registry = TypeRegistry.for(namespace); + let errorSchema: ErrorSchema; try { errorSchema = registry.getSchema(errorIdentifier) as ErrorSchema; } catch (e) { + if (dataObject.Message) { + dataObject.message = dataObject.Message; + } const baseExceptionSchema = TypeRegistry.for("smithy.ts.sdk.synthetic." + namespace).getBaseException(); if (baseExceptionSchema) { const ErrorCtor = baseExceptionSchema.ctor; - throw Object.assign(new ErrorCtor(errorName), dataObject); + throw Object.assign(new ErrorCtor({ name: errorName }), errorMetadata, dataObject); } - throw new Error(errorName); + throw Object.assign(new Error(errorName), errorMetadata, dataObject); } const ns = NormalizedSchema.of(errorSchema); @@ -162,15 +162,15 @@ export class AwsRestXmlProtocol extends HttpBindingProtocol { output[name] = this.codec.createDeserializer().readSchema(member, value); } - Object.assign(exception, { - $metadata: metadata, - $response: response, - $fault: ns.getMergedTraits().error, - message, - ...output, - }); - - throw exception; + throw Object.assign( + exception, + errorMetadata, + { + $fault: ns.getMergedTraits().error, + message, + }, + output + ); } /** diff --git a/packages/core/src/submodules/protocols/xml/XmlShapeSerializer.ts b/packages/core/src/submodules/protocols/xml/XmlShapeSerializer.ts index ef66c11dd711b..ab06ba1bb7e18 100644 --- a/packages/core/src/submodules/protocols/xml/XmlShapeSerializer.ts +++ b/packages/core/src/submodules/protocols/xml/XmlShapeSerializer.ts @@ -79,10 +79,6 @@ export class XmlShapeSerializer extends SerdeContextConfig implements ShapeSeria const [xmlnsAttr, xmlns] = this.getXmlnsAttribute(ns, parentXmlns); - if (xmlns) { - structXmlNode.addAttribute(xmlnsAttr as string, xmlns); - } - for (const [memberName, memberSchema] of ns.structIterator()) { const val = (value as any)[memberName]; @@ -108,6 +104,10 @@ export class XmlShapeSerializer extends SerdeContextConfig implements ShapeSeria } } + if (xmlns) { + structXmlNode.addAttribute(xmlnsAttr as string, xmlns); + } + return structXmlNode; } diff --git a/packages/middleware-logger/src/loggerMiddleware.ts b/packages/middleware-logger/src/loggerMiddleware.ts index ac547d64dcf1d..7d249f8151696 100644 --- a/packages/middleware-logger/src/loggerMiddleware.ts +++ b/packages/middleware-logger/src/loggerMiddleware.ts @@ -1,4 +1,4 @@ -import { +import type { AbsoluteLocation, HandlerExecutionContext, InitializeHandler, diff --git a/private/aws-client-retry-test/src/ClientRetryTest.spec.ts b/private/aws-client-retry-test/src/ClientRetryTest.spec.ts index 360e86b19680a..7c9dac0283518 100644 --- a/private/aws-client-retry-test/src/ClientRetryTest.spec.ts +++ b/private/aws-client-retry-test/src/ClientRetryTest.spec.ts @@ -88,17 +88,6 @@ describe("util-retry integration tests", () => { }); it("should retry until attempts are exhausted", async () => { - const expectedException = new S3ServiceException({ - $metadata: { - httpStatusCode: 429, - }, - $fault: "client", - $retryable: { - throttling: true, - }, - message: "UnknownError", - name: "ThrottlingException", - }); const client = new S3Client({ requestHandler: { handle: () => Promise.resolve(mockThrottled), @@ -112,10 +101,15 @@ describe("util-retry integration tests", () => { try { await client.send(headObjectCommand); } catch (error) { - expect(error).toStrictEqual(expectedException); expect(error.$metadata.httpStatusCode).toBe(429); expect(error.$metadata.attempts).toBe(3); expect(error.$metadata.totalRetryDelay).toBeGreaterThan(0); + expect(error).toMatchObject({ + $metadata: { + httpStatusCode: 429, + }, + $fault: "client", + }); } }); diff --git a/private/aws-middleware-test/src/middleware-serde.spec.ts b/private/aws-middleware-test/src/middleware-serde.spec.ts index 9049f5bb0591b..ebe2cc3fe8511 100644 --- a/private/aws-middleware-test/src/middleware-serde.spec.ts +++ b/private/aws-middleware-test/src/middleware-serde.spec.ts @@ -4,7 +4,7 @@ import { SageMaker } from "@aws-sdk/client-sagemaker"; import { SageMakerRuntime } from "@aws-sdk/client-sagemaker-runtime"; import { describe, test as it } from "vitest"; -import { requireRequestsFrom } from "../../aws-util-test/src"; +import { requireRequestsFrom } from "@aws-sdk/aws-util-test/src"; describe("middleware-serde", () => { describe(S3.name, () => {