diff --git a/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddAwsAuthPlugin.java b/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddAwsAuthPlugin.java index 29fe4e917e45..a574667f6731 100644 --- a/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddAwsAuthPlugin.java +++ b/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddAwsAuthPlugin.java @@ -15,6 +15,7 @@ package software.amazon.smithy.aws.typescript.codegen; +import static software.amazon.smithy.aws.typescript.codegen.AwsTraitsUtils.isSigV4Service; import static software.amazon.smithy.typescript.codegen.integration.RuntimeClientPlugin.Convention.HAS_CONFIG; import static software.amazon.smithy.typescript.codegen.integration.RuntimeClientPlugin.Convention.HAS_MIDDLEWARE; @@ -46,6 +47,7 @@ /** * Configure clients with AWS auth configurations and plugin. */ +// TODO: Think about AWS Auth supported for only some operations and not all, when not AWS service, with say @auth([]) public final class AddAwsAuthPlugin implements TypeScriptIntegration { static final String STS_CLIENT_PREFIX = "sts-client-"; static final String ROLE_ASSUMERS_FILE = "defaultRoleAssumers"; @@ -59,6 +61,9 @@ public void addConfigInterfaceFields( TypeScriptWriter writer ) { ServiceShape service = settings.getService(model); + if (!isSigV4Service(service)) { + return; + } if (!areAllOptionalAuthOperations(model, service)) { writer.addImport("Credentials", "__Credentials", TypeScriptDependency.AWS_SDK_TYPES.packageName); writer.writeDocs("Default credentials provider; Not available in browser runtime.") @@ -71,7 +76,9 @@ public List getClientPlugins() { return ListUtils.of( RuntimeClientPlugin.builder() .withConventions(AwsDependency.MIDDLEWARE_SIGNING.dependency, "AwsAuth", HAS_CONFIG) - .servicePredicate((m, s) -> !areAllOptionalAuthOperations(m, s) && !testServiceId(s, "STS")) + .servicePredicate((m, s) -> isSigV4Service(s) + && !areAllOptionalAuthOperations(m, s) + && !testServiceId(s, "STS")) .build(), RuntimeClientPlugin.builder() .withConventions(AwsDependency.STS_MIDDLEWARE.dependency, @@ -83,7 +90,7 @@ public List getClientPlugins() { .withConventions(AwsDependency.MIDDLEWARE_SIGNING.dependency, "AwsAuth", HAS_MIDDLEWARE) // See operationUsesAwsAuth() below for AwsAuth Middleware customizations. .servicePredicate( - (m, s) -> !testServiceId(s, "STS") && !hasOptionalAuthOperation(m, s) + (m, s) -> !testServiceId(s, "STS") && isSigV4Service(s) && !hasOptionalAuthOperation(m, s) ).build(), RuntimeClientPlugin.builder() .withConventions(AwsDependency.MIDDLEWARE_SIGNING.dependency, "AwsAuth", HAS_MIDDLEWARE) @@ -100,7 +107,7 @@ public Map> getRuntimeConfigWriters( LanguageTarget target ) { ServiceShape service = settings.getService(model); - if (areAllOptionalAuthOperations(model, service)) { + if (!isSigV4Service(service) || areAllOptionalAuthOperations(model, service)) { return Collections.emptyMap(); } switch (target) { @@ -187,8 +194,8 @@ private static boolean operationUsesAwsAuth(Model model, ServiceShape service, O } // optionalAuth trait doesn't require authentication. - if (hasOptionalAuthOperation(model, service)) { - return !operation.getTrait(OptionalAuthTrait.class).isPresent(); + if (isSigV4Service(service) && hasOptionalAuthOperation(model, service)) { + return !operation.hasTrait(OptionalAuthTrait.class); } return false; } @@ -197,7 +204,7 @@ private static boolean hasOptionalAuthOperation(Model model, ServiceShape servic TopDownIndex topDownIndex = TopDownIndex.of(model); Set operations = topDownIndex.getContainedOperations(service); for (OperationShape operation : operations) { - if (operation.getTrait(OptionalAuthTrait.class).isPresent()) { + if (operation.hasTrait(OptionalAuthTrait.class)) { return true; } } @@ -208,7 +215,7 @@ private static boolean areAllOptionalAuthOperations(Model model, ServiceShape se TopDownIndex topDownIndex = TopDownIndex.of(model); Set operations = topDownIndex.getContainedOperations(service); for (OperationShape operation : operations) { - if (!operation.getTrait(OptionalAuthTrait.class).isPresent()) { + if (!operation.hasTrait(OptionalAuthTrait.class)) { return false; } } diff --git a/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddAwsRuntimeConfig.java b/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddAwsRuntimeConfig.java index 1d230838034d..cd3213bde692 100644 --- a/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddAwsRuntimeConfig.java +++ b/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddAwsRuntimeConfig.java @@ -15,6 +15,9 @@ package software.amazon.smithy.aws.typescript.codegen; +import static software.amazon.smithy.aws.typescript.codegen.AwsTraitsUtils.isAwsService; +import static software.amazon.smithy.aws.typescript.codegen.AwsTraitsUtils.isSigV4Service; + import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -31,6 +34,9 @@ import software.amazon.smithy.typescript.codegen.integration.TypeScriptIntegration; import software.amazon.smithy.utils.MapUtils; +// TODO: This javadoc is specific to needs of AWS client. However it has elements that would be needed by non-AWS +// clients too, like logger, region for SigV4. We should refactor these into different Integration or rename this +// class to be generic. /** * AWS clients need to know the service name for collecting metrics, the * region name used to resolve endpoints, the max attempt to retry a request @@ -82,10 +88,14 @@ public void addConfigInterfaceFields( writer.addImport("Provider", "__Provider", TypeScriptDependency.AWS_SDK_TYPES.packageName); writer.addImport("Logger", "__Logger", TypeScriptDependency.AWS_SDK_TYPES.packageName); - writer.writeDocs("Unique service identifier.\n@internal") - .write("serviceId?: string;\n"); - writer.writeDocs("The AWS region to which this client will send requests") - .write("region?: string | __Provider;\n"); + if (isAwsService(settings, model)) { + writer.writeDocs("Unique service identifier.\n@internal") + .write("serviceId?: string;\n"); + } + if (isSigV4Service(settings, model)) { + writer.writeDocs("The AWS region to which this client will send requests or use as signingRegion") + .write("region?: string | __Provider;\n"); + } writer.writeDocs("Value for how many times a request will be made at most in case of retry.") .write("maxAttempts?: number | __Provider;\n"); writer.writeDocs("Optional logger for logging debug/info/warn/error.") @@ -114,11 +124,17 @@ public Map> getRuntimeConfigWriters( + "trait was found on " + service.getId()); } } - runtimeConfigs.putAll(getDefaultConfig(target)); + runtimeConfigs.putAll(getDefaultConfig(target, settings, model)); return runtimeConfigs; } - private Map> getDefaultConfig(LanguageTarget target) { + private Map> getDefaultConfig( + LanguageTarget target, + TypeScriptSettings settings, + Model model + ) { + Map> defaultConfigs = new HashMap(); + boolean isSigV4Service = isSigV4Service(settings, model); switch (target) { case SHARED: return MapUtils.of( @@ -128,40 +144,46 @@ private Map> getDefaultConfig(LanguageTarget } ); case BROWSER: - return MapUtils.of( - "region", writer -> { - writer.addDependency(TypeScriptDependency.INVALID_DEPENDENCY); - writer.addImport("invalidProvider", "invalidProvider", - TypeScriptDependency.INVALID_DEPENDENCY.packageName); - writer.write("region: invalidProvider(\"Region is missing\"),"); - }, - "maxAttempts", writer -> { - writer.addDependency(TypeScriptDependency.MIDDLEWARE_RETRY); - writer.addImport("DEFAULT_MAX_ATTEMPTS", "DEFAULT_MAX_ATTEMPTS", - TypeScriptDependency.MIDDLEWARE_RETRY.packageName); - writer.write("maxAttempts: DEFAULT_MAX_ATTEMPTS,"); - } - ); + if (isSigV4Service) { + defaultConfigs.put("region", writer -> { + writer.addDependency(TypeScriptDependency.INVALID_DEPENDENCY); + writer.addImport("invalidProvider", "invalidProvider", + TypeScriptDependency.INVALID_DEPENDENCY.packageName); + writer.write("region: invalidProvider(\"Region is missing\"),"); + }); + } + defaultConfigs.put("maxAttempts", writer -> { + writer.addDependency(TypeScriptDependency.MIDDLEWARE_RETRY); + writer.addImport("DEFAULT_MAX_ATTEMPTS", "DEFAULT_MAX_ATTEMPTS", + TypeScriptDependency.MIDDLEWARE_RETRY.packageName); + writer.write("maxAttempts: DEFAULT_MAX_ATTEMPTS,"); + }); + return defaultConfigs; case NODE: - return MapUtils.of( - "region", writer -> { - writer.addDependency(AwsDependency.NODE_CONFIG_PROVIDER); - writer.addImport("loadConfig", "loadNodeConfig", - AwsDependency.NODE_CONFIG_PROVIDER.packageName); - writer.addDependency(TypeScriptDependency.CONFIG_RESOLVER); - writer.addImport("NODE_REGION_CONFIG_OPTIONS", "NODE_REGION_CONFIG_OPTIONS", - TypeScriptDependency.CONFIG_RESOLVER.packageName); - writer.addImport("NODE_REGION_CONFIG_FILE_OPTIONS", "NODE_REGION_CONFIG_FILE_OPTIONS", - TypeScriptDependency.CONFIG_RESOLVER.packageName); - writer.write( + if (isSigV4Service) { + // TODO: For non-AWS service, figure out how the region should be configured. + defaultConfigs.put("region", writer -> { + writer.addDependency(AwsDependency.NODE_CONFIG_PROVIDER); + writer.addImport("loadConfig", "loadNodeConfig", + AwsDependency.NODE_CONFIG_PROVIDER.packageName); + writer.addDependency(TypeScriptDependency.CONFIG_RESOLVER); + writer.addImport("NODE_REGION_CONFIG_OPTIONS", "NODE_REGION_CONFIG_OPTIONS", + TypeScriptDependency.CONFIG_RESOLVER.packageName); + writer.addImport("NODE_REGION_CONFIG_FILE_OPTIONS", "NODE_REGION_CONFIG_FILE_OPTIONS", + TypeScriptDependency.CONFIG_RESOLVER.packageName); + writer.write( "region: loadNodeConfig(NODE_REGION_CONFIG_OPTIONS, NODE_REGION_CONFIG_FILE_OPTIONS),"); - }, - "maxAttempts", writer -> { - writer.addImport("NODE_MAX_ATTEMPT_CONFIG_OPTIONS", "NODE_MAX_ATTEMPT_CONFIG_OPTIONS", - TypeScriptDependency.MIDDLEWARE_RETRY.packageName); - writer.write("maxAttempts: loadNodeConfig(NODE_MAX_ATTEMPT_CONFIG_OPTIONS),"); - } - ); + }); + } + defaultConfigs.put("maxAttempts", writer -> { + writer.addDependency(AwsDependency.NODE_CONFIG_PROVIDER); + writer.addImport("loadConfig", "loadNodeConfig", + AwsDependency.NODE_CONFIG_PROVIDER.packageName); + writer.addImport("NODE_MAX_ATTEMPT_CONFIG_OPTIONS", "NODE_MAX_ATTEMPT_CONFIG_OPTIONS", + TypeScriptDependency.MIDDLEWARE_RETRY.packageName); + writer.write("maxAttempts: loadNodeConfig(NODE_MAX_ATTEMPT_CONFIG_OPTIONS),"); + }); + return defaultConfigs; default: return Collections.emptyMap(); } diff --git a/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddBuiltinPlugins.java b/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddBuiltinPlugins.java index c34b801ed786..f9561dda282c 100644 --- a/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddBuiltinPlugins.java +++ b/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddBuiltinPlugins.java @@ -15,6 +15,7 @@ package software.amazon.smithy.aws.typescript.codegen; +import static software.amazon.smithy.aws.typescript.codegen.AwsTraitsUtils.isAwsService; import static software.amazon.smithy.typescript.codegen.integration.RuntimeClientPlugin.Convention.HAS_CONFIG; import static software.amazon.smithy.typescript.codegen.integration.RuntimeClientPlugin.Convention.HAS_MIDDLEWARE; @@ -44,9 +45,16 @@ public List getClientPlugins() { return ListUtils.of( RuntimeClientPlugin.builder() .withConventions(TypeScriptDependency.CONFIG_RESOLVER.dependency, "Region", HAS_CONFIG) + .servicePredicate((m, s) -> isAwsService(s)) .build(), + // Only one of Endpoints or CustomEndpoints should be used RuntimeClientPlugin.builder() .withConventions(TypeScriptDependency.CONFIG_RESOLVER.dependency, "Endpoints", HAS_CONFIG) + .servicePredicate((m, s) -> isAwsService(s)) + .build(), + RuntimeClientPlugin.builder() + .withConventions(TypeScriptDependency.CONFIG_RESOLVER.dependency, "CustomEndpoints", HAS_CONFIG) + .servicePredicate((m, s) -> !isAwsService(s)) .build(), RuntimeClientPlugin.builder() .withConventions(TypeScriptDependency.MIDDLEWARE_RETRY.dependency, "Retry") diff --git a/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddUserAgentDependency.java b/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddUserAgentDependency.java index 485c9f198913..9158c06914d8 100644 --- a/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddUserAgentDependency.java +++ b/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddUserAgentDependency.java @@ -15,6 +15,8 @@ package software.amazon.smithy.aws.typescript.codegen; +import static software.amazon.smithy.aws.typescript.codegen.AwsTraitsUtils.isAwsService; + import java.util.Collections; import java.util.List; import java.util.Map; @@ -33,12 +35,15 @@ /** * Add client plubins and configs to support injecting user agent. */ +// TODO: Looks to add this back for non-AWS service clients, by fixing the dependency on ClientSharedValues.serviceId public class AddUserAgentDependency implements TypeScriptIntegration { @Override public List getClientPlugins() { return ListUtils.of( RuntimeClientPlugin.builder() - .withConventions(AwsDependency.MIDDLEWARE_USER_AGENT.dependency, "UserAgent").build()); + .withConventions(AwsDependency.MIDDLEWARE_USER_AGENT.dependency, "UserAgent") + .servicePredicate((m, s) -> isAwsService(s)) + .build()); } @Override @@ -48,6 +53,9 @@ public void addConfigInterfaceFields( SymbolProvider symbolProvider, TypeScriptWriter writer ) { + if (!isAwsService(settings, model)) { + return; + } writer.addImport("Provider", "Provider", TypeScriptDependency.AWS_SDK_TYPES.packageName); writer.addImport("UserAgent", "__UserAgent", TypeScriptDependency.AWS_SDK_TYPES.packageName); writer.writeDocs("The provider populating default tracking information to be sent with `user-agent`, " @@ -62,6 +70,9 @@ public Map> getRuntimeConfigWriters( SymbolProvider symbolProvider, LanguageTarget target ) { + if (!isAwsService(settings, model)) { + return Collections.emptyMap(); + } switch (target) { case NODE: return MapUtils.of( diff --git a/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AwsEndpointGeneratorIntegration.java b/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AwsEndpointGeneratorIntegration.java index 35a0dde5f773..64c26c3a6f1e 100644 --- a/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AwsEndpointGeneratorIntegration.java +++ b/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AwsEndpointGeneratorIntegration.java @@ -15,6 +15,8 @@ package software.amazon.smithy.aws.typescript.codegen; +import static software.amazon.smithy.aws.typescript.codegen.AwsTraitsUtils.isAwsService; + import java.util.Collections; import java.util.Map; import java.util.function.BiConsumer; @@ -39,6 +41,10 @@ public void writeAdditionalFiles( SymbolProvider symbolProvider, BiConsumer> writerFactory ) { + if (!settings.generateClient() || !isAwsService(settings, model)) { + return; + } + writerFactory.accept("endpoints.ts", writer -> { new EndpointGenerator(settings.getService(model), writer).run(); }); @@ -51,6 +57,10 @@ public void addConfigInterfaceFields( SymbolProvider symbolProvider, TypeScriptWriter writer ) { + if (!settings.generateClient() || !isAwsService(settings, model)) { + return; + } + writer.addImport("RegionInfoProvider", "RegionInfoProvider", TypeScriptDependency.AWS_SDK_TYPES.packageName); writer.writeDocs("Fetch related hostname, signing name or signing region with given region."); writer.write("regionInfoProvider?: RegionInfoProvider;\n"); @@ -63,6 +73,10 @@ public Map> getRuntimeConfigWriters( SymbolProvider symbolProvider, LanguageTarget target ) { + if (!settings.generateClient() || !isAwsService(settings, model)) { + return Collections.emptyMap(); + } + switch (target) { case SHARED: return MapUtils.of("regionInfoProvider", writer -> { diff --git a/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AwsPackageFixturesGeneratorIntegration.java b/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AwsPackageFixturesGeneratorIntegration.java index 791802662f0c..167f0a3e23e7 100644 --- a/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AwsPackageFixturesGeneratorIntegration.java +++ b/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AwsPackageFixturesGeneratorIntegration.java @@ -15,6 +15,8 @@ package software.amazon.smithy.aws.typescript.codegen; +import static software.amazon.smithy.aws.typescript.codegen.AwsTraitsUtils.isAwsService; + import java.util.Arrays; import java.util.Calendar; import java.util.function.BiConsumer; @@ -55,6 +57,12 @@ public void writeAdditionalFiles( resource = resource.replace("${year}", Integer.toString(Calendar.getInstance().get(Calendar.YEAR))); writer.write(resource); }); + + // TODO: May need to generate a different/modified README.md for these cases + if (!settings.generateClient() || !isAwsService(settings, model)) { + return; + } + writerFactory.accept("README.md", writer -> { ServiceShape service = settings.getService(model); String resource = IoUtils.readUtf8Resource(getClass(), "README.md.template"); 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 2500effab6f4..def50877aaf9 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 @@ -221,6 +221,10 @@ static boolean writeXmlNamespace(GenerationContext context, Shape shape, String * @param context The generation context. */ static void addItempotencyAutofillImport(GenerationContext context) { + // servers do not autogenerate idempotency tokens during deserialization + if (!context.getSettings().generateClient()) { + return; + } context.getModel().shapes(MemberShape.class) .filter(memberShape -> memberShape.hasTrait(IdempotencyTokenTrait.class)) .findFirst() diff --git a/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AwsServiceIdIntegration.java b/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AwsServiceIdIntegration.java index fe00dc300c51..045391406265 100644 --- a/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AwsServiceIdIntegration.java +++ b/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AwsServiceIdIntegration.java @@ -44,6 +44,7 @@ public SymbolProvider decorateSymbolProvider( return symbol; } + // TODO: Should this WARNING be avoided somehow if client is not for an AWS service? // If the SDK service ID trait is present, use that, otherwise fall back to // the default naming strategy for the service. return shape.getTrait(ServiceTrait.class) diff --git a/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AwsTraitsUtils.java b/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AwsTraitsUtils.java new file mode 100644 index 000000000000..465037596e0e --- /dev/null +++ b/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AwsTraitsUtils.java @@ -0,0 +1,46 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.smithy.aws.typescript.codegen; + +import software.amazon.smithy.aws.traits.ServiceTrait; +import software.amazon.smithy.aws.traits.auth.SigV4Trait; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.shapes.ServiceShape; +import software.amazon.smithy.typescript.codegen.TypeScriptSettings; + +/** + * Utility methods related to AWS traits. + */ +final class AwsTraitsUtils { + + private AwsTraitsUtils() {} + + static boolean isAwsService(TypeScriptSettings settings, Model model) { + return isAwsService(settings.getService(model)); + } + + static boolean isAwsService(ServiceShape serviceShape) { + return serviceShape.hasTrait(ServiceTrait.class); + } + + static boolean isSigV4Service(TypeScriptSettings settings, Model model) { + return isSigV4Service(settings.getService(model)); + } + + static boolean isSigV4Service(ServiceShape serviceShape) { + return serviceShape.hasTrait(SigV4Trait.class); + } +} 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 769b919321d4..4418472dc718 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 @@ -90,6 +90,12 @@ protected void writeDefaultHeaders(GenerationContext context, OperationShape ope AwsProtocolUtils.generateUnsignedPayloadSigV4Header(context, operation); } + @Override + protected void writeDefaultErrorHeaders(GenerationContext context, StructureShape error) { + super.writeDefaultErrorHeaders(context, error); + context.getWriter().write("'x-amzn-errortype': $S,", error.getId().getName()); + } + @Override public void serializeInputDocument( GenerationContext context, diff --git a/codegen/smithy-aws-typescript-codegen/src/test/resources/software/amazon/smithy/aws/typescript/codegen/NotSame.smithy b/codegen/smithy-aws-typescript-codegen/src/test/resources/software/amazon/smithy/aws/typescript/codegen/NotSame.smithy index 24c748af32e3..3401b6f3c41c 100644 --- a/codegen/smithy-aws-typescript-codegen/src/test/resources/software/amazon/smithy/aws/typescript/codegen/NotSame.smithy +++ b/codegen/smithy-aws-typescript-codegen/src/test/resources/software/amazon/smithy/aws/typescript/codegen/NotSame.smithy @@ -4,6 +4,7 @@ use aws.protocols#restJson1 @aws.api#service(sdkId: "Not Same") @restJson1 +@aws.auth#sigv4(name: "notsame") service OriginalName { version: "2019-10-15", operations: [GetFoo] diff --git a/packages/config-resolver/src/CustomEndpointsConfig.spec.ts b/packages/config-resolver/src/CustomEndpointsConfig.spec.ts new file mode 100644 index 000000000000..39ea547ad38a --- /dev/null +++ b/packages/config-resolver/src/CustomEndpointsConfig.spec.ts @@ -0,0 +1,64 @@ +import { Endpoint } from "@aws-sdk/types"; + +import { resolveCustomEndpointsConfig } from "./CustomEndpointsConfig"; + +describe("CustomEndpointsConfig", () => { + const urlParser = jest.fn(); + + const input = { urlParser }; + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe("endpoint", () => { + const mockEndpoint: Endpoint = { protocol: "protocol", hostname: "hostname", path: "path" }; + + it("returns output of urlParser if endpoint is of type string", async () => { + const endpoint = "endpoint"; + urlParser.mockReturnValueOnce(mockEndpoint); + const { endpoint: endpointProvider, isCustomEndpoint } = resolveCustomEndpointsConfig({ ...input, endpoint }); + expect(isCustomEndpoint).toBe(true); + const endpointOutput = await endpointProvider(); + expect(endpointOutput).toStrictEqual(mockEndpoint); + expect(urlParser).toHaveBeenCalledTimes(1); + expect(urlParser).toHaveBeenCalledWith(endpoint); + }); + + it("returns promisified endpoint if it's of type object", async () => { + const endpoint = mockEndpoint; + const { endpoint: endpointProvider, isCustomEndpoint } = resolveCustomEndpointsConfig({ ...input, endpoint }); + expect(isCustomEndpoint).toBe(true); + const endpointOutput = await endpointProvider(); + expect(endpointOutput).toStrictEqual(endpoint); + expect(urlParser).not.toHaveBeenCalled(); + }); + + it("returns endpoint if it's already Provider", async () => { + const endpoint = () => Promise.resolve(mockEndpoint); + const { endpoint: endpointProvider, isCustomEndpoint } = resolveCustomEndpointsConfig({ ...input, endpoint }); + expect(isCustomEndpoint).toBe(true); + const endpointOutput = await endpointProvider(); + expect(endpointOutput).toStrictEqual(mockEndpoint); + expect(urlParser).not.toHaveBeenCalled(); + }); + }); + + describe("tls", () => { + const endpoint = "endpoint"; + + beforeEach(() => { + urlParser.mockReturnValueOnce({ protocol: "protocol", hostname: "hostname", path: "path" }); + }); + + [true, false].forEach((tls) => { + it(`returns input.tls when it's ${tls}`, () => { + expect(resolveCustomEndpointsConfig({ ...input, endpoint, tls }).tls).toStrictEqual(tls); + }); + }); + + it("returns true is input.tls is undefined", () => { + expect(resolveCustomEndpointsConfig({ ...input, endpoint }).tls).toStrictEqual(true); + }); + }); +}); diff --git a/packages/config-resolver/src/CustomEndpointsConfig.ts b/packages/config-resolver/src/CustomEndpointsConfig.ts new file mode 100644 index 000000000000..e8c1291ade5c --- /dev/null +++ b/packages/config-resolver/src/CustomEndpointsConfig.ts @@ -0,0 +1,43 @@ +import { Endpoint, Provider, UrlParser } from "@aws-sdk/types"; + +export interface CustomEndpointsInputConfig { + /** + * The fully qualified endpoint of the webservice. + */ + endpoint: string | Endpoint | Provider; + + /** + * Whether TLS is enabled for requests. + */ + tls?: boolean; +} + +interface PreviouslyResolved { + urlParser: UrlParser; +} + +export interface CustomEndpointsResolvedConfig extends Required { + endpoint: Provider; + isCustomEndpoint: true; +} + +export const resolveCustomEndpointsConfig = ( + input: T & CustomEndpointsInputConfig & PreviouslyResolved +): T & CustomEndpointsResolvedConfig => ({ + ...input, + tls: input.tls ?? true, + endpoint: normalizeEndpoint(input), + isCustomEndpoint: true, +}); + +const normalizeEndpoint = (input: CustomEndpointsInputConfig & PreviouslyResolved): Provider => { + const { endpoint, urlParser } = input; + if (typeof endpoint === "string") { + const promisified = Promise.resolve(urlParser(endpoint)); + return () => promisified; + } else if (typeof endpoint === "object") { + const promisified = Promise.resolve(endpoint); + return () => promisified; + } + return endpoint; +}; diff --git a/packages/config-resolver/src/index.ts b/packages/config-resolver/src/index.ts index fcf9542bc29f..47d04a39807c 100644 --- a/packages/config-resolver/src/index.ts +++ b/packages/config-resolver/src/index.ts @@ -1,2 +1,3 @@ +export * from "./CustomEndpointsConfig"; export * from "./EndpointsConfig"; export * from "./RegionConfig";