diff --git a/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddDefaultAwsEndpointRuleSet.java b/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddDefaultAwsEndpointRuleSet.java new file mode 100644 index 000000000000..0c329c06f59e --- /dev/null +++ b/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddDefaultAwsEndpointRuleSet.java @@ -0,0 +1,400 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.aws.typescript.codegen; + +import java.util.List; +import software.amazon.smithy.aws.traits.ServiceTrait; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait; +import software.amazon.smithy.typescript.codegen.TypeScriptSettings; +import software.amazon.smithy.typescript.codegen.endpointsV2.AddDefaultEndpointRuleSet; +import software.amazon.smithy.typescript.codegen.integration.TypeScriptIntegration; + + +/** + * This replaces behavior from {@link EndpointGenerator}. + */ +public class AddDefaultAwsEndpointRuleSet implements TypeScriptIntegration { + /** + * Running before the smithy-typescript default endpoint integration + * will prevent it from applying its non-AWS default ruleset and + * the endpointRequired plugin, both of which are not applicable + * for default regional AWS endpoints. + */ + @Override + public List runBefore() { + return List.of(AddDefaultEndpointRuleSet.class.getCanonicalName()); + } + + @Override + public Model preprocessModel(Model model, TypeScriptSettings settings) { + Model.Builder modelBuilder = model.toBuilder(); + + model.getServiceShapes().forEach(serviceShape -> { + if (!serviceShape.hasTrait(EndpointRuleSetTrait.class) + && AwsTraitsUtils.isAwsService(settings, model)) { + // this branch is for models that identify as AWS services + // but do not include an endpoint ruleset. + + modelBuilder.removeShape(serviceShape.toShapeId()); + modelBuilder.addShape(serviceShape.toBuilder() + .addTrait(getDefaultRegionalEndpointRuleSet( + serviceShape.expectTrait(ServiceTrait.class).getEndpointPrefix()) + ) + .build()); + } + }); + + return modelBuilder.build(); + } + + private EndpointRuleSetTrait getDefaultRegionalEndpointRuleSet(String endpointPrefix) { + return EndpointRuleSetTrait.builder() + .ruleSet(Node.parse(""" +{ + "version": "1.0", + "parameters": { + "Region": { + "builtIn": "AWS::Region", + "required": false, + "documentation": "The AWS Region. This is a default regional AWS endpointRuleSet.", + "type": "String" + }, + "UseDualStack": { + "builtIn": "AWS::UseDualStack", + "required": true, + "default": false, + "documentation": "Whether to use dual-stack.", + "type": "Boolean" + }, + "UseFIPS": { + "builtIn": "AWS::UseFIPS", + "required": true, + "default": false, + "documentation": "Whether to use FIPS-compliant regional endpoint.", + "type": "Boolean" + }, + "Endpoint": { + "builtIn": "SDK::Endpoint", + "required": false, + "documentation": "Override the endpoint.", + "type": "String" + } + }, + "rules": [ + { + "conditions": [ + { + "fn": "isSet", + "argv": [ + { + "ref": "Endpoint" + } + ] + } + ], + "rules": [ + { + "conditions": [ + { + "fn": "booleanEquals", + "argv": [ + { + "ref": "UseFIPS" + }, + true + ] + } + ], + "error": "Invalid Configuration: FIPS and custom endpoint are not supported", + "type": "error" + }, + { + "conditions": [ + { + "fn": "booleanEquals", + "argv": [ + { + "ref": "UseDualStack" + }, + true + ] + } + ], + "error": "Invalid Configuration: Dualstack and custom endpoint are not supported", + "type": "error" + }, + { + "conditions": [], + "endpoint": { + "url": { + "ref": "Endpoint" + }, + "properties": {}, + "headers": {} + }, + "type": "endpoint" + } + ], + "type": "tree" + }, + { + "conditions": [ + { + "fn": "isSet", + "argv": [ + { + "ref": "Region" + } + ] + } + ], + "rules": [ + { + "conditions": [ + { + "fn": "aws.partition", + "argv": [ + { + "ref": "Region" + } + ], + "assign": "PartitionResult" + } + ], + "rules": [ + { + "conditions": [ + { + "fn": "booleanEquals", + "argv": [ + { + "ref": "UseFIPS" + }, + true + ] + }, + { + "fn": "booleanEquals", + "argv": [ + { + "ref": "UseDualStack" + }, + true + ] + } + ], + "rules": [ + { + "conditions": [ + { + "fn": "booleanEquals", + "argv": [ + true, + { + "fn": "getAttr", + "argv": [ + { + "ref": "PartitionResult" + }, + "supportsFIPS" + ] + } + ] + }, + { + "fn": "booleanEquals", + "argv": [ + true, + { + "fn": "getAttr", + "argv": [ + { + "ref": "PartitionResult" + }, + "supportsDualStack" + ] + } + ] + } + ], + "rules": [ + { + "conditions": [], + "endpoint": { + "url": "https://%1$s-fips.{Region}.{PartitionResult#dualStackDnsSuffix}", + "properties": {}, + "headers": {} + }, + "type": "endpoint" + } + ], + "type": "tree" + }, + { + "conditions": [], + "error": "FIPS and DualStack are enabled, but this partition does not support one or both", + "type": "error" + } + ], + "type": "tree" + }, + { + "conditions": [ + { + "fn": "booleanEquals", + "argv": [ + { + "ref": "UseFIPS" + }, + true + ] + } + ], + "rules": [ + { + "conditions": [ + { + "fn": "booleanEquals", + "argv": [ + { + "fn": "getAttr", + "argv": [ + { + "ref": "PartitionResult" + }, + "supportsFIPS" + ] + }, + true + ] + } + ], + "rules": [ + { + "conditions": [ + { + "fn": "stringEquals", + "argv": [ + { + "fn": "getAttr", + "argv": [ + { + "ref": "PartitionResult" + }, + "name" + ] + }, + "aws-us-gov" + ] + } + ], + "endpoint": { + "url": "https://%1$s.{Region}.amazonaws.com", + "properties": {}, + "headers": {} + }, + "type": "endpoint" + }, + { + "conditions": [], + "endpoint": { + "url": "https://%1$s-fips.{Region}.{PartitionResult#dnsSuffix}", + "properties": {}, + "headers": {} + }, + "type": "endpoint" + } + ], + "type": "tree" + }, + { + "conditions": [], + "error": "FIPS is enabled but this partition does not support FIPS", + "type": "error" + } + ], + "type": "tree" + }, + { + "conditions": [ + { + "fn": "booleanEquals", + "argv": [ + { + "ref": "UseDualStack" + }, + true + ] + } + ], + "rules": [ + { + "conditions": [ + { + "fn": "booleanEquals", + "argv": [ + true, + { + "fn": "getAttr", + "argv": [ + { + "ref": "PartitionResult" + }, + "supportsDualStack" + ] + } + ] + } + ], + "rules": [ + { + "conditions": [], + "endpoint": { + "url": "https://%1$s.{Region}.{PartitionResult#dualStackDnsSuffix}", + "properties": {}, + "headers": {} + }, + "type": "endpoint" + } + ], + "type": "tree" + }, + { + "conditions": [], + "error": "DualStack is enabled but this partition does not support DualStack", + "type": "error" + } + ], + "type": "tree" + }, + { + "conditions": [], + "endpoint": { + "url": "https://%1$s.{Region}.{PartitionResult#dnsSuffix}", + "properties": {}, + "headers": {} + }, + "type": "endpoint" + } + ], + "type": "tree" + } + ], + "type": "tree" + }, + { + "conditions": [], + "error": "Invalid Configuration: Missing Region", + "type": "error" + } + ] +} +""".formatted(endpointPrefix))) + .build(); + } +} diff --git a/codegen/smithy-aws-typescript-codegen/src/main/resources/META-INF/services/software.amazon.smithy.typescript.codegen.integration.TypeScriptIntegration b/codegen/smithy-aws-typescript-codegen/src/main/resources/META-INF/services/software.amazon.smithy.typescript.codegen.integration.TypeScriptIntegration index 3d66c3fc046b..1e21b8ec9f3a 100644 --- a/codegen/smithy-aws-typescript-codegen/src/main/resources/META-INF/services/software.amazon.smithy.typescript.codegen.integration.TypeScriptIntegration +++ b/codegen/smithy-aws-typescript-codegen/src/main/resources/META-INF/services/software.amazon.smithy.typescript.codegen.integration.TypeScriptIntegration @@ -7,6 +7,7 @@ software.amazon.smithy.aws.typescript.codegen.AddAwsAuthPlugin software.amazon.smithy.aws.typescript.codegen.AddTokenAuthPlugin software.amazon.smithy.aws.typescript.codegen.AddProtocols software.amazon.smithy.aws.typescript.codegen.AwsEndpointGeneratorIntegration +software.amazon.smithy.aws.typescript.codegen.AddDefaultAwsEndpointRuleSet software.amazon.smithy.aws.typescript.codegen.AddNimbleCustomizations software.amazon.smithy.aws.typescript.codegen.AwsServiceIdIntegration software.amazon.smithy.aws.typescript.codegen.AwsPackageFixturesGeneratorIntegration diff --git a/scripts/validation/endpointRuleSet-structure-analyzer.js b/scripts/validation/endpointRuleSet-structure-analyzer.js new file mode 100644 index 000000000000..d19c92237ff3 --- /dev/null +++ b/scripts/validation/endpointRuleSet-structure-analyzer.js @@ -0,0 +1,33 @@ +const fs = require("node:fs"); +const path = require("node:path"); + +const root = path.join(__dirname, "..", ".."); +const modelsDir = path.join(root, "codegen", "sdk-codegen", "aws-models"); + +function rulesetHasher(ruleset) { + return ruleset.rules + .map((r) => { + if (r.type === "error") { + return "error"; + } + return `${r?.conditions?.[0]?.fn}(${r?.conditions?.[0]?.argv?.[0]?.ref})`; + }) + .join("-"); +} + +const data = {}; + +for (const modelFile of fs.readdirSync(modelsDir)) { + const model = require(path.join(modelsDir, modelFile)); + + const serviceShape = Object.values(model.shapes).find((s) => s.type === "service"); + + const serviceTrait = serviceShape.traits["aws.api#service"]; + const ruleset = serviceShape.traits["smithy.rules#endpointRuleSet"]; + + const hash = rulesetHasher(ruleset); + data[hash] = data[hash] ?? {}; + data[hash][serviceTrait.endpointPrefix] = 1; +} + +console.log(data);