From 3b3b281b885e3cabcc0902ba8a0ddfd1e4a9dda1 Mon Sep 17 00:00:00 2001 From: Yang Liu Date: Mon, 4 May 2026 20:57:57 -0700 Subject: [PATCH 1/3] Add smithy-cfn-json build plugin Customers deploying API Gateway REST APIs via CloudFormation need Smithy JSON AST output with `Fn::Sub` intrinsic function wrapping so that CFN resolves resource references before passing the body to the SmithyImporter. Without this plugin, customers must go through the OpenAPI conversion path to get `Fn::Sub` support. Add a new `smithy-aws-apigateway-cfn` subproject containing the `smithy-cfn-json` build plugin. The plugin serializes a Smithy model to JSON AST via `ModelSerializer`, then walks the tree with a `NodeVisitor` that wraps `${...}` strings at known trait paths in `{"Fn::Sub": "..."}` objects. Known paths cover integration URIs, credentials, connection IDs, integration targets, authorizer URIs and credentials, and Cognito provider ARNs. --- ...a4027fb498101b16faf7bad26d9048a0ffb2b.json | 5 + .../guides/model-translations/index.rst | 1 + .../model-translations/smithy-cfn-json.rst | 147 ++ settings.gradle.kts | 1 + smithy-aws-apigateway-cfn/README.md | 67 + smithy-aws-apigateway-cfn/build.gradle.kts | 19 + .../cfn/CloudFormationFnSubInjector.java | 114 ++ .../aws/apigateway/cfn/SmithyCfnJson.java | 61 + .../apigateway/cfn/SmithyCfnJsonConfig.java | 54 + ...ware.amazon.smithy.build.SmithyBuildPlugin | 1 + .../cfn/CloudFormationFnSubInjectorTest.java | 133 ++ .../cfn/SmithyCfnJsonConfigTest.java | 35 + .../cfn/SmithyCfnJsonGoldenFileTest.java | 46 + .../cfn/SmithyCfnJsonIntegrationTest.java | 91 + .../aws/apigateway/cfn/SmithyCfnJsonTest.java | 38 + .../cfn/integration-model.expected.json | 1652 +++++++++++++++++ .../apigateway/cfn/integration-model.smithy | 26 + 17 files changed, 2491 insertions(+) create mode 100644 .changes/next-release/feature-91ca4027fb498101b16faf7bad26d9048a0ffb2b.json create mode 100644 docs/source-2.0/guides/model-translations/smithy-cfn-json.rst create mode 100644 smithy-aws-apigateway-cfn/README.md create mode 100644 smithy-aws-apigateway-cfn/build.gradle.kts create mode 100644 smithy-aws-apigateway-cfn/src/main/java/software/amazon/smithy/aws/apigateway/cfn/CloudFormationFnSubInjector.java create mode 100644 smithy-aws-apigateway-cfn/src/main/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJson.java create mode 100644 smithy-aws-apigateway-cfn/src/main/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJsonConfig.java create mode 100644 smithy-aws-apigateway-cfn/src/main/resources/META-INF/services/software.amazon.smithy.build.SmithyBuildPlugin create mode 100644 smithy-aws-apigateway-cfn/src/test/java/software/amazon/smithy/aws/apigateway/cfn/CloudFormationFnSubInjectorTest.java create mode 100644 smithy-aws-apigateway-cfn/src/test/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJsonConfigTest.java create mode 100644 smithy-aws-apigateway-cfn/src/test/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJsonGoldenFileTest.java create mode 100644 smithy-aws-apigateway-cfn/src/test/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJsonIntegrationTest.java create mode 100644 smithy-aws-apigateway-cfn/src/test/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJsonTest.java create mode 100644 smithy-aws-apigateway-cfn/src/test/resources/software/amazon/smithy/aws/apigateway/cfn/integration-model.expected.json create mode 100644 smithy-aws-apigateway-cfn/src/test/resources/software/amazon/smithy/aws/apigateway/cfn/integration-model.smithy diff --git a/.changes/next-release/feature-91ca4027fb498101b16faf7bad26d9048a0ffb2b.json b/.changes/next-release/feature-91ca4027fb498101b16faf7bad26d9048a0ffb2b.json new file mode 100644 index 00000000000..7c8b122b24e --- /dev/null +++ b/.changes/next-release/feature-91ca4027fb498101b16faf7bad26d9048a0ffb2b.json @@ -0,0 +1,5 @@ +{ + "type": "feature", + "description": "Added smithy-cfn-json plugin that serializes Smithy models to JSON AST with CloudFormation Fn Sub intrinsic functions", + "pull_requests": [] +} diff --git a/docs/source-2.0/guides/model-translations/index.rst b/docs/source-2.0/guides/model-translations/index.rst index e92ca181a3e..2a631c4d29c 100644 --- a/docs/source-2.0/guides/model-translations/index.rst +++ b/docs/source-2.0/guides/model-translations/index.rst @@ -9,6 +9,7 @@ formats. :maxdepth: 1 converting-to-openapi + smithy-cfn-json migrating-idl-1-to-2 generating-cloudformation-resources diff --git a/docs/source-2.0/guides/model-translations/smithy-cfn-json.rst b/docs/source-2.0/guides/model-translations/smithy-cfn-json.rst new file mode 100644 index 00000000000..8f1135778a1 --- /dev/null +++ b/docs/source-2.0/guides/model-translations/smithy-cfn-json.rst @@ -0,0 +1,147 @@ +.. _smithy-cfn-json: + +========================================================== +Converting Smithy to CloudFormation JSON +========================================================== + +This guide describes how Smithy models can be serialized to JSON AST with +CloudFormation ``Fn::Sub`` intrinsic function wrapping using the +``smithy-cfn-json`` plugin. + +------------ +Introduction +------------ + +The ``smithy-cfn-json`` plugin serializes a Smithy model to its JSON AST +representation with automatic CloudFormation ``Fn::Sub`` substitution wrapping. +The output is intended for use as the ``Body`` property of an +``AWS::ApiGateway::RestApi`` CloudFormation resource, enabling direct Smithy +model import without OpenAPI conversion. + +String values containing ``${...}`` variable syntax at known trait paths are +automatically wrapped in ``{"Fn::Sub": "..."}`` objects so that CloudFormation +resolves the references at deploy time before passing the body to the API +Gateway SmithyImporter. + +.. _smithy-cfn-json-configuration: + +----------------------------------------------- +Converting to JSON AST with smithy-build +----------------------------------------------- + +The ``smithy-cfn-json`` plugin contained in the +``software.amazon.smithy:smithy-aws-apigateway-cfn`` package can be used with +smithy-build to produce CloudFormation-ready JSON from Smithy models. + +The following example shows how to configure the plugin in +``smithy-build.json``: + +.. code-block:: json + :caption: smithy-build.json + + { + "version": "1.0", + "plugins": { + "smithy-cfn-json": { + "service": "com.example#MyService" + } + } + } + +The plugin writes ``{ServiceName}.smithy.json`` to the build output directory. + +.. _smithy-cfn-json-settings: + +---------------------- +Configuration settings +---------------------- + +.. _smithy-cfn-json-setting-service: + +service (``string``) +==================== + +**Required**. The Smithy service :ref:`shape ID ` to export. + +.. code-block:: json + :caption: smithy-build.json + + { + "version": "1.0", + "plugins": { + "smithy-cfn-json": { + "service": "com.example#MyService" + } + } + } + +.. _smithy-cfn-json-setting-disableCloudFormationSubstitution: + +disableCloudFormationSubstitution (``boolean``) +=============================================== + +Set to ``true`` to disable automatic ``Fn::Sub`` wrapping of string values +that contain ``${...}`` variable references. Defaults to ``false``. + +.. code-block:: json + :caption: smithy-build.json + + { + "version": "1.0", + "plugins": { + "smithy-cfn-json": { + "service": "com.example#MyService", + "disableCloudFormationSubstitution": true + } + } + } + +.. _smithy-cfn-json-substitution: + +------------------------------ +CloudFormation substitution +------------------------------ + +When ``disableCloudFormationSubstitution`` is ``false`` (the default), string +values containing ``${...}`` variable syntax at the following trait paths are +automatically wrapped in ``{"Fn::Sub": "..."}`` objects: + +* ``aws.apigateway#integration`` — ``uri``, ``credentials``, ``connectionId``, + ``integrationTarget`` +* ``aws.apigateway#authorizers`` — ``*/uri``, ``*/credentials`` +* ``aws.auth#cognitoUserPools`` — ``providerArns/*`` + +CloudFormation resolves ``Fn::Sub`` at deploy time before passing the body to +the API Gateway SmithyImporter. + +.. _smithy-cfn-json-example: + +------- +Example +------- + +Given the following Smithy model input: + +.. code-block:: smithy + + @integration( + type: "aws_proxy" + uri: "${MyLambdaFunction.Arn}" + httpMethod: "POST" + credentials: "${ApiGatewayRole.Arn}" + ) + +The plugin produces the following in the generated JSON AST: + +.. code-block:: json + + { + "aws.apigateway#integration": { + "type": "aws_proxy", + "uri": {"Fn::Sub": "${MyLambdaFunction.Arn}"}, + "httpMethod": "POST", + "credentials": {"Fn::Sub": "${ApiGatewayRole.Arn}"} + } + } + +Values that do not contain ``${...}`` syntax are left as plain strings. diff --git a/settings.gradle.kts b/settings.gradle.kts index e2ccdbdbca2..84d3ee5eaa9 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -12,6 +12,7 @@ include(":smithy-aws-iam-traits") include(":smithy-aws-traits") include(":smithy-aws-apigateway-traits") include(":smithy-aws-apigateway-openapi") +include(":smithy-aws-apigateway-cfn") include(":smithy-aws-protocol-tests") include(":smithy-cli") include(":smithy-codegen-core") diff --git a/smithy-aws-apigateway-cfn/README.md b/smithy-aws-apigateway-cfn/README.md new file mode 100644 index 00000000000..4bdbdc318f8 --- /dev/null +++ b/smithy-aws-apigateway-cfn/README.md @@ -0,0 +1,67 @@ +# Smithy AWS API Gateway CloudFormation JSON Plugin + +This module provides a `smithy-build` plugin that serializes a Smithy model to +JSON AST with CloudFormation `Fn::Sub` intrinsic function wrapping. The output +is intended for use as the `Body` property of an `AWS::ApiGateway::RestApi` +CloudFormation resource, enabling direct Smithy model import without OpenAPI +conversion. + +## Usage + +Add the following to your `smithy-build.json`: + +```json +{ + "version": "1.0", + "plugins": { + "smithy-cfn-json": { + "service": "com.example#MyService" + } + } +} +``` + +### Configuration + +| Property | Type | Required | Default | Description | +|----------|------|----------|---------|-------------| +| `service` | ShapeId | Yes | — | The service shape to export | +| `disableCloudFormationSubstitution` | boolean | No | `false` | Disable `Fn::Sub` wrapping | + +### Output + +The plugin writes `{ServiceName}.smithy.json` to the build output directory. + +## CloudFormation Substitution + +String values containing `${...}` variable syntax at the following trait paths +are automatically wrapped in `{"Fn::Sub": "..."}` objects: + +- `aws.apigateway#integration` → `uri`, `credentials`, `connectionId`, `integrationTarget` +- `aws.apigateway#authorizers` → `*/uri`, `*/credentials` +- `aws.auth#cognitoUserPools` → `providerArns/*` + +### Example + +Input (Smithy IDL): +```smithy +@integration( + type: "aws_proxy" + uri: "${MyLambdaFunction.Arn}" + httpMethod: "POST" + credentials: "${ApiGatewayRole.Arn}" +) +``` + +Output (in the generated JSON AST): +```json +"aws.apigateway#integration": { + "type": "aws_proxy", + "uri": {"Fn::Sub": "${MyLambdaFunction.Arn}"}, + "httpMethod": "POST", + "credentials": {"Fn::Sub": "${ApiGatewayRole.Arn}"} +} +``` + +CloudFormation resolves `Fn::Sub` at deploy time before passing the body to +the API Gateway SmithyImporter. diff --git a/smithy-aws-apigateway-cfn/build.gradle.kts b/smithy-aws-apigateway-cfn/build.gradle.kts new file mode 100644 index 00000000000..b9251bc9296 --- /dev/null +++ b/smithy-aws-apigateway-cfn/build.gradle.kts @@ -0,0 +1,19 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +plugins { + id("smithy.module-conventions") +} + +description = "This module provides a smithy-build plugin that serializes a Smithy model " + + "to JSON AST with CloudFormation Fn::Sub substitution for use as a CFN RestApi Body." + +extra["displayName"] = "Smithy :: Amazon API Gateway CloudFormation JSON" +extra["moduleName"] = "software.amazon.smithy.aws.apigateway.cfn" + +dependencies { + api(project(":smithy-build")) + api(project(":smithy-model")) + testImplementation(project(":smithy-aws-apigateway-traits")) +} diff --git a/smithy-aws-apigateway-cfn/src/main/java/software/amazon/smithy/aws/apigateway/cfn/CloudFormationFnSubInjector.java b/smithy-aws-apigateway-cfn/src/main/java/software/amazon/smithy/aws/apigateway/cfn/CloudFormationFnSubInjector.java new file mode 100644 index 00000000000..25831e2a37e --- /dev/null +++ b/smithy-aws-apigateway-cfn/src/main/java/software/amazon/smithy/aws/apigateway/cfn/CloudFormationFnSubInjector.java @@ -0,0 +1,114 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package software.amazon.smithy.aws.apigateway.cfn; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Deque; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import software.amazon.smithy.model.SourceLocation; +import software.amazon.smithy.model.node.ArrayNode; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.NodeVisitor; +import software.amazon.smithy.model.node.ObjectNode; +import software.amazon.smithy.model.node.StringNode; + +/** + * Walks a Smithy JSON AST node tree and wraps string values containing + * CloudFormation variable syntax in Fn::Sub intrinsic function objects. + */ +final class CloudFormationFnSubInjector extends NodeVisitor.Default { + + private static final Logger LOGGER = Logger.getLogger(CloudFormationFnSubInjector.class.getName()); + private static final String SUBSTITUTION_KEY = "Fn::Sub"; + private static final Pattern SUBSTITUTION_PATTERN = Pattern.compile("\\$\\{.+}"); + + static final List PATHS = Arrays.asList( + "shapes/*/traits/aws.apigateway#integration/uri", + "shapes/*/traits/aws.apigateway#integration/credentials", + "shapes/*/traits/aws.apigateway#integration/connectionId", + "shapes/*/traits/aws.apigateway#integration/integrationTarget", + "shapes/*/traits/aws.apigateway#authorizers/*/uri", + "shapes/*/traits/aws.apigateway#authorizers/*/credentials", + "shapes/*/traits/aws.auth#cognitoUserPools/providerArns/*"); + + private final Deque stack = new ArrayDeque<>(); + private final List paths; + + CloudFormationFnSubInjector() { + this(PATHS); + } + + CloudFormationFnSubInjector(List paths) { + this.paths = paths.stream() + .map(path -> path.split(Pattern.quote("/"))) + .collect(Collectors.toList()); + } + + @Override + protected Node getDefault(Node node) { + return node; + } + + @Override + public Node arrayNode(ArrayNode node) { + List result = new ArrayList<>(); + for (int i = 0; i < node.size(); i++) { + Node member = node.get(i).get(); + stack.addLast(String.valueOf(i)); + result.add(member.accept(this)); + stack.removeLast(); + } + return new ArrayNode(result, SourceLocation.NONE); + } + + @Override + public Node objectNode(ObjectNode node) { + Map result = new LinkedHashMap<>(); + for (Map.Entry entry : node.getMembers().entrySet()) { + stack.addLast(entry.getKey().getValue()); + result.put(entry.getKey(), entry.getValue().accept(this)); + stack.removeLast(); + } + return new ObjectNode(result, SourceLocation.NONE); + } + + @Override + public Node stringNode(StringNode node) { + if (SUBSTITUTION_PATTERN.matcher(node.getValue()).find() && isInPath()) { + LOGGER.fine(() -> String.format( + "Wrapping CloudFormation variable in Fn::Sub at path %s: %s", + String.join("/", stack), + node.getValue())); + return Node.objectNode().withMember(SUBSTITUTION_KEY, node); + } + return node; + } + + private boolean isInPath() { + return paths.stream().anyMatch(this::matchesPath); + } + + private boolean matchesPath(String[] path) { + Iterator iterator = stack.iterator(); + for (String segment : path) { + if (!iterator.hasNext()) { + return false; + } + String current = iterator.next(); + if (!segment.equals(current) && !segment.equals("*")) { + return false; + } + } + return true; + } +} diff --git a/smithy-aws-apigateway-cfn/src/main/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJson.java b/smithy-aws-apigateway-cfn/src/main/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJson.java new file mode 100644 index 00000000000..fa7c88a4251 --- /dev/null +++ b/smithy-aws-apigateway-cfn/src/main/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJson.java @@ -0,0 +1,61 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package software.amazon.smithy.aws.apigateway.cfn; + +import software.amazon.smithy.build.PluginContext; +import software.amazon.smithy.build.SmithyBuildException; +import software.amazon.smithy.build.SmithyBuildPlugin; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.node.NodeMapper; +import software.amazon.smithy.model.node.ObjectNode; +import software.amazon.smithy.model.shapes.ModelSerializer; +import software.amazon.smithy.model.shapes.ServiceShape; +import software.amazon.smithy.model.shapes.ShapeId; + +/** + * Serializes a Smithy model to JSON AST with CloudFormation Fn::Sub + * substitution for use as a CFN RestApi Body. + * + *

This plugin is configured using {@link SmithyCfnJsonConfig}. + */ +public final class SmithyCfnJson implements SmithyBuildPlugin { + + @Override + public String getName() { + return "smithy-cfn-json"; + } + + @Override + public boolean requiresValidModel() { + return true; + } + + @Override + public void execute(PluginContext context) { + SmithyCfnJsonConfig config = new NodeMapper().deserialize( + context.getSettings(), + SmithyCfnJsonConfig.class); + + ShapeId serviceId = config.getService(); + if (serviceId == null) { + throw new SmithyBuildException( + "smithy-cfn-json plugin requires a 'service' configuration property"); + } + + Model model = context.getModel(); + ServiceShape service = model.expectShape(serviceId, ServiceShape.class); + + ObjectNode astNode = ModelSerializer.builder().build().serialize(model); + + if (!config.getDisableCloudFormationSubstitution()) { + astNode = astNode.accept(new CloudFormationFnSubInjector()).expectObjectNode(); + } + + context.getFileManifest() + .writeJson( + service.getId().getName() + ".smithy.json", + astNode); + } +} diff --git a/smithy-aws-apigateway-cfn/src/main/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJsonConfig.java b/smithy-aws-apigateway-cfn/src/main/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJsonConfig.java new file mode 100644 index 00000000000..4ca88ca68e2 --- /dev/null +++ b/smithy-aws-apigateway-cfn/src/main/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJsonConfig.java @@ -0,0 +1,54 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package software.amazon.smithy.aws.apigateway.cfn; + +import software.amazon.smithy.model.shapes.ShapeId; + +/** + * Configuration for the {@code smithy-cfn-json} plugin. + * + *

Example {@code smithy-build.json} configuration: + *

{@code
+ * {
+ *   "plugins": {
+ *     "smithy-cfn-json": {
+ *       "service": "com.example#MyService",
+ *       "disableCloudFormationSubstitution": false
+ *     }
+ *   }
+ * }
+ * }
+ */ +public final class SmithyCfnJsonConfig { + + private ShapeId service; + private boolean disableCloudFormationSubstitution; + + /** + * Gets the service shape ID to export. + * + * @return Returns the service shape ID. + */ + public ShapeId getService() { + return service; + } + + public void setService(ShapeId service) { + this.service = service; + } + + /** + * Gets whether CloudFormation substitution is disabled. + * + * @return Returns true if substitution is disabled. + */ + public boolean getDisableCloudFormationSubstitution() { + return disableCloudFormationSubstitution; + } + + public void setDisableCloudFormationSubstitution(boolean disableCloudFormationSubstitution) { + this.disableCloudFormationSubstitution = disableCloudFormationSubstitution; + } +} diff --git a/smithy-aws-apigateway-cfn/src/main/resources/META-INF/services/software.amazon.smithy.build.SmithyBuildPlugin b/smithy-aws-apigateway-cfn/src/main/resources/META-INF/services/software.amazon.smithy.build.SmithyBuildPlugin new file mode 100644 index 00000000000..2f0a42d9e2c --- /dev/null +++ b/smithy-aws-apigateway-cfn/src/main/resources/META-INF/services/software.amazon.smithy.build.SmithyBuildPlugin @@ -0,0 +1 @@ +software.amazon.smithy.aws.apigateway.cfn.SmithyCfnJson diff --git a/smithy-aws-apigateway-cfn/src/test/java/software/amazon/smithy/aws/apigateway/cfn/CloudFormationFnSubInjectorTest.java b/smithy-aws-apigateway-cfn/src/test/java/software/amazon/smithy/aws/apigateway/cfn/CloudFormationFnSubInjectorTest.java new file mode 100644 index 00000000000..11957a11c7b --- /dev/null +++ b/smithy-aws-apigateway-cfn/src/test/java/software/amazon/smithy/aws/apigateway/cfn/CloudFormationFnSubInjectorTest.java @@ -0,0 +1,133 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package software.amazon.smithy.aws.apigateway.cfn; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; + +import org.junit.jupiter.api.Test; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.ObjectNode; + +public class CloudFormationFnSubInjectorTest { + + @Test + public void substitutesIntegrationUri() { + ObjectNode input = buildIntegrationNode("uri", "${MyLambda.Arn}"); + ObjectNode result = input.accept(new CloudFormationFnSubInjector()).expectObjectNode(); + + assertThat(getIntegrationField(result, "uri"), + equalTo(Node.objectNode().withMember("Fn::Sub", "${MyLambda.Arn}"))); + } + + @Test + public void substitutesIntegrationCredentials() { + ObjectNode input = buildIntegrationNode("credentials", "${Role.Arn}"); + ObjectNode result = input.accept(new CloudFormationFnSubInjector()).expectObjectNode(); + + assertThat(getIntegrationField(result, "credentials"), + equalTo(Node.objectNode().withMember("Fn::Sub", "${Role.Arn}"))); + } + + @Test + public void substitutesIntegrationTarget() { + ObjectNode input = buildIntegrationNode("integrationTarget", "${ALB.Arn}"); + ObjectNode result = input.accept(new CloudFormationFnSubInjector()).expectObjectNode(); + + assertThat(getIntegrationField(result, "integrationTarget"), + equalTo(Node.objectNode().withMember("Fn::Sub", "${ALB.Arn}"))); + } + + @Test + public void doesNotSubstituteWithoutPattern() { + ObjectNode input = buildIntegrationNode("uri", "https://example.com"); + ObjectNode result = input.accept(new CloudFormationFnSubInjector()).expectObjectNode(); + + assertThat(getIntegrationField(result, "uri"), + equalTo(Node.from("https://example.com"))); + } + + @Test + public void doesNotSubstituteAtNonKnownPath() { + // "type" is not a known path for substitution + ObjectNode input = buildIntegrationNode("type", "${SomeVar}"); + ObjectNode result = input.accept(new CloudFormationFnSubInjector()).expectObjectNode(); + + assertThat(getIntegrationField(result, "type"), + equalTo(Node.from("${SomeVar}"))); + } + + @Test + public void wildcardMatchesAnyShapeId() { + // Different shape IDs should all match the wildcard + ObjectNode input = buildShapesNode("com.foo#Op1", "uri", "${Lambda.Arn}"); + ObjectNode result = input.accept(new CloudFormationFnSubInjector()).expectObjectNode(); + + Node uri = result.expectObjectMember("shapes") + .expectObjectMember("com.foo#Op1") + .expectObjectMember("traits") + .expectObjectMember("aws.apigateway#integration") + .expectMember("uri"); + assertThat(uri, equalTo(Node.objectNode().withMember("Fn::Sub", "${Lambda.Arn}"))); + } + + @Test + public void preservesNonStringNodes() { + ObjectNode input = Node.objectNodeBuilder() + .withMember("shapes", + Node.objectNodeBuilder() + .withMember("ns#Op", + Node.objectNodeBuilder() + .withMember("traits", + Node.objectNodeBuilder() + .withMember("aws.apigateway#integration", + Node.objectNodeBuilder() + .withMember("uri", "${Fn.Arn}") + .withMember("timeoutInMillis", 29000) + .build()) + .build()) + .build()) + .build()) + .build(); + + ObjectNode result = input.accept(new CloudFormationFnSubInjector()).expectObjectNode(); + Node timeout = result.expectObjectMember("shapes") + .expectObjectMember("ns#Op") + .expectObjectMember("traits") + .expectObjectMember("aws.apigateway#integration") + .expectMember("timeoutInMillis"); + assertThat(timeout, equalTo(Node.from(29000))); + } + + private static ObjectNode buildIntegrationNode(String field, String value) { + return buildShapesNode("ns#Op", field, value); + } + + private static ObjectNode buildShapesNode(String shapeId, String field, String value) { + return Node.objectNodeBuilder() + .withMember("shapes", + Node.objectNodeBuilder() + .withMember(shapeId, + Node.objectNodeBuilder() + .withMember("traits", + Node.objectNodeBuilder() + .withMember("aws.apigateway#integration", + Node.objectNodeBuilder() + .withMember(field, value) + .build()) + .build()) + .build()) + .build()) + .build(); + } + + private static Node getIntegrationField(ObjectNode root, String field) { + return root.expectObjectMember("shapes") + .expectObjectMember("ns#Op") + .expectObjectMember("traits") + .expectObjectMember("aws.apigateway#integration") + .expectMember(field); + } +} diff --git a/smithy-aws-apigateway-cfn/src/test/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJsonConfigTest.java b/smithy-aws-apigateway-cfn/src/test/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJsonConfigTest.java new file mode 100644 index 00000000000..d24b6df1d26 --- /dev/null +++ b/smithy-aws-apigateway-cfn/src/test/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJsonConfigTest.java @@ -0,0 +1,35 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package software.amazon.smithy.aws.apigateway.cfn; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import org.junit.jupiter.api.Test; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.NodeMapper; +import software.amazon.smithy.model.shapes.ShapeId; + +public class SmithyCfnJsonConfigTest { + @Test + public void deserializesFullConfig() { + Node node = Node.parse("{\"service\": \"com.example#MyService\", " + + "\"disableCloudFormationSubstitution\": true}"); + SmithyCfnJsonConfig config = new NodeMapper().deserialize(node, SmithyCfnJsonConfig.class); + + assertThat(config.getService(), equalTo(ShapeId.from("com.example#MyService"))); + assertThat(config.getDisableCloudFormationSubstitution(), equalTo(true)); + } + + @Test + public void defaultsDisableToFalse() { + Node node = Node.parse("{\"service\": \"com.example#MyService\"}"); + SmithyCfnJsonConfig config = new NodeMapper().deserialize(node, SmithyCfnJsonConfig.class); + + assertThat(config.getService(), equalTo(ShapeId.from("com.example#MyService"))); + assertFalse(config.getDisableCloudFormationSubstitution()); + } +} diff --git a/smithy-aws-apigateway-cfn/src/test/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJsonGoldenFileTest.java b/smithy-aws-apigateway-cfn/src/test/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJsonGoldenFileTest.java new file mode 100644 index 00000000000..f836131eedc --- /dev/null +++ b/smithy-aws-apigateway-cfn/src/test/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJsonGoldenFileTest.java @@ -0,0 +1,46 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package software.amazon.smithy.aws.apigateway.cfn; + +import org.junit.jupiter.api.Test; +import software.amazon.smithy.build.MockManifest; +import software.amazon.smithy.build.PluginContext; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.ObjectNode; +import software.amazon.smithy.utils.IoUtils; + +public class SmithyCfnJsonGoldenFileTest { + + @Test + public void producesExpectedOutput() { + Model model = Model.assembler() + .discoverModels(getClass().getClassLoader()) + .addImport(getClass().getResource("integration-model.smithy")) + .assemble() + .unwrap(); + + MockManifest manifest = new MockManifest(); + ObjectNode settings = Node.objectNodeBuilder() + .withMember("service", "com.example#MyService") + .build(); + + PluginContext context = PluginContext.builder() + .model(model) + .fileManifest(manifest) + .settings(settings) + .build(); + + new SmithyCfnJson().execute(context); + + String output = manifest.getFileString("MyService.smithy.json").get(); + ObjectNode actual = Node.parse(output).expectObjectNode(); + ObjectNode expected = Node.parse( + IoUtils.readUtf8Resource(getClass(), "integration-model.expected.json")) + .expectObjectNode(); + + Node.assertEquals(actual, expected); + } +} diff --git a/smithy-aws-apigateway-cfn/src/test/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJsonIntegrationTest.java b/smithy-aws-apigateway-cfn/src/test/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJsonIntegrationTest.java new file mode 100644 index 00000000000..46104443a7f --- /dev/null +++ b/smithy-aws-apigateway-cfn/src/test/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJsonIntegrationTest.java @@ -0,0 +1,91 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package software.amazon.smithy.aws.apigateway.cfn; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; + +import org.junit.jupiter.api.Test; +import software.amazon.smithy.build.MockManifest; +import software.amazon.smithy.build.PluginContext; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.ObjectNode; +import software.amazon.smithy.model.shapes.ModelSerializer; + +public class SmithyCfnJsonIntegrationTest { + + @Test + public void substitutesIntegrationFieldsInRealModel() { + Model model = Model.assembler() + .discoverModels(getClass().getClassLoader()) + .addImport(getClass().getResource("integration-model.smithy")) + .assemble() + .unwrap(); + + MockManifest manifest = new MockManifest(); + ObjectNode settings = Node.objectNodeBuilder() + .withMember("service", "com.example#MyService") + .build(); + + PluginContext context = PluginContext.builder() + .model(model) + .fileManifest(manifest) + .settings(settings) + .build(); + + new SmithyCfnJson().execute(context); + + String output = manifest.getFileString("MyService.smithy.json").get(); + ObjectNode outputNode = Node.parse(output).expectObjectNode(); + ObjectNode integration = outputNode.expectObjectMember("shapes") + .expectObjectMember("com.example#GetItems") + .expectObjectMember("traits") + .expectObjectMember("aws.apigateway#integration"); + + // Substituted fields are wrapped in Fn::Sub + assertThat(integration.expectObjectMember("uri").expectStringMember("Fn::Sub").getValue(), + equalTo("arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyLambda.Arn}/invocations")); + assertThat(integration.expectObjectMember("credentials").expectStringMember("Fn::Sub").getValue(), + equalTo("${ApiGatewayRole.Arn}")); + assertThat(integration.expectObjectMember("connectionId").expectStringMember("Fn::Sub").getValue(), + equalTo("${MyVpcLink}")); + assertThat(integration.expectObjectMember("integrationTarget").expectStringMember("Fn::Sub").getValue(), + equalTo("${MyALBListener.Arn}")); + + // Non-substituted fields remain as plain strings + assertThat(integration.expectStringMember("type").getValue(), equalTo("aws_proxy")); + assertThat(integration.expectStringMember("httpMethod").getValue(), equalTo("POST")); + } + + @Test + public void disabledSubstitutionMatchesModelSerializer() { + Model model = Model.assembler() + .discoverModels(getClass().getClassLoader()) + .addImport(getClass().getResource("integration-model.smithy")) + .assemble() + .unwrap(); + + MockManifest manifest = new MockManifest(); + ObjectNode settings = Node.objectNodeBuilder() + .withMember("service", "com.example#MyService") + .withMember("disableCloudFormationSubstitution", true) + .build(); + + PluginContext context = PluginContext.builder() + .model(model) + .fileManifest(manifest) + .settings(settings) + .build(); + + new SmithyCfnJson().execute(context); + + String output = manifest.getFileString("MyService.smithy.json").get(); + ObjectNode outputNode = Node.parse(output).expectObjectNode(); + ObjectNode expected = ModelSerializer.builder().build().serialize(model); + + Node.assertEquals(outputNode, expected); + } +} diff --git a/smithy-aws-apigateway-cfn/src/test/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJsonTest.java b/smithy-aws-apigateway-cfn/src/test/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJsonTest.java new file mode 100644 index 00000000000..0853c8f798c --- /dev/null +++ b/smithy-aws-apigateway-cfn/src/test/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJsonTest.java @@ -0,0 +1,38 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package software.amazon.smithy.aws.apigateway.cfn; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ServiceLoader; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.build.SmithyBuildPlugin; + +public class SmithyCfnJsonTest { + + @Test + public void hasCorrectName() { + assertThat(new SmithyCfnJson().getName(), equalTo("smithy-cfn-json")); + } + + @Test + public void requiresValidModel() { + assertTrue(new SmithyCfnJson().requiresValidModel()); + } + + @Test + public void isDiscoverableViaSpi() { + boolean found = false; + for (SmithyBuildPlugin plugin : ServiceLoader.load(SmithyBuildPlugin.class)) { + if (plugin.getName().equals("smithy-cfn-json")) { + found = true; + break; + } + } + assertTrue(found, "smithy-cfn-json plugin should be discoverable via ServiceLoader"); + } +} diff --git a/smithy-aws-apigateway-cfn/src/test/resources/software/amazon/smithy/aws/apigateway/cfn/integration-model.expected.json b/smithy-aws-apigateway-cfn/src/test/resources/software/amazon/smithy/aws/apigateway/cfn/integration-model.expected.json new file mode 100644 index 00000000000..f1e8ce2b935 --- /dev/null +++ b/smithy-aws-apigateway-cfn/src/test/resources/software/amazon/smithy/aws/apigateway/cfn/integration-model.expected.json @@ -0,0 +1,1652 @@ +{ + "smithy": "2.0", + "shapes": { + "aws.api#ArnNamespace": { + "type": "string", + "traits": { + "smithy.api#documentation": "A string representing a service's ARN namespace.", + "smithy.api#pattern": "^[a-z0-9.\\-]{1,63}$", + "smithy.api#private": {} + } + }, + "aws.api#CloudFormationName": { + "type": "string", + "traits": { + "smithy.api#documentation": "A string representing a CloudFormation service name.", + "smithy.api#pattern": "^[A-Z][A-Za-z0-9]+$", + "smithy.api#private": {} + } + }, + "aws.api#CloudWatchMetricNamespace": { + "type": "string", + "traits": { + "smithy.api#length": { + "min": 1, + "max": 255 + }, + "smithy.api#pattern": "^[^:].*$", + "smithy.api#private": {} + } + }, + "aws.api#ResourceDelimiter": { + "type": "enum", + "members": { + "FORWARD_SLASH": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#documentation": "The `/` character.", + "smithy.api#enumValue": "/" + } + }, + "COLON": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#documentation": "The `:` character.", + "smithy.api#enumValue": ":" + } + } + }, + "traits": { + "smithy.api#documentation": "The possible delimiters for an ARN resource segment.", + "smithy.api#private": {} + } + }, + "aws.api#TagOperationReference": { + "type": "string", + "traits": { + "smithy.api#documentation": "Points to an operation designated for a tagging APi", + "smithy.api#idRef": { + "failWhenMissing": true, + "selector": "resource > operation" + } + } + }, + "aws.api#TaggableApiConfig": { + "type": "structure", + "members": { + "tagApi": { + "target": "aws.api#TagOperationReference", + "traits": { + "smithy.api#documentation": "The `tagApi` property is a string value that references a non-instance\nor create operation that creates or updates tags on the resource.", + "smithy.api#required": {} + } + }, + "untagApi": { + "target": "aws.api#TagOperationReference", + "traits": { + "smithy.api#documentation": "The `untagApi` property is a string value that references a non-instance\noperation that removes tags on the resource.", + "smithy.api#required": {} + } + }, + "listTagsApi": { + "target": "aws.api#TagOperationReference", + "traits": { + "smithy.api#documentation": "The `listTagsApi` property is a string value that references a non-\ninstance operation which gets the current tags on the resource.", + "smithy.api#required": {} + } + } + }, + "traits": { + "smithy.api#documentation": "Structure representing the configuration of resource specific tagging APIs" + } + }, + "aws.api#arn": { + "type": "structure", + "members": { + "template": { + "target": "smithy.api#String", + "traits": { + "smithy.api#documentation": "Defines the ARN template. The provided string contains URI-template\nstyle label placeholders that contain the name of one of the identifiers\ndefined in the `identifiers` property of the resource. These labels can\nbe substituted at runtime with the actual identifiers of the resource.\nEvery identifier name of the resource MUST have corresponding label of\nthe same name. Note that percent-encoding **is not** performed on these\nplaceholder values; they are to be replaced literally. For relative ARN\ntemplates that have not set `absolute` to `true`, the template string\ncontains only the resource part of the ARN (for example,\n`foo/{MyResourceId}`). Relative ARNs MUST NOT start with \"/\".", + "smithy.api#required": {} + } + }, + "absolute": { + "target": "smithy.api#Boolean", + "traits": { + "smithy.api#documentation": "Set to true to indicate that the ARN template contains a fully-formed\nARN that does not need to be merged with the service. This type of ARN\nMUST be used when the identifier of a resource is an ARN or is based on\nthe ARN identifier of a parent resource." + } + }, + "noRegion": { + "target": "smithy.api#Boolean", + "traits": { + "smithy.api#documentation": "Set to true to specify that the ARN does not contain a region. If not\nset, or if set to false, the resolved ARN will contain a placeholder\nfor the region. This can only be set to true if `absolute` is not set\nor is false." + } + }, + "noAccount": { + "target": "smithy.api#Boolean", + "traits": { + "smithy.api#documentation": "Set to true to specify that the ARN does not contain an account ID. If\nnot set, or if set to false, the resolved ARN will contain a placeholder\nfor the customer account ID. This can only be set to true if absolute\nis not set or is false." + } + }, + "resourceDelimiter": { + "target": "aws.api#ResourceDelimiter", + "traits": { + "smithy.api#documentation": "Defines which character is used to delimit sections of the resource\nsegment of an ARN. This can only be set if absolute is set to true." + } + }, + "reusable": { + "target": "smithy.api#Boolean", + "traits": { + "smithy.api#documentation": "Set to true to indicate that an ARN may be reused for different\ninstances of a resource." + } + } + }, + "traits": { + "smithy.api#documentation": "Specifies an ARN template for the resource.", + "smithy.api#externalDocumentation": { + "Reference": "https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html" + }, + "smithy.api#trait": { + "selector": "resource" + } + } + }, + "aws.api#arnReference": { + "type": "structure", + "members": { + "type": { + "target": "smithy.api#String", + "traits": { + "smithy.api#documentation": "The AWS CloudFormation resource type contained in the ARN." + } + }, + "resource": { + "target": "smithy.api#String", + "traits": { + "smithy.api#documentation": "An absolute shape ID that references the Smithy resource type contained\nin the ARN (e.g., `com.foo#SomeResource`). The targeted resource is not\nrequired to be found in the model, allowing for external shapes to be\nreferenced without needing to take on an additional dependency. If the\nshape is found in the model, it MUST target a resource shape, and the\nresource MUST be found within the closure of the referenced service\nshape." + } + }, + "service": { + "target": "smithy.api#String", + "traits": { + "smithy.api#documentation": "The Smithy service absolute shape ID that is referenced by the ARN. The\ntargeted service is not required to be found in the model, allowing for\nexternal shapes to be referenced without needing to take on an\nadditional dependency." + } + } + }, + "traits": { + "smithy.api#documentation": "Marks a string as containing an ARN.", + "smithy.api#trait": { + "selector": "string" + } + } + }, + "aws.api#awsChunked": { + "type": "structure", + "members": {}, + "traits": { + "smithy.api#documentation": "Indicates that the streaming blob supports aws-chunked content encoding.\n\nWhen present, SDKs MUST aws-chunk encode the underlying data stream.\naws-chunked encoding is a series of data blocks followed by a final block\nthat contains metadata about the content transferred (e.g., checksums).", + "smithy.api#trait": { + "selector": "blob[trait|streaming]", + "breakingChanges": [ + { + "change": "remove" + } + ] + } + } + }, + "aws.api#clientDiscoveredEndpoint": { + "type": "structure", + "members": { + "required": { + "target": "smithy.api#Boolean", + "traits": { + "smithy.api#documentation": "This field denotes whether or not this operation requires the use of a\nspecific endpoint. If this field is false, the standard regional\nendpoint for a service can handle this request. The client will start\nsending requests to the standard regional endpoint while working to\ndiscover a more specific endpoint.", + "smithy.api#required": {} + } + } + }, + "traits": { + "smithy.api#documentation": "Indicates that the target operation should use the client's endpoint\ndiscovery logic.", + "smithy.api#trait": { + "selector": "operation" + } + } + }, + "aws.api#clientEndpointDiscovery": { + "type": "structure", + "members": { + "operation": { + "target": "smithy.api#String", + "traits": { + "smithy.api#documentation": "Indicates the operation that clients should use to discover endpoints\nfor the service.", + "smithy.api#idRef": { + "failWhenMissing": true, + "selector": "operation" + }, + "smithy.api#required": {} + } + }, + "error": { + "target": "smithy.api#String", + "traits": { + "smithy.api#documentation": "Indicates the error that tells clients that the endpoint they are using\nis no longer valid. This error MUST be bound to any operation bound to\nthe service which is marked with the aws.api#clientDiscoveredEndpoint\ntrait.", + "smithy.api#idRef": { + "failWhenMissing": true, + "selector": "structure[trait|error]" + }, + "smithy.api#recommended": {} + } + } + }, + "traits": { + "smithy.api#documentation": "Configures endpoint discovery for the service.", + "smithy.api#trait": { + "selector": "service" + } + } + }, + "aws.api#clientEndpointDiscoveryId": { + "type": "structure", + "members": {}, + "traits": { + "smithy.api#documentation": "Indicates members of the operation input which should be use to discover\nendpoints.", + "smithy.api#trait": { + "selector": "operation[trait|aws.api#clientDiscoveredEndpoint] -[input]->\nstructure > :test(member[trait|required] > string)" + } + } + }, + "aws.api#controlPlane": { + "type": "structure", + "members": {}, + "traits": { + "smithy.api#documentation": "Defines a service, resource, or operation as operating on the control plane.", + "smithy.api#trait": { + "selector": ":test(service, resource, operation)", + "conflicts": [ + "aws.api#dataPlane" + ] + } + } + }, + "aws.api#data": { + "type": "enum", + "members": { + "CUSTOMER_CONTENT": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#documentation": "Customer content means any software (including machine images), data,\ntext, audio, video or images that customers or any customer end user\ntransfers to AWS for processing, storage or hosting by AWS services in\nconnection with the customer’s accounts and any computational results\nthat customers or any customer end user derive from the foregoing\nthrough their use of AWS services.", + "smithy.api#enumValue": "content" + } + }, + "CUSTOMER_ACCOUNT_INFORMATION": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#documentation": "Account information means information about customers that customers\nprovide to AWS in connection with the creation or administration of\ncustomers’ accounts.", + "smithy.api#enumValue": "account" + } + }, + "SERVICE_ATTRIBUTES": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#documentation": "Service Attributes means service usage data related to a customer’s\naccount, such as resource identifiers, metadata tags, security and\naccess roles, rules, usage policies, permissions, usage statistics,\nlogging data, and analytics.", + "smithy.api#enumValue": "usage" + } + }, + "TAG_DATA": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#documentation": "Designates metadata tags applied to AWS resources.", + "smithy.api#enumValue": "tagging" + } + }, + "PERMISSIONS_DATA": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#documentation": "Designates security and access roles, rules, usage policies, and\npermissions.", + "smithy.api#enumValue": "permissions" + } + } + }, + "traits": { + "smithy.api#documentation": "Designates the target as containing data of a known classification level.", + "smithy.api#trait": { + "selector": ":test(simpleType, list, structure, union, member)" + } + } + }, + "aws.api#dataPlane": { + "type": "structure", + "members": {}, + "traits": { + "smithy.api#documentation": "Defines a service, resource, or operation as operating on the data plane.", + "smithy.api#trait": { + "selector": ":test(service, resource, operation)", + "conflicts": [ + "aws.api#controlPlane" + ] + } + } + }, + "aws.api#service": { + "type": "structure", + "members": { + "sdkId": { + "target": "smithy.api#String", + "traits": { + "smithy.api#documentation": "The `sdkId` property is a required string value that specifies the AWS\nSDK service ID (e.g., \"API Gateway\"). This value is used for generating\nclient names in SDKs and for linking between services.", + "smithy.api#required": {} + } + }, + "arnNamespace": { + "target": "aws.api#ArnNamespace", + "traits": { + "smithy.api#documentation": "The `arnNamespace` property is a string value that defines the ARN service\nnamespace of the service (e.g., \"apigateway\"). This value is used in\nARNs assigned to resources in the service. If not set, this value\ndefaults to the lowercase name of the service shape." + } + }, + "cloudFormationName": { + "target": "aws.api#CloudFormationName", + "traits": { + "smithy.api#documentation": "The `cloudFormationName` property is a string value that specifies the\nAWS CloudFormation service name (e.g., `ApiGateway`). When not set,\nthis value defaults to the name of the service shape. This value is\npart of the CloudFormation resource type name that is automatically\nassigned to resources in the service (e.g., `AWS::::resourceName`)." + } + }, + "cloudTrailEventSource": { + "target": "smithy.api#String", + "traits": { + "smithy.api#documentation": "The `cloudTrailEventSource` property is a string value that defines the\nAWS customer-facing eventSource property contained in CloudTrail event\nrecords emitted by the service. If not specified, this value defaults\nto the `arnNamespace` plus `.amazonaws.com`." + } + }, + "docId": { + "target": "smithy.api#String", + "traits": { + "smithy.api#documentation": "The `docId` property is a string value that defines the identifier\nused to implement linking between service and SDK documentation for\nAWS services. If not specified, this value defaults to the `sdkId` in\nlower case plus the service `version` property, separated by dashes." + } + }, + "endpointPrefix": { + "target": "smithy.api#String", + "traits": { + "smithy.api#documentation": "The `endpointPrefix` property is a string value that identifies which\nendpoint in a given region should be used to connect to the service.\nFor example, most services in the AWS standard partition have endpoints\nwhich follow the format: `{endpointPrefix}.{region}.amazonaws.com`. A\nservice with the endpoint prefix example in the region us-west-2 might\nhave the endpoint example.us-west-2.amazonaws.com.\n\nThis value is not unique across services and is subject to change.\nTherefore, it MUST NOT be used for client naming or for any other\npurpose that requires a static, unique identifier. sdkId should be used\nfor those purposes. Additionally, this value can be used to attempt to\nresolve endpoints." + } + }, + "cloudWatchNamespace": { + "target": "aws.api#CloudWatchMetricNamespace", + "traits": { + "smithy.api#documentation": "The `cloudWatchNamespace` property is a string value that defines the\nAWS customer-facing namespace of most metrics emitted by the service." + } + } + }, + "traits": { + "smithy.api#documentation": "An AWS service is defined using the `aws.api#service` trait. This trait\nprovides information about the service like the name used to generate AWS\nSDK client classes and the namespace used in ARNs.", + "smithy.api#trait": { + "selector": "service" + } + } + }, + "aws.api#tagEnabled": { + "type": "structure", + "members": { + "disableDefaultOperations": { + "target": "smithy.api#Boolean", + "traits": { + "smithy.api#documentation": "The `disableDefaultOperations` property is a boolean value that specifies\nif the service does not have the standard tag operations supporting all\nresources on the service. Default value is `false`" + } + } + }, + "traits": { + "smithy.api#documentation": "Annotates a service as having tagging on 1 or more resources and associated\nAPIs to perform CRUD operations on those tags", + "smithy.api#trait": { + "selector": "service" + }, + "smithy.api#unstable": {} + } + }, + "aws.api#taggable": { + "type": "structure", + "members": { + "property": { + "target": "smithy.api#String", + "traits": { + "smithy.api#documentation": "The `property` property is a string value that identifies which\nresource property represents tags for the resource." + } + }, + "apiConfig": { + "target": "aws.api#TaggableApiConfig", + "traits": { + "smithy.api#documentation": "Specifies configuration for resource specific tagging APIs if the\nresource has them." + } + }, + "disableSystemTags": { + "target": "smithy.api#Boolean", + "traits": { + "smithy.api#documentation": "Flag indicating if the resource is not able to carry AWS system level.\nUsed by service principals. Default value is `false`" + } + } + }, + "traits": { + "smithy.api#documentation": "Indicates a resource supports CRUD operations for tags. Either through\nresource lifecycle or instance operations or tagging operations on the\nservice.", + "smithy.api#trait": { + "selector": "resource" + }, + "smithy.api#unstable": {} + } + }, + "aws.apigateway#Arn": { + "type": "string", + "traits": { + "aws.api#arnReference": {}, + "smithy.api#documentation": "The ARN of an AWS integration target.\n\nThis string MAY contain the literal string `{serviceName}` and/or the\nliteral string `{operationName}`, which will be replaced with the name of\nthe Smithy service shape and the name of the Smithy operation shape,\nrespectively.", + "smithy.api#private": {} + } + }, + "aws.apigateway#AuthorizerDefinition": { + "type": "structure", + "members": { + "scheme": { + "target": "smithy.api#String", + "traits": { + "smithy.api#documentation": "The Smithy authentication scheme used by the client (e.g, aws.v4).", + "smithy.api#idRef": { + "selector": "[trait|authDefinition]", + "failWhenMissing": true, + "errorMessage": "The scheme of an authorizer definition must reference an auth trait" + }, + "smithy.api#required": {} + } + }, + "type": { + "target": "smithy.api#String", + "traits": { + "smithy.api#documentation": "The type of the authorizer. If specifying information beyond the scheme,\nthis value is required. The value must be \"token\", for an authorizer\nwith the caller identity embedded in an authorization token, or\n\"request\", for an authorizer with the caller identity contained in\nrequest parameters." + } + }, + "customAuthType": { + "target": "smithy.api#String", + "traits": { + "smithy.api#documentation": "This value is not used directly by API Gateway but will be used for\nOpenAPI exports. This will default to \"awsSigV4\" if your scheme is\n\"aws.v4\", or \"custom\" otherwise." + } + }, + "uri": { + "target": "smithy.api#String", + "traits": { + "smithy.api#documentation": "The Uniform Resource Identifier (URI) of the authorizer Lambda function" + } + }, + "credentials": { + "target": "smithy.api#String", + "traits": { + "smithy.api#documentation": "Credentials required for invoking the authorizer" + } + }, + "identitySource": { + "target": "smithy.api#String", + "traits": { + "smithy.api#documentation": "Comma-separated list of mapping expressions of the request parameters\nas the identity source. Applicable for the authorizer of the \"request\"\ntype only." + } + }, + "identityValidationExpression": { + "target": "smithy.api#String", + "traits": { + "smithy.api#documentation": "A regular expression for validating the token as the incoming identity" + } + }, + "resultTtlInSeconds": { + "target": "smithy.api#Integer", + "traits": { + "smithy.api#documentation": "The number of seconds for which the resulting IAM policy is cached." + } + }, + "authorizerPayloadFormatVersion": { + "target": "aws.apigateway#PayloadFormatVersion", + "traits": { + "smithy.api#documentation": "Format version of the payload sent from API Gateway to the authorizer\nand how API Gateway interprets the response. Used only by HTTP APIs." + } + }, + "enableSimpleResponses": { + "target": "smithy.api#Boolean", + "traits": { + "smithy.api#documentation": "Specifies if the autorizer returns either a boolean or an IAM Policy.\nIf enabled, authorizer returns a boolean. Used only by HTTP APIs.\nOnly supported when authorizerPayloadFormatVersion is set to 2.0." + } + } + }, + "traits": { + "smithy.api#documentation": "An object that associates an authorizer and associated metadata with an\nauthentication mechanism.", + "smithy.api#private": {} + } + }, + "aws.apigateway#ConnectionType": { + "type": "enum", + "members": { + "INTERNET": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#documentation": "Connections through the public routable internet.", + "smithy.api#enumValue": "INTERNET" + } + }, + "VPC_LINK": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#documentation": "Private connections between API Gateway and a network load balancer in\na VPC.", + "smithy.api#enumValue": "VPC_LINK" + } + } + }, + "traits": { + "smithy.api#private": {} + } + }, + "aws.apigateway#ContentHandling": { + "type": "enum", + "members": { + "CONVERT_TO_TEXT": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#documentation": "For converting a binary payload into a Base64-encoded string or\nconverting a text payload into a utf-8-encoded string or passing\nthrough the text payload natively without modification", + "smithy.api#enumValue": "CONVERT_TO_TEXT" + } + }, + "CONVERT_TO_BINARY": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#documentation": "For converting a text payload into Base64-decoded blob or passing\nthrough a binary payload natively without modification.", + "smithy.api#enumValue": "CONVERT_TO_BINARY" + } + } + }, + "traits": { + "smithy.api#documentation": "Defines the contentHandling for the integration.", + "smithy.api#private": {} + } + }, + "aws.apigateway#IamRoleArn": { + "type": "string", + "traits": { + "aws.api#arnReference": { + "type": "AWS::IAM::Role" + }, + "smithy.api#documentation": "The ARN of the IAM role to assume with invoking the integration.\n\nThis string MAY contain the literal string `{serviceName}` and/or the\nliteral string `{operationName}`, which will be replaced with the name of\nthe Smithy service shape and the name of the Smithy operation shape,\nrespectively.", + "smithy.api#private": {} + } + }, + "aws.apigateway#IntegrationResponse": { + "type": "structure", + "members": { + "statusCode": { + "target": "smithy.api#String", + "traits": { + "smithy.api#documentation": "HTTP status code for the method response; for example, \"200\". This must\ncorrespond to a matching response in the OpenAPI Operation responses\nfield." + } + }, + "contentHandling": { + "target": "aws.apigateway#ContentHandling", + "traits": { + "smithy.api#documentation": "Response payload content handling." + } + }, + "responseTemplates": { + "target": "aws.apigateway#Templates", + "traits": { + "smithy.api#documentation": "Specifies media type-specific mapping templates for the response's\npayload." + } + }, + "responseParameters": { + "target": "aws.apigateway#ResponseParameters", + "traits": { + "smithy.api#documentation": "Specifies parameter mappings for the response. Only the header and\nbody parameters of the integration response can be mapped to the header\nparameters of the method." + } + } + }, + "traits": { + "smithy.api#documentation": "Defines a response and specifies parameter mappings.", + "smithy.api#private": {} + } + }, + "aws.apigateway#IntegrationResponses": { + "type": "map", + "key": { + "target": "smithy.api#String" + }, + "value": { + "target": "aws.apigateway#IntegrationResponse" + }, + "traits": { + "smithy.api#documentation": "A map of response identifiers to responses.", + "smithy.api#private": {} + } + }, + "aws.apigateway#IntegrationType": { + "type": "enum", + "members": { + "AWS": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#documentation": "An integration with AWS Lambda functions or other AWS services such as\nAmazon DynamoDB, Amazon Simple Notification Service or Amazon Simple\nQueue Service.", + "smithy.api#enumValue": "aws" + } + }, + "AWS_PROXY": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#documentation": "An integration with AWS Lambda functions.", + "smithy.api#enumValue": "aws_proxy" + } + }, + "HTTP": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#documentation": "An integration with an HTTP backend.", + "smithy.api#enumValue": "http" + } + }, + "HTTP_PROXY": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#documentation": "An integration with an HTTP backend.", + "smithy.api#enumValue": "http_proxy" + } + } + }, + "traits": { + "smithy.api#private": {} + } + }, + "aws.apigateway#PassThroughBehavior": { + "type": "enum", + "members": { + "WHEN_NO_TEMPLATES": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#documentation": "Passes the method request body through the integration request to the\nback end without transformation when no mapping template is defined in\nthe integration request. If a template is defined when this option is\nselected, the method request of an unmapped content-type will be\nrejected with an HTTP 415 Unsupported Media Type response.", + "smithy.api#enumValue": "when_no_templates" + } + }, + "WHEN_NO_MATCH": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#documentation": "Passes the method request body through the integration request to the\nback end without transformation when the method request content type\ndoes not match any content type associated with the mapping templates\ndefined in the integration request.", + "smithy.api#enumValue": "when_no_match" + } + }, + "NEVER": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#documentation": "Rejects the method request with an HTTP 415 Unsupported Media Type\nresponse when either the method request content type does not match any\ncontent type associated with the mapping templates defined in the\nintegration request or no mapping template is defined in the integration\nrequest.", + "smithy.api#enumValue": "never" + } + } + }, + "traits": { + "smithy.api#documentation": "Defines the passThroughBehavior for the integration", + "smithy.api#private": {} + } + }, + "aws.apigateway#PayloadFormatVersion": { + "type": "enum", + "members": { + "V1_0": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#documentation": "Specifies 1.0 version of the format used by the authorizer", + "smithy.api#enumValue": "1.0" + } + }, + "V2_0": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#documentation": "Specifies 2.0 version of the format used by the authorizer", + "smithy.api#enumValue": "2.0" + } + } + }, + "traits": { + "smithy.api#documentation": "Defines the payloadFormatVersion used by authorizers", + "smithy.api#private": {} + } + }, + "aws.apigateway#RequestParameters": { + "type": "map", + "key": { + "target": "smithy.api#String" + }, + "value": { + "target": "smithy.api#String" + }, + "traits": { + "smithy.api#documentation": "A mapping of integration request parameters to the API Gateway data\nmapping expression that should be used to populate the parameter.\n\n**Note:** This feature is provided primarily to allow injecting static\nvalues and context variables for request parameters. Request data MAY be\nmapped to headers using the syntax described in\n[the API Gateway Developer Guide](https://docs.aws.amazon.com/apigateway/latest/developerguide/request-response-data-mappings.html#mapping-response-parameters);\nhowever, the data must be identified according to its location in the\nserialized request, which may differ from protocol to protocol. Here be\ndragons!", + "smithy.api#private": {} + } + }, + "aws.apigateway#ResponseParameters": { + "type": "map", + "key": { + "target": "smithy.api#String" + }, + "value": { + "target": "smithy.api#String" + }, + "traits": { + "smithy.api#documentation": "Specifies parameter mappings for the response. Only the header and body\nparameters of the integration response can be mapped to the header\nparameters of the method.", + "smithy.api#private": {} + } + }, + "aws.apigateway#StringList": { + "type": "list", + "member": { + "target": "smithy.api#String" + }, + "traits": { + "smithy.api#private": {} + } + }, + "aws.apigateway#Templates": { + "type": "map", + "key": { + "target": "smithy.api#String" + }, + "value": { + "target": "smithy.api#String" + }, + "traits": { + "smithy.api#documentation": "A map of MIME types to velocity templates allowing you to craft a new\nintegration message from received data.\n\n**Note:** This feature is provided primarily to allow injecting static\nvalues and context variables for request parameters. Request data MAY be\nmapped to headers using the syntax described in\n[the API Gateway Developer Guide](https://docs.aws.amazon.com/apigateway/latest/developerguide/request-response-data-mappings.html#mapping-response-parameters);\nhowever, the data must be identified according to its location in the\nserialized request, which may differ from protocol to protocol. Here be\ndragons!", + "smithy.api#private": {} + } + }, + "aws.apigateway#apiKeySource": { + "type": "string", + "traits": { + "smithy.api#documentation": "Specifies the source of the caller identifier that will be used to throttle\nAPI methods that require a key.", + "smithy.api#externalDocumentation": { + "Developer Guide": "https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-api-key-source.html" + }, + "smithy.api#internal": {}, + "smithy.api#tags": [ + "internal" + ], + "smithy.api#trait": { + "selector": "service" + } + } + }, + "aws.apigateway#authorizer": { + "type": "string", + "traits": { + "smithy.api#documentation": "Attaches an authorizer to a service, resource, or operation.", + "smithy.api#internal": {}, + "smithy.api#tags": [ + "internal" + ], + "smithy.api#trait": { + "selector": ":test(service, resource, operation)" + } + } + }, + "aws.apigateway#authorizers": { + "type": "map", + "key": { + "target": "smithy.api#String" + }, + "value": { + "target": "aws.apigateway#AuthorizerDefinition" + }, + "traits": { + "smithy.api#documentation": "A list of API Gateway authorizers to augment the service's declared authentication\nmechanisms.", + "smithy.api#internal": {}, + "smithy.api#tags": [ + "internal" + ], + "smithy.api#trait": { + "selector": "service" + } + } + }, + "aws.apigateway#integration": { + "type": "structure", + "members": { + "type": { + "target": "aws.apigateway#IntegrationType", + "traits": { + "smithy.api#documentation": "The type of integration with the specified backend.", + "smithy.api#required": {} + } + }, + "uri": { + "target": "aws.apigateway#Arn", + "traits": { + "smithy.api#documentation": "The endpoint URI of the backend. For integrations of the `aws` type,\nthis is an ARN value. For the HTTP integration, this is the URL of the\nHTTP endpoint including the `https` or `http` scheme.", + "smithy.api#required": {} + } + }, + "credentials": { + "target": "aws.apigateway#IamRoleArn", + "traits": { + "smithy.api#documentation": "Specifies the credentials required for the integration, if any. For AWS\nIAM role-based credentials, specify the ARN of an appropriate IAM role.\nIf unspecified, credentials will default to resource-based permissions\nthat must be added manually to allow the API to access the resource." + } + }, + "httpMethod": { + "target": "smithy.api#String", + "traits": { + "smithy.api#documentation": "Specifies the integration's HTTP method type (for example, `POST`).\nFor Lambda function invocations, the value must be `POST`.", + "smithy.api#required": {} + } + }, + "passThroughBehavior": { + "target": "aws.apigateway#PassThroughBehavior", + "traits": { + "smithy.api#documentation": "Specifies how a request payload of unmapped content type is passed\nthrough the integration request without modification." + } + }, + "contentHandling": { + "target": "aws.apigateway#ContentHandling", + "traits": { + "smithy.api#documentation": "Request payload content handling." + } + }, + "timeoutInMillis": { + "target": "smithy.api#Integer", + "traits": { + "smithy.api#documentation": "Integration timeouts between 50 ms and 29,000 ms." + } + }, + "connectionId": { + "target": "smithy.api#String", + "traits": { + "smithy.api#documentation": "The ID of a VpcLink for the private integration." + } + }, + "connectionType": { + "target": "aws.apigateway#ConnectionType", + "traits": { + "smithy.api#default": "INTERNET", + "smithy.api#documentation": "The type of the network connection to the integration endpoint. The\nvalid value is `INTERNET` for connections through the public routable\ninternet or `VPC_LINK` for private connections between API Gateway and\na network load balancer in a VPC. The default value is `INTERNET`." + } + }, + "cacheNamespace": { + "target": "smithy.api#String", + "traits": { + "smithy.api#documentation": "An API-specific tag group of related cached parameters." + } + }, + "payloadFormatVersion": { + "target": "smithy.api#String", + "traits": { + "smithy.api#documentation": "Specifies the format of the payload sent to an integration. Required\nfor HTTP APIs. For HTTP APIs, supported values for Lambda proxy\nintegrations are 1.0 and 2.0. For all other integrations, 1.0 is the\nonly supported value." + } + }, + "cacheKeyParameters": { + "target": "aws.apigateway#StringList", + "traits": { + "smithy.api#documentation": "A list of request parameter names whose values are to be cached." + } + }, + "requestParameters": { + "target": "aws.apigateway#RequestParameters", + "traits": { + "smithy.api#documentation": "Specifies mappings from method request parameters to integration\nrequest parameters." + } + }, + "requestTemplates": { + "target": "aws.apigateway#Templates", + "traits": { + "smithy.api#documentation": "Mapping templates for a request payload of specified media types." + } + }, + "responses": { + "target": "aws.apigateway#IntegrationResponses", + "traits": { + "smithy.api#documentation": "Defines the method's responses and specifies desired parameter mappings\nor payload mappings from integration responses to method responses." + } + } + }, + "traits": { + "smithy.api#documentation": "Defines an API Gateway integration.", + "smithy.api#internal": {}, + "smithy.api#tags": [ + "internal" + ], + "smithy.api#trait": { + "selector": ":test(service, resource, operation)", + "conflicts": [ + "aws.apigateway#mockIntegration" + ] + } + } + }, + "aws.apigateway#mockIntegration": { + "type": "structure", + "members": { + "passThroughBehavior": { + "target": "aws.apigateway#PassThroughBehavior", + "traits": { + "smithy.api#documentation": "Specifies how a request payload of unmapped content type is passed\nthrough the integration request without modification." + } + }, + "requestParameters": { + "target": "aws.apigateway#RequestParameters", + "traits": { + "smithy.api#documentation": "Specifies mappings from method request parameters to integration\nrequest parameters." + } + }, + "requestTemplates": { + "target": "aws.apigateway#Templates", + "traits": { + "smithy.api#documentation": "Mapping templates for a request payload of specified media types." + } + }, + "responses": { + "target": "aws.apigateway#IntegrationResponses", + "traits": { + "smithy.api#documentation": "Defines the method's responses and specifies desired parameter mappings\nor payload mappings from integration responses to method responses." + } + } + }, + "traits": { + "smithy.api#documentation": "Defines an API Gateway mock integration.", + "smithy.api#internal": {}, + "smithy.api#tags": [ + "internal" + ], + "smithy.api#trait": { + "selector": ":test(service, resource, operation)", + "conflicts": [ + "aws.apigateway#integration" + ] + } + } + }, + "aws.apigateway#requestValidator": { + "type": "string", + "traits": { + "smithy.api#documentation": "Selects which request validation strategy to use. One of: 'full', 'params-only', 'body-only'", + "smithy.api#internal": {}, + "smithy.api#tags": [ + "internal" + ], + "smithy.api#trait": { + "selector": ":test(service, operation)" + } + } + }, + "aws.auth#StringList": { + "type": "list", + "member": { + "target": "smithy.api#String" + }, + "traits": { + "smithy.api#private": {} + } + }, + "aws.auth#cognitoUserPools": { + "type": "structure", + "members": { + "providerArns": { + "target": "aws.auth#StringList", + "traits": { + "smithy.api#documentation": "A list of the Amazon Cognito user pool ARNs. Each element is of this\nformat: `arn:aws:cognito-idp:{region}:{account_id}:userpool/{user_pool_id}`.", + "smithy.api#required": {} + } + } + }, + "traits": { + "smithy.api#authDefinition": {}, + "smithy.api#documentation": "Configures an Amazon Cognito User Pools auth scheme.", + "smithy.api#internal": {}, + "smithy.api#tags": [ + "internal" + ], + "smithy.api#trait": { + "selector": "service" + } + } + }, + "aws.auth#sigv4": { + "type": "structure", + "members": { + "name": { + "target": "smithy.api#String", + "traits": { + "smithy.api#documentation": "The signature version 4 service signing name to use in the credential\nscope when signing requests. This value SHOULD match the `arnNamespace`\nproperty of the `aws.api#service` trait if present and the `name`\nproperty of the `aws.api#sigv4a` trait if present.", + "smithy.api#externalDocumentation": { + "Reference": "https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html" + }, + "smithy.api#length": { + "min": 1 + }, + "smithy.api#required": {} + } + } + }, + "traits": { + "smithy.api#authDefinition": { + "traits": [ + "aws.auth#unsignedPayload" + ] + }, + "smithy.api#documentation": "[Signature Version 4](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html)\nis the process to add authentication information to AWS requests sent by HTTP. For\nsecurity, most requests to AWS must be signed with an access key, which consists\nof an access key ID and secret access key. These two keys are commonly referred to\nas your security credentials.", + "smithy.api#externalDocumentation": { + "Reference": "https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html" + }, + "smithy.api#trait": { + "selector": "service", + "breakingChanges": [ + { + "change": "remove", + "message": "Removing the existing authentication scheme is not backward compatible and can break existing clients' authentication." + } + ] + } + } + }, + "aws.auth#sigv4a": { + "type": "structure", + "members": { + "name": { + "target": "smithy.api#String", + "traits": { + "smithy.api#documentation": "The signature version 4a service signing name to use in the credential\nscope when signing requests. This value SHOULD match the `arnNamespace`\nproperty of the `aws.api#service` trait if present and the `name`\nproperty of the `aws.api#sigv4` trait.", + "smithy.api#externalDocumentation": { + "Reference": "https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html" + }, + "smithy.api#length": { + "min": 1 + }, + "smithy.api#required": {} + } + } + }, + "traits": { + "smithy.api#authDefinition": { + "traits": [ + "aws.auth#unsignedPayload" + ] + }, + "smithy.api#documentation": "Signature Version 4 Asymmetric (SigV4A), an extension of Signature Version 4 (SigV4), is the\nprocess to add authentication information to AWS requests sent by HTTP. SigV4A is nearly\nidentical to SigV4, but also uses public-private keys and asymmetric cryptographic signatures\nfor every request. Most notably, SigV4A supports signatures for multi-region API requests.", + "smithy.api#externalDocumentation": { + "Reference": "https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html", + "Examples": "https://github.com/aws-samples/sigv4a-signing-examples" + }, + "smithy.api#trait": { + "selector": "service[trait|aws.auth#sigv4]", + "breakingChanges": [ + { + "change": "remove", + "message": "Removing the existing authentication scheme is not backward compatible and can break existing clients' authentication." + } + ] + } + } + }, + "aws.auth#unsignedPayload": { + "type": "structure", + "members": {}, + "traits": { + "smithy.api#documentation": "Indicates that the request payload of a signed request is not to be used\nas part of the signature.", + "smithy.api#trait": { + "selector": "operation" + } + } + }, + "aws.customizations#s3UnwrappedXmlOutput": { + "type": "structure", + "members": {}, + "traits": { + "smithy.api#documentation": "Indicates the response body from S3 is not wrapped in the\naws-restxml-protocol operation-level XML node. Intended to only be used by\nAWS S3.", + "smithy.api#trait": { + "selector": "operation" + } + } + }, + "aws.protocols#ChecksumAlgorithm": { + "type": "enum", + "members": { + "CRC32": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#documentation": "CRC32", + "smithy.api#enumValue": "CRC32" + } + }, + "CRC32C": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#documentation": "CRC32C", + "smithy.api#enumValue": "CRC32C" + } + }, + "CRC64NVME": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#documentation": "CRC64NVME", + "smithy.api#enumValue": "CRC64NVME" + } + }, + "MD5": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#documentation": "MD5", + "smithy.api#enumValue": "MD5" + } + }, + "SHA1": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#documentation": "SHA1", + "smithy.api#enumValue": "SHA1" + } + }, + "SHA256": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#documentation": "SHA256", + "smithy.api#enumValue": "SHA256" + } + }, + "SHA512": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#documentation": "SHA512", + "smithy.api#enumValue": "SHA512" + } + }, + "XXHASH64": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#documentation": "XXHASH64", + "smithy.api#enumValue": "XXHASH64" + } + }, + "XXHASH3": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#documentation": "XXHASH3", + "smithy.api#enumValue": "XXHASH3" + } + }, + "XXHASH128": { + "target": "smithy.api#Unit", + "traits": { + "smithy.api#documentation": "XXHASH128", + "smithy.api#enumValue": "XXHASH128" + } + } + }, + "traits": { + "smithy.api#private": {} + } + }, + "aws.protocols#ChecksumAlgorithmSet": { + "type": "list", + "member": { + "target": "aws.protocols#ChecksumAlgorithm" + }, + "traits": { + "smithy.api#length": { + "min": 1 + }, + "smithy.api#private": {}, + "smithy.api#uniqueItems": {} + } + }, + "aws.protocols#HttpConfiguration": { + "type": "structure", + "members": { + "http": { + "target": "aws.protocols#StringList", + "traits": { + "smithy.api#documentation": "The priority ordered list of supported HTTP protocol versions." + } + }, + "eventStreamHttp": { + "target": "aws.protocols#StringList", + "traits": { + "smithy.api#documentation": "The priority ordered list of supported HTTP protocol versions that\nare required when using event streams with the service. If not set,\nthis value defaults to the value of the `http` member. Any entry in\n`eventStreamHttp` MUST also appear in `http`." + } + } + }, + "traits": { + "smithy.api#documentation": "Contains HTTP protocol configuration for HTTP-based protocols.", + "smithy.api#mixin": { + "localTraits": [ + "smithy.api#private" + ] + }, + "smithy.api#private": {} + } + }, + "aws.protocols#StringList": { + "type": "list", + "member": { + "target": "smithy.api#String" + }, + "traits": { + "smithy.api#private": {} + } + }, + "aws.protocols#awsJson1_0": { + "type": "structure", + "mixins": [ + { + "target": "aws.protocols#HttpConfiguration" + } + ], + "members": {}, + "traits": { + "smithy.api#documentation": "An RPC-based protocol that sends JSON payloads. This protocol does not use\nHTTP binding traits.", + "smithy.api#protocolDefinition": { + "traits": [ + "smithy.api#timestampFormat", + "smithy.api#cors", + "smithy.api#endpoint", + "smithy.api#hostLabel" + ] + }, + "smithy.api#trait": { + "selector": "service" + } + } + }, + "aws.protocols#awsJson1_1": { + "type": "structure", + "mixins": [ + { + "target": "aws.protocols#HttpConfiguration" + } + ], + "members": {}, + "traits": { + "smithy.api#documentation": "An RPC-based protocol that sends JSON payloads. This protocol does not use\nHTTP binding traits.", + "smithy.api#protocolDefinition": { + "traits": [ + "smithy.api#timestampFormat", + "smithy.api#cors", + "smithy.api#endpoint", + "smithy.api#hostLabel" + ] + }, + "smithy.api#trait": { + "selector": "service" + } + } + }, + "aws.protocols#awsQuery": { + "type": "structure", + "members": {}, + "traits": { + "smithy.api#deprecated": {}, + "smithy.api#documentation": "An RPC-based protocol that sends 'POST' requests in the body as\n`x-www-form-urlencoded` strings and responses in XML documents. This\nprotocol does not use HTTP binding traits.", + "smithy.api#protocolDefinition": { + "traits": [ + "smithy.api#timestampFormat", + "smithy.api#cors", + "smithy.api#endpoint", + "smithy.api#hostLabel", + "aws.protocols#awsQueryError", + "smithy.api#xmlAttribute", + "smithy.api#xmlFlattened", + "smithy.api#xmlName", + "smithy.api#xmlNamespace" + ] + }, + "smithy.api#trait": { + "selector": "service [trait|xmlNamespace]" + }, + "smithy.api#traitValidators": { + "UnsupportedProtocolDocument": { + "selector": "~> member :test(> document)", + "message": "Document types are not supported with awsQuery" + } + } + } + }, + "aws.protocols#awsQueryCompatible": { + "type": "structure", + "members": {}, + "traits": { + "smithy.api#documentation": "Enable backward compatibility when migrating from awsQuery to the awsJson\nprotocol or Smithy RPC v2 CBOR.", + "smithy.api#trait": { + "selector": "service :test([trait|aws.protocols#awsJson1_0], [trait|smithy.protocols#rpcv2Cbor])" + } + } + }, + "aws.protocols#awsQueryError": { + "type": "structure", + "members": { + "code": { + "target": "smithy.api#String", + "traits": { + "smithy.api#documentation": "The value used to distinguish this error shape during serialization.", + "smithy.api#required": {} + } + }, + "httpResponseCode": { + "target": "smithy.api#Integer", + "traits": { + "smithy.api#documentation": "The HTTP response code used on a response containing this error shape.", + "smithy.api#required": {} + } + } + }, + "traits": { + "smithy.api#documentation": "Provides the value in the 'Code' distinguishing field and HTTP response\ncode for an operation error.", + "smithy.api#trait": { + "selector": "structure [trait|error]", + "breakingChanges": [ + { + "change": "any" + } + ] + } + } + }, + "aws.protocols#ec2Query": { + "type": "structure", + "members": {}, + "traits": { + "smithy.api#deprecated": {}, + "smithy.api#documentation": "An RPC-based protocol that sends 'POST' requests in the body as Amazon EC2\nformatted `x-www-form-urlencoded` strings and responses in XML documents.\nThis protocol does not use HTTP binding traits.", + "smithy.api#protocolDefinition": { + "traits": [ + "smithy.api#timestampFormat", + "smithy.api#cors", + "smithy.api#endpoint", + "smithy.api#hostLabel", + "aws.protocols#ec2QueryName", + "smithy.api#xmlAttribute", + "smithy.api#xmlFlattened", + "smithy.api#xmlName", + "smithy.api#xmlNamespace" + ] + }, + "smithy.api#trait": { + "selector": "service [trait|xmlNamespace]" + }, + "smithy.api#traitValidators": { + "UnsupportedProtocolDocument": { + "selector": "~> member :test(> document)", + "message": "Document types are not supported with ec2Query" + } + } + } + }, + "aws.protocols#ec2QueryName": { + "type": "string", + "traits": { + "smithy.api#documentation": "Indicates the serialized name of a structure member when that structure is\nserialized for the input of an EC2 operation.", + "smithy.api#pattern": "^[a-zA-Z_][a-zA-Z_0-9-]*$", + "smithy.api#trait": { + "selector": "structure > member" + } + } + }, + "aws.protocols#httpChecksum": { + "type": "structure", + "members": { + "requestAlgorithmMember": { + "target": "smithy.api#String", + "traits": { + "smithy.api#documentation": "Defines a top-level operation input member that is used to configure\nrequest checksum behavior." + } + }, + "requestChecksumRequired": { + "target": "smithy.api#Boolean", + "traits": { + "smithy.api#documentation": "Indicates an operation requires a checksum in its HTTP request." + } + }, + "requestValidationModeMember": { + "target": "smithy.api#String", + "traits": { + "smithy.api#documentation": "Defines a top-level operation input member used to opt-in to response\nchecksum validation." + } + }, + "responseAlgorithms": { + "target": "aws.protocols#ChecksumAlgorithmSet", + "traits": { + "smithy.api#documentation": "Defines the checksum algorithms clients should look for when performing\nHTTP response checksum validation." + } + } + }, + "traits": { + "smithy.api#documentation": "Indicates that an operation supports checksum validation.", + "smithy.api#trait": { + "selector": "operation", + "breakingChanges": [ + { + "change": "remove", + "severity": "DANGER", + "message": "Removing the trait removes the ability for clients to do request or response checksums. The service MUST continue to support old clients by supporting the `httpChecksum` trait." + }, + { + "change": "remove", + "path": "/requestAlgorithmMember", + "severity": "DANGER", + "message": "`requestAlgorithmMember` was removed, so newly generated clients will no longer be able to pick the request checksum algorithms The service MUST continue to support old clients by supporting `requestAlgorithmMember`." + }, + { + "change": "remove", + "path": "/requestValidationModeMember", + "severity": "DANGER", + "message": "`requestValidationModeMember` was removed, so newly generated clients will no longer validate response checksums. The service MUST continue to support old clients by supporting `requestValidationModeMember`." + }, + { + "change": "remove", + "path": "/responseAlgorithms/member", + "severity": "DANGER", + "message": "Members of `responseAlgorithms` were removed, so newly generated clients will no longer validate response checksums for the removed algorithms. The service MUST continue to support old clients by supporting removed compression algorithms." + } + ] + }, + "smithy.api#unstable": {} + } + }, + "aws.protocols#restJson1": { + "type": "structure", + "mixins": [ + { + "target": "aws.protocols#HttpConfiguration" + } + ], + "members": {}, + "traits": { + "smithy.api#documentation": "A RESTful protocol that sends JSON in structured payloads.", + "smithy.api#protocolDefinition": { + "traits": [ + "smithy.api#timestampFormat", + "smithy.api#cors", + "smithy.api#endpoint", + "smithy.api#hostLabel", + "smithy.api#http", + "smithy.api#httpError", + "smithy.api#httpHeader", + "smithy.api#httpLabel", + "smithy.api#httpPayload", + "smithy.api#httpPrefixHeaders", + "smithy.api#httpQuery", + "smithy.api#httpQueryParams", + "smithy.api#httpResponseCode", + "aws.protocols#httpChecksum", + "smithy.api#httpChecksumRequired", + "smithy.api#jsonName" + ] + }, + "smithy.api#trait": { + "selector": "service" + } + } + }, + "aws.protocols#restXml": { + "type": "structure", + "mixins": [ + { + "target": "aws.protocols#HttpConfiguration" + } + ], + "members": { + "noErrorWrapping": { + "target": "smithy.api#Boolean", + "traits": { + "smithy.api#deprecated": {}, + "smithy.api#documentation": "Disables the serialization wrapping of error properties in an 'Error'\nXML element." + } + } + }, + "traits": { + "smithy.api#deprecated": {}, + "smithy.api#documentation": "A RESTful protocol that sends XML in structured payloads.", + "smithy.api#protocolDefinition": { + "traits": [ + "smithy.api#timestampFormat", + "smithy.api#cors", + "smithy.api#endpoint", + "smithy.api#hostLabel", + "smithy.api#http", + "smithy.api#httpError", + "smithy.api#httpHeader", + "smithy.api#httpLabel", + "smithy.api#httpPayload", + "smithy.api#httpPrefixHeaders", + "smithy.api#httpQuery", + "smithy.api#httpQueryParams", + "smithy.api#httpResponseCode", + "aws.protocols#httpChecksum", + "smithy.api#httpChecksumRequired", + "smithy.api#xmlAttribute", + "smithy.api#xmlFlattened", + "smithy.api#xmlName", + "smithy.api#xmlNamespace" + ] + }, + "smithy.api#trait": { + "selector": "service" + }, + "smithy.api#traitValidators": { + "UnsupportedProtocolDocument": { + "selector": "~> member :test(> document)", + "message": "Document types are not supported with restXml" + } + } + } + }, + "com.example#GetItems": { + "type": "operation", + "input": { + "target": "smithy.api#Unit" + }, + "output": { + "target": "com.example#GetItemsOutput" + }, + "traits": { + "aws.apigateway#integration": { + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyLambda.Arn}/invocations" + }, + "httpMethod": "POST", + "credentials": { + "Fn::Sub": "${ApiGatewayRole.Arn}" + }, + "connectionType": "VPC_LINK", + "connectionId": { + "Fn::Sub": "${MyVpcLink}" + }, + "integrationTarget": { + "Fn::Sub": "${MyALBListener.Arn}" + } + }, + "smithy.api#http": { + "method": "GET", + "uri": "/items" + } + } + }, + "com.example#GetItemsOutput": { + "type": "structure", + "members": {}, + "traits": { + "smithy.api#output": {} + } + }, + "com.example#MyService": { + "type": "service", + "version": "2024-01-01", + "operations": [ + { + "target": "com.example#GetItems" + } + ], + "traits": { + "aws.protocols#restJson1": {} + } + }, + "smithy.protocols#StringList": { + "type": "list", + "member": { + "target": "smithy.api#String" + }, + "traits": { + "smithy.api#documentation": "A list of String shapes.", + "smithy.api#private": {} + } + }, + "smithy.protocols#rpcv2Cbor": { + "type": "structure", + "members": { + "http": { + "target": "smithy.protocols#StringList", + "traits": { + "smithy.api#documentation": "Priority ordered list of supported HTTP protocol versions." + } + }, + "eventStreamHttp": { + "target": "smithy.protocols#StringList", + "traits": { + "smithy.api#documentation": "Priority ordered list of supported HTTP protocol versions\nthat are required when using event streams." + } + } + }, + "traits": { + "smithy.api#documentation": "An RPC-based protocol that serializes CBOR payloads.", + "smithy.api#protocolDefinition": { + "traits": [ + "smithy.api#cors", + "smithy.api#endpoint", + "smithy.api#hostLabel", + "smithy.api#httpError", + "smithy.api#requestCompression" + ] + }, + "smithy.api#trait": { + "selector": "service" + }, + "smithy.api#traitValidators": { + "rpcv2Cbor.NoDocuments": { + "selector": "service ~> member :test(> document)", + "message": "This protocol does not support document types in most possible scenarios.", + "severity": "DANGER" + } + } + } + }, + "smithy.protocols#rpcv2Json": { + "type": "structure", + "members": { + "http": { + "target": "smithy.protocols#StringList", + "traits": { + "smithy.api#documentation": "Priority ordered list of supported HTTP protocol versions." + } + }, + "eventStreamHttp": { + "target": "smithy.protocols#StringList", + "traits": { + "smithy.api#documentation": "Priority ordered list of supported HTTP protocol versions\nthat are required when using event streams." + } + } + }, + "traits": { + "smithy.api#documentation": "An RPC-based protocol that serializes JSON payloads.", + "smithy.api#protocolDefinition": { + "traits": [ + "smithy.api#cors", + "smithy.api#endpoint", + "smithy.api#hostLabel", + "smithy.api#httpError", + "smithy.api#requestCompression" + ] + }, + "smithy.api#trait": { + "selector": "service" + } + } + } + } +} \ No newline at end of file diff --git a/smithy-aws-apigateway-cfn/src/test/resources/software/amazon/smithy/aws/apigateway/cfn/integration-model.smithy b/smithy-aws-apigateway-cfn/src/test/resources/software/amazon/smithy/aws/apigateway/cfn/integration-model.smithy new file mode 100644 index 00000000000..3e7772d477a --- /dev/null +++ b/smithy-aws-apigateway-cfn/src/test/resources/software/amazon/smithy/aws/apigateway/cfn/integration-model.smithy @@ -0,0 +1,26 @@ +$version: "2.0" + +namespace com.example + +use aws.apigateway#integration +use aws.protocols#restJson1 + +@restJson1 +service MyService { + version: "2024-01-01" + operations: [GetItems] +} + +@http(method: "GET", uri: "/items") +@integration( + type: "aws_proxy" + uri: "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyLambda.Arn}/invocations" + httpMethod: "POST" + credentials: "${ApiGatewayRole.Arn}" + connectionType: "VPC_LINK" + connectionId: "${MyVpcLink}" + integrationTarget: "${MyALBListener.Arn}" +) +operation GetItems { + output := {} +} From 53e6f6a5e87644f45178af8a19da8ac7fe3385ad Mon Sep 17 00:00:00 2001 From: Yang Liu Date: Mon, 4 May 2026 21:10:00 -0700 Subject: [PATCH 2/3] Update .changes/next-release/feature-91ca4027fb498101b16faf7bad26d9048a0ffb2b.json Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../feature-91ca4027fb498101b16faf7bad26d9048a0ffb2b.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.changes/next-release/feature-91ca4027fb498101b16faf7bad26d9048a0ffb2b.json b/.changes/next-release/feature-91ca4027fb498101b16faf7bad26d9048a0ffb2b.json index 7c8b122b24e..835fa96aa88 100644 --- a/.changes/next-release/feature-91ca4027fb498101b16faf7bad26d9048a0ffb2b.json +++ b/.changes/next-release/feature-91ca4027fb498101b16faf7bad26d9048a0ffb2b.json @@ -1,5 +1,7 @@ { "type": "feature", "description": "Added smithy-cfn-json plugin that serializes Smithy models to JSON AST with CloudFormation Fn Sub intrinsic functions", - "pull_requests": [] + "pull_requests": [ + "[#3095](https://github.com/smithy-lang/smithy/pull/3095)" + ] } From aeb8ab78d6b44c0d698e91a3edb4c813c8b935af Mon Sep 17 00:00:00 2001 From: Yang Liu Date: Wed, 13 May 2026 08:59:11 -0700 Subject: [PATCH 3/3] add jsonAdd functionality --- .../aws/apigateway/cfn/SmithyCfnJson.java | 17 ++++++++++ .../apigateway/cfn/SmithyCfnJsonConfig.java | 16 +++++++++ .../cfn/SmithyCfnJsonConfigTest.java | 12 +++++++ .../cfn/SmithyCfnJsonGoldenFileTest.java | 4 +++ .../cfn/SmithyCfnJsonIntegrationTest.java | 34 +++++++++++++++++++ .../cfn/integration-model.expected.json | 3 ++ 6 files changed, 86 insertions(+) diff --git a/smithy-aws-apigateway-cfn/src/main/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJson.java b/smithy-aws-apigateway-cfn/src/main/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJson.java index fa7c88a4251..b4d662b14c7 100644 --- a/smithy-aws-apigateway-cfn/src/main/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJson.java +++ b/smithy-aws-apigateway-cfn/src/main/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJson.java @@ -4,11 +4,14 @@ */ package software.amazon.smithy.aws.apigateway.cfn; +import java.util.Map; import software.amazon.smithy.build.PluginContext; import software.amazon.smithy.build.SmithyBuildException; import software.amazon.smithy.build.SmithyBuildPlugin; import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.node.NodeMapper; +import software.amazon.smithy.model.node.NodePointer; import software.amazon.smithy.model.node.ObjectNode; import software.amazon.smithy.model.shapes.ModelSerializer; import software.amazon.smithy.model.shapes.ServiceShape; @@ -53,6 +56,20 @@ public void execute(PluginContext context) { astNode = astNode.accept(new CloudFormationFnSubInjector()).expectObjectNode(); } + // Apply jsonAdd patches + for (Map.Entry entry : config.getJsonAdd().entrySet()) { + try { + astNode = NodePointer.parse(entry.getKey()) + .addWithIntermediateValues(astNode, entry.getValue().toNode()) + .expectObjectNode(); + } catch (IllegalArgumentException e) { + throw new SmithyBuildException( + "smithy-cfn-json: invalid jsonAdd pointer '" + + entry.getKey() + "': " + e.getMessage(), + e); + } + } + context.getFileManifest() .writeJson( service.getId().getName() + ".smithy.json", diff --git a/smithy-aws-apigateway-cfn/src/main/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJsonConfig.java b/smithy-aws-apigateway-cfn/src/main/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJsonConfig.java index 4ca88ca68e2..e16b7306b8c 100644 --- a/smithy-aws-apigateway-cfn/src/main/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJsonConfig.java +++ b/smithy-aws-apigateway-cfn/src/main/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJsonConfig.java @@ -4,6 +4,8 @@ */ package software.amazon.smithy.aws.apigateway.cfn; +import java.util.Map; +import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.shapes.ShapeId; /** @@ -25,6 +27,7 @@ public final class SmithyCfnJsonConfig { private ShapeId service; private boolean disableCloudFormationSubstitution; + private Map jsonAdd = java.util.Collections.emptyMap(); /** * Gets the service shape ID to export. @@ -51,4 +54,17 @@ public boolean getDisableCloudFormationSubstitution() { public void setDisableCloudFormationSubstitution(boolean disableCloudFormationSubstitution) { this.disableCloudFormationSubstitution = disableCloudFormationSubstitution; } + + /** + * Gets the JSON Pointer patches to apply to the output. + * + * @return Returns the jsonAdd map. + */ + public Map getJsonAdd() { + return jsonAdd; + } + + public void setJsonAdd(Map jsonAdd) { + this.jsonAdd = java.util.Objects.requireNonNull(jsonAdd); + } } diff --git a/smithy-aws-apigateway-cfn/src/test/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJsonConfigTest.java b/smithy-aws-apigateway-cfn/src/test/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJsonConfigTest.java index d24b6df1d26..5f3dcedf29b 100644 --- a/smithy-aws-apigateway-cfn/src/test/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJsonConfigTest.java +++ b/smithy-aws-apigateway-cfn/src/test/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJsonConfigTest.java @@ -31,5 +31,17 @@ public void defaultsDisableToFalse() { assertThat(config.getService(), equalTo(ShapeId.from("com.example#MyService"))); assertFalse(config.getDisableCloudFormationSubstitution()); + assertThat(config.getJsonAdd().size(), equalTo(0)); + } + + @Test + public void deserializesJsonAdd() { + Node node = Node.parse("{\"service\": \"com.example#MyService\", " + + "\"jsonAdd\": {\"/metadata/custom\": \"hello\"}}"); + SmithyCfnJsonConfig config = new NodeMapper().deserialize(node, SmithyCfnJsonConfig.class); + + assertThat(config.getJsonAdd().size(), equalTo(1)); + assertThat(config.getJsonAdd().get("/metadata/custom").expectStringNode().getValue(), + equalTo("hello")); } } diff --git a/smithy-aws-apigateway-cfn/src/test/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJsonGoldenFileTest.java b/smithy-aws-apigateway-cfn/src/test/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJsonGoldenFileTest.java index f836131eedc..87cda3ebc21 100644 --- a/smithy-aws-apigateway-cfn/src/test/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJsonGoldenFileTest.java +++ b/smithy-aws-apigateway-cfn/src/test/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJsonGoldenFileTest.java @@ -25,6 +25,10 @@ public void producesExpectedOutput() { MockManifest manifest = new MockManifest(); ObjectNode settings = Node.objectNodeBuilder() .withMember("service", "com.example#MyService") + .withMember("jsonAdd", + Node.objectNodeBuilder() + .withMember("/metadata/deployRegion", "us-west-2") + .build()) .build(); PluginContext context = PluginContext.builder() diff --git a/smithy-aws-apigateway-cfn/src/test/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJsonIntegrationTest.java b/smithy-aws-apigateway-cfn/src/test/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJsonIntegrationTest.java index 46104443a7f..497fc2350cf 100644 --- a/smithy-aws-apigateway-cfn/src/test/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJsonIntegrationTest.java +++ b/smithy-aws-apigateway-cfn/src/test/java/software/amazon/smithy/aws/apigateway/cfn/SmithyCfnJsonIntegrationTest.java @@ -88,4 +88,38 @@ public void disabledSubstitutionMatchesModelSerializer() { Node.assertEquals(outputNode, expected); } + + @Test + public void appliesJsonAddPatches() { + Model model = Model.assembler() + .discoverModels(getClass().getClassLoader()) + .addImport(getClass().getResource("integration-model.smithy")) + .assemble() + .unwrap(); + + MockManifest manifest = new MockManifest(); + ObjectNode settings = Node.objectNodeBuilder() + .withMember("service", "com.example#MyService") + .withMember("jsonAdd", + Node.objectNodeBuilder() + .withMember("/metadata/custom", "injected-value") + .build()) + .build(); + + PluginContext context = PluginContext.builder() + .model(model) + .fileManifest(manifest) + .settings(settings) + .build(); + + new SmithyCfnJson().execute(context); + + String output = manifest.getFileString("MyService.smithy.json").get(); + ObjectNode outputNode = Node.parse(output).expectObjectNode(); + + // Verify the jsonAdd patch was applied + assertThat(outputNode.expectObjectMember("metadata") + .expectStringMember("custom") + .getValue(), equalTo("injected-value")); + } } diff --git a/smithy-aws-apigateway-cfn/src/test/resources/software/amazon/smithy/aws/apigateway/cfn/integration-model.expected.json b/smithy-aws-apigateway-cfn/src/test/resources/software/amazon/smithy/aws/apigateway/cfn/integration-model.expected.json index f1e8ce2b935..76d63c44b13 100644 --- a/smithy-aws-apigateway-cfn/src/test/resources/software/amazon/smithy/aws/apigateway/cfn/integration-model.expected.json +++ b/smithy-aws-apigateway-cfn/src/test/resources/software/amazon/smithy/aws/apigateway/cfn/integration-model.expected.json @@ -1648,5 +1648,8 @@ } } } + }, + "metadata": { + "deployRegion": "us-west-2" } } \ No newline at end of file