diff --git a/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/traits/DualStackOnlyEndpointsTrait.java b/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/traits/DualStackOnlyEndpointsTrait.java new file mode 100644 index 00000000000..e0d30848476 --- /dev/null +++ b/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/traits/DualStackOnlyEndpointsTrait.java @@ -0,0 +1,33 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.rulesengine.aws.traits; + +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.ObjectNode; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.model.traits.AnnotationTrait; + +/** + * An endpoints modifier trait that indicates a service has only dual stack endpoints, + * does not support IPV4 only endpoints, and should not have the useDualStackEndpoint endpoint parameter. + */ +public final class DualStackOnlyEndpointsTrait extends AnnotationTrait { + public static final ShapeId ID = ShapeId.from("aws.endpoints#dualStackOnlyEndpoints"); + + public DualStackOnlyEndpointsTrait(ObjectNode node) { + super(ID, node); + } + + public DualStackOnlyEndpointsTrait() { + this(Node.objectNode()); + } + + public static final class Provider extends AnnotationTrait.Provider { + public Provider() { + super(ID, DualStackOnlyEndpointsTrait::new); + } + } +} diff --git a/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/traits/EndpointModifierIndex.java b/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/traits/EndpointModifierIndex.java new file mode 100644 index 00000000000..9f65c647c3f --- /dev/null +++ b/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/traits/EndpointModifierIndex.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.rulesengine.aws.traits; + +import java.util.HashMap; +import java.util.Map; +import java.util.TreeMap; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.knowledge.KnowledgeIndex; +import software.amazon.smithy.model.shapes.ServiceShape; +import software.amazon.smithy.model.shapes.Shape; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.model.shapes.ToShapeId; +import software.amazon.smithy.model.traits.Trait; + +/** + * Locates the endpoint modifier traits applied to services. + * + * Endpoint modifier traits are traits that are marked by {@link EndpointModifierTrait} + */ +public final class EndpointModifierIndex implements KnowledgeIndex { + + private final Map> endpointModifierTraits = new HashMap<>(); + + public EndpointModifierIndex(Model model) { + for (ServiceShape serviceShape : model.getServiceShapes()) { + Map result = new TreeMap<>(); + for (Trait trait : serviceShape.getAllTraits().values()) { + Shape traitShape = model.getShape(trait.toShapeId()).get(); + if (traitShape.hasTrait(EndpointModifierTrait.ID)) { + result.put(trait.toShapeId(), trait); + } + } + endpointModifierTraits.put(serviceShape.toShapeId(), result); + } + } + + public static EndpointModifierIndex of(Model model) { + return model.getKnowledge(EndpointModifierIndex.class, EndpointModifierIndex::new); + } + + /** + * Gets all endpoint modifier traits applied to a service. + * + * @param toShapeId Service shape to query + * @return Map of endpoint modifier trait ID to the trait + */ + public Map getEndpointModifierTraits(ToShapeId toShapeId) { + return endpointModifierTraits.get(toShapeId); + } +} diff --git a/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/traits/EndpointModifierTrait.java b/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/traits/EndpointModifierTrait.java new file mode 100644 index 00000000000..1ee4e401dd6 --- /dev/null +++ b/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/traits/EndpointModifierTrait.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.rulesengine.aws.traits; + +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.ObjectNode; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.model.traits.AnnotationTrait; + +/** + * A meta-trait that marks a trait as an endpoint modifier. + * + * Traits that are marked with this trait are applied to service shapes or operation shapes to + * indicate how a client can resolve endpoints for that service or operation. + */ +public final class EndpointModifierTrait extends AnnotationTrait { + public static final ShapeId ID = ShapeId.from("aws.endpoints#endpointsModifier"); + + public EndpointModifierTrait(ObjectNode node) { + super(ID, node); + } + + public EndpointModifierTrait() { + this(Node.objectNode()); + } + + public static final class Provider extends AnnotationTrait.Provider { + public Provider() { + super(ID, EndpointModifierTrait::new); + } + } +} diff --git a/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/traits/EndpointPatternType.java b/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/traits/EndpointPatternType.java new file mode 100644 index 00000000000..f836f7e1529 --- /dev/null +++ b/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/traits/EndpointPatternType.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.rulesengine.aws.traits; + +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.StringNode; + +/** + * The pattern type to use for the partitional services. + */ +public enum EndpointPatternType { + /** An endpoint with pattern `{service}.{dnsSuffix}`.*/ + SERVICE_DNSSUFFIX("service_dnsSuffix"), + + /** An endpoint with pattern `{service}.{region}.{dnsSuffix}`. */ + SERVICE_REGION_DNSSUFFI("service_region_dnsSuffix"); + + private final String name; + + EndpointPatternType(String name) { + this.name = name; + } + + /** + * Gets the name of a partitional service pattern type. + * + * @return Returns a partitional service pattern type name. + */ + public String getName() { + return name; + } + + public static EndpointPatternType fromNode(Node node) { + StringNode value = node.expectStringNode(); + for (EndpointPatternType type: EndpointPatternType.values()) { + if (type.name.equals(value.getValue())) { + return type; + } + } + throw new RuntimeException(String.format( + "Unable to find EndpointPatternType enum with value [%s]", value.getValue())); + } +} diff --git a/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/traits/PartitionEndpointSpecialCase.java b/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/traits/PartitionEndpointSpecialCase.java new file mode 100644 index 00000000000..3adaefb139f --- /dev/null +++ b/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/traits/PartitionEndpointSpecialCase.java @@ -0,0 +1,185 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.rulesengine.aws.traits; + +import java.util.Objects; +import software.amazon.smithy.model.FromSourceLocation; +import software.amazon.smithy.model.SourceLocation; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.ObjectNode; +import software.amazon.smithy.model.node.ToNode; +import software.amazon.smithy.utils.SmithyBuilder; +import software.amazon.smithy.utils.ToSmithyBuilder; + +/** + * A special case that does not follow the services standard patterns + * or are located in a region other than the partition's default global region. + */ +public final class PartitionEndpointSpecialCase + implements FromSourceLocation, ToNode, ToSmithyBuilder { + + private static final String ENDPOINT = "endpoint"; + private static final String REGION = "region"; + private static final String DUAL_STACK = "dualStack"; + private static final String FIPS = "fips"; + private final String endpoint; + private final String region; + private final Boolean dualStack; + private final Boolean fips; + private final SourceLocation sourceLocation; + + private PartitionEndpointSpecialCase(Builder builder) { + this.endpoint = builder.endpoint; + this.region = builder.region; + this.dualStack = builder.dualStack; + this.fips = builder.fips; + this.sourceLocation = Objects.requireNonNull(builder.sourceLocation); + } + + /** + * Gets the endpoint. + * + * @return Returns the endpoint + */ + public String getEndpoint() { + return endpoint; + } + + /** + * Gets the dualStack. + * + * @return Returns the dualStack + */ + public Boolean getDualStack() { + return dualStack; + } + + /** + * Gets the fips. + * + * @return Returns the fips + */ + public Boolean getFips() { + return fips; + } + + /** + * Gets the region. + * + * @return Returns the region + */ + public String getRegion() { + return region; + } + + @Override + public Node toNode() { + return Node.objectNodeBuilder() + .withMember(ENDPOINT, endpoint) + .withMember(REGION, region) + .withMember(DUAL_STACK, dualStack.toString()) + .withMember(FIPS, fips.toString()) + .build(); + } + + @Override + public SmithyBuilder toBuilder() { + return new Builder() + .endpoint(endpoint) + .region(region) + .dualStack(dualStack) + .fips(fips) + .sourceLocation(sourceLocation); + } + + @Override + public SourceLocation getSourceLocation() { + return FromSourceLocation.super.getSourceLocation(); + } + + /** + * Creates a {@link PartitionEndpointSpecialCase} instance from the given Node information. + * + * @param node the node to deserialize. + * @return Returns a PartitionEndpointSpecialCase + */ + public static PartitionEndpointSpecialCase fromNode(Node node) { + ObjectNode objectNode = node.expectObjectNode(); + return builder() + .sourceLocation(objectNode.getSourceLocation()) + .endpoint(objectNode.expectStringMember(ENDPOINT).getValue()) + .region(objectNode.expectStringMember(REGION).getValue()) + .dualStack(objectNode.getBooleanMemberOrDefault(DUAL_STACK, null)) + .fips(objectNode.getBooleanMemberOrDefault(FIPS, null)) + .build(); + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder implements SmithyBuilder { + private String endpoint; + private String region; + private Boolean dualStack; + private Boolean fips; + private SourceLocation sourceLocation = SourceLocation.none(); + + @Override + public PartitionEndpointSpecialCase build() { + return new PartitionEndpointSpecialCase(this); + } + + /** + * Sets the special case endpoint template. + * + * @param endpoint Special case endpoint template to set. + * @return Returns the builder. + */ + public Builder endpoint(String endpoint) { + this.endpoint = endpoint; + return this; + } + + /** + * Sets the dualstack. + * + * @param dualStack dualstack to set. + * @return Returns the builder. + */ + public Builder dualStack(Boolean dualStack) { + this.dualStack = dualStack; + return this; + } + + /** + * Sets the fips. + * + * @param fips fips to set. + * @return Returns the builder. + */ + public Builder fips(Boolean fips) { + this.fips = fips; + return this; + } + + /** + * Sets the region. + * + * @param region region to set. + * @return Returns the builder. + */ + public Builder region(String region) { + this.region = region; + return this; + } + + Builder sourceLocation(SourceLocation sourceLocation) { + this.sourceLocation = sourceLocation; + return this; + } + } +} diff --git a/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/traits/PartitionSpecialCase.java b/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/traits/PartitionSpecialCase.java new file mode 100644 index 00000000000..857e74c5fa7 --- /dev/null +++ b/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/traits/PartitionSpecialCase.java @@ -0,0 +1,157 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.rulesengine.aws.traits; + +import java.util.Objects; +import software.amazon.smithy.model.FromSourceLocation; +import software.amazon.smithy.model.SourceLocation; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.ObjectNode; +import software.amazon.smithy.model.node.ToNode; +import software.amazon.smithy.utils.SmithyBuilder; +import software.amazon.smithy.utils.ToSmithyBuilder; + +/** + * A special case where endpoints for a partition that do not follow the standard patterns. + */ +public final class PartitionSpecialCase implements FromSourceLocation, ToNode, ToSmithyBuilder { + + private static final String ENDPOINT = "endpoint"; + private static final String DUAL_STACK = "dualStack"; + private static final String FIPS = "fips"; + private final String endpoint; + private final Boolean dualStack; + private final Boolean fips; + private final SourceLocation sourceLocation; + + private PartitionSpecialCase(Builder builder) { + this.endpoint = builder.endpoint; + this.dualStack = builder.dualStack; + this.fips = builder.fips; + this.sourceLocation = Objects.requireNonNull(builder.sourceLocation); + } + + /** + * Gets the endpoint. + * + * @return Returns the endpoint + */ + public String getEndpoint() { + return endpoint; + } + + /** + * Gets the dualStack. + * + * @return Returns the dualStack + */ + public Boolean getDualStack() { + return dualStack; + } + + /** + * Gets the fips. + * + * @return Returns the fips + */ + public Boolean getFips() { + return fips; + } + + @Override + public Node toNode() { + return Node.objectNodeBuilder() + .withMember(ENDPOINT, endpoint) + .withMember(DUAL_STACK, dualStack.toString()) + .withMember(FIPS, fips.toString()) + .build(); + } + + @Override + public SmithyBuilder toBuilder() { + return new Builder() + .dualStack(dualStack) + .endpoint(endpoint) + .fips(fips) + .sourceLocation(sourceLocation); + } + + @Override + public SourceLocation getSourceLocation() { + return FromSourceLocation.super.getSourceLocation(); + } + + /** + * Creates a {@link PartitionSpecialCase} instance from the given Node information. + * + * @param node the node to deserialize. + * @return Returns a PartitionSpecialCase + */ + public static PartitionSpecialCase fromNode(Node node) { + ObjectNode objectNode = node.expectObjectNode(); + return builder() + .sourceLocation(objectNode.getSourceLocation()) + .endpoint(objectNode.expectStringMember(ENDPOINT).getValue()) + .dualStack(objectNode.getBooleanMemberOrDefault(DUAL_STACK, null)) + .fips(objectNode.getBooleanMemberOrDefault(FIPS, null)) + .build(); + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder implements SmithyBuilder { + private String endpoint; + private Boolean dualStack; + private Boolean fips; + private SourceLocation sourceLocation = SourceLocation.none(); + + @Override + public PartitionSpecialCase build() { + return new PartitionSpecialCase(this); + } + + /** + * Sets the special case endpoint template. + * + * @param endpoint Special case endpoint template to set. + * @return Returns the builder. + */ + public Builder endpoint(String endpoint) { + this.endpoint = endpoint; + return this; + } + + /** + * Sets the dualstack. + * + * @param dualStack dualstack to set. + * @return Returns the builder. + */ + public Builder dualStack(Boolean dualStack) { + this.dualStack = dualStack; + return this; + } + + /** + * Sets the fips. + * + * @param fips fips to set. + * @return Returns the builder. + */ + public Builder fips(Boolean fips) { + this.fips = fips; + return this; + } + + Builder sourceLocation(SourceLocation sourceLocation) { + this.sourceLocation = sourceLocation; + return this; + } + + } +} diff --git a/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/traits/RegionSpecialCase.java b/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/traits/RegionSpecialCase.java new file mode 100644 index 00000000000..742c551ffe4 --- /dev/null +++ b/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/traits/RegionSpecialCase.java @@ -0,0 +1,183 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.rulesengine.aws.traits; + +import java.util.Objects; +import software.amazon.smithy.model.FromSourceLocation; +import software.amazon.smithy.model.SourceLocation; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.ObjectNode; +import software.amazon.smithy.model.node.ToNode; +import software.amazon.smithy.utils.SmithyBuilder; +import software.amazon.smithy.utils.ToSmithyBuilder; + +/** + * A special case where endpoints for a region that do not follow the standard patterns. + */ +public final class RegionSpecialCase implements FromSourceLocation, ToNode, ToSmithyBuilder { + private static final String ENDPOINT = "endpoint"; + private static final String DUAL_STACK = "dualStack"; + private static final String FIPS = "fips"; + private static final String SIGNING_REGION = "signingRegion"; + private final String endpoint; + private final Boolean dualStack; + private final Boolean fips; + private final String signingRegion; + private final SourceLocation sourceLocation; + + private RegionSpecialCase(Builder builder) { + this.endpoint = builder.endpoint; + this.dualStack = builder.dualStack; + this.fips = builder.fips; + this.signingRegion = builder.signingRegion; + this.sourceLocation = Objects.requireNonNull(builder.sourceLocation); + } + + /** + * Gets the endpoint. + * + * @return Returns the endpoint + */ + public String getEndpoint() { + return endpoint; + } + + /** + * Gets the dualStack. + * + * @return Returns the dualStack + */ + public Boolean getDualStack() { + return dualStack; + } + + /** + * Gets the fips. + * + * @return Returns the fips + */ + public Boolean getFips() { + return fips; + } + + /** + * Gets the signing region. + * + * @return Returns the signing region + */ + public String getSigningRegion() { + return signingRegion; + } + + @Override + public Node toNode() { + return Node.objectNodeBuilder() + .withMember(ENDPOINT, endpoint) + .withMember(DUAL_STACK, dualStack.toString()) + .withMember(FIPS, fips.toString()) + .withMember(SIGNING_REGION, signingRegion) + .build(); + } + + @Override + public SmithyBuilder toBuilder() { + return new Builder() + .dualStack(dualStack) + .endpoint(endpoint) + .fips(fips) + .signingRegion(signingRegion) + .sourceLocation(sourceLocation); + } + + @Override + public SourceLocation getSourceLocation() { + return FromSourceLocation.super.getSourceLocation(); + } + + /** + * Creates a {@link RegionSpecialCase} instance from the given Node information. + * + * @param node the node to deserialize. + * @return Returns a RegionSpecialCase + */ + public static RegionSpecialCase fromNode(Node node) { + ObjectNode objectNode = node.expectObjectNode(); + return builder() + .sourceLocation(objectNode.getSourceLocation()) + .endpoint(objectNode.expectStringMember(ENDPOINT).getValue()) + .dualStack(objectNode.getBooleanMemberOrDefault(DUAL_STACK, null)) + .fips(objectNode.getBooleanMemberOrDefault(FIPS, null)) + .signingRegion(objectNode.getStringMemberOrDefault(SIGNING_REGION, null)) + .build(); + } + + public static Builder builder() { + return new RegionSpecialCase.Builder(); + } + + public static final class Builder implements SmithyBuilder { + private String endpoint; + private Boolean dualStack; + private Boolean fips; + private String signingRegion; + private SourceLocation sourceLocation = SourceLocation.none(); + + @Override + public RegionSpecialCase build() { + return new RegionSpecialCase(this); + } + + /** + * Sets the special case endpoint template. + * + * @param endpoint Special case endpoint template to set. + * @return Returns the builder. + */ + public Builder endpoint(String endpoint) { + this.endpoint = Objects.requireNonNull(endpoint); + return this; + } + + /** + * Sets the dualstack. + * + * @param dualStack dualstack to set. + * @return Returns the builder. + */ + public Builder dualStack(Boolean dualStack) { + this.dualStack = dualStack; + return this; + } + + /** + * Sets the fips. + * + * @param fips fips to set. + * @return Returns the builder. + */ + public Builder fips(Boolean fips) { + this.fips = fips; + return this; + } + + /** + * Sets the signing region. + * + * @param signingRegion region to set. + * @return Returns the builder. + */ + public Builder signingRegion(String signingRegion) { + this.signingRegion = signingRegion; + return this; + } + + public Builder sourceLocation(SourceLocation sourceLocation) { + this.sourceLocation = sourceLocation; + return this; + } + + } +} diff --git a/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/traits/RuleBasedEndpointsTrait.java b/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/traits/RuleBasedEndpointsTrait.java new file mode 100644 index 00000000000..2f050e3d590 --- /dev/null +++ b/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/traits/RuleBasedEndpointsTrait.java @@ -0,0 +1,37 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.rulesengine.aws.traits; + +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.ObjectNode; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.model.traits.AnnotationTrait; + +/** + * A trait that indicates that a service has handwritten endpoint rules. + * + * Services marked with this trait have handwritten endpoint rules that + * extend or replace their standard generated endpoint rules through an external mechanism. + * This trait marks the presence of handwritten rules, which are added to the model by a transformer, + * but does not specify their behavior. + */ +public final class RuleBasedEndpointsTrait extends AnnotationTrait { + public static final ShapeId ID = ShapeId.from("aws.endpoints#rulesBasedEndpoints"); + + public RuleBasedEndpointsTrait(ObjectNode node) { + super(ID, node); + } + + public RuleBasedEndpointsTrait() { + this(Node.objectNode()); + } + + public static final class Provider extends AnnotationTrait.Provider { + public Provider() { + super(ID, RuleBasedEndpointsTrait::new); + } + } +} diff --git a/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/traits/StandardPartitionalEndpointsTrait.java b/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/traits/StandardPartitionalEndpointsTrait.java new file mode 100644 index 00000000000..d20319f8921 --- /dev/null +++ b/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/traits/StandardPartitionalEndpointsTrait.java @@ -0,0 +1,175 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.rulesengine.aws.traits; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.ObjectNode; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.model.traits.AbstractTrait; +import software.amazon.smithy.model.traits.AbstractTraitBuilder; +import software.amazon.smithy.model.traits.Trait; +import software.amazon.smithy.utils.BuilderRef; +import software.amazon.smithy.utils.SmithyBuilder; +import software.amazon.smithy.utils.ToSmithyBuilder; + +/** + * An endpoints modifier trait that indicates that a service is partitional + * and a single endpoint should resolve per partition. + */ +public final class StandardPartitionalEndpointsTrait extends AbstractTrait + implements ToSmithyBuilder { + public static final ShapeId ID = ShapeId.from("aws.endpoints#standardPartitionalEndpoints"); + public static final String PARTITION_ENDPOINT_SPECIAL_CASES = "partitionEndpointSpecialCases"; + public static final String ENDPOINT_PATTERN_TYPE = "endpointPatternType"; + + private final Map> partitionEndpointSpecialCases; + + private final EndpointPatternType endpointPatternType; + + public StandardPartitionalEndpointsTrait(StandardPartitionalEndpointsTrait.Builder builder) { + super(ID, builder.getSourceLocation()); + partitionEndpointSpecialCases = builder.partitionEndpointSpecialCases.copy(); + endpointPatternType = Objects.requireNonNull(builder.endpointPatternType); + } + + /** + * Gets the map of partition string to a list of partition endpoint special cases defined in the trait. + * + * @return Returns a map of partition string to a list of {@link PartitionEndpointSpecialCase} + */ + public Map> getPartitionEndpointSpecialCases() { + return partitionEndpointSpecialCases; + } + + /** + * Gets the endpoint pattern type defined in the trait. + * + * @return Returns a {@link EndpointPatternType} + */ + public EndpointPatternType getEndpointPatternType() { + return endpointPatternType; + } + + @Override + protected Node createNode() { + ObjectNode.Builder partitionEndpointSpecialCasesNodeBuilder = ObjectNode.objectNodeBuilder(); + for (Map.Entry> entry + : partitionEndpointSpecialCases.entrySet()) { + List nodes = new ArrayList<>(); + for (PartitionEndpointSpecialCase partitionEndpointSpecialCase : entry.getValue()) { + nodes.add(partitionEndpointSpecialCase.toNode()); + } + partitionEndpointSpecialCasesNodeBuilder.withMember(entry.getKey(), Node.fromNodes(nodes)); + } + + return ObjectNode.objectNodeBuilder() + .sourceLocation(getSourceLocation()) + .withMember(PARTITION_ENDPOINT_SPECIAL_CASES, partitionEndpointSpecialCasesNodeBuilder.build()) + .withMember(ENDPOINT_PATTERN_TYPE, endpointPatternType.getName()) + .build(); + } + + @Override + public SmithyBuilder toBuilder() { + return new Builder() + .partitionEndpointSpecialCases(partitionEndpointSpecialCases) + .endpointPatternType(endpointPatternType); + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Provider extends AbstractTrait.Provider { + public Provider() { + super(ID); + } + + @Override + public Trait createTrait(ShapeId target, Node value) { + ObjectNode objectNode = value.expectObjectNode(); + + EndpointPatternType endpointPatternType = EndpointPatternType + .fromNode(objectNode.expectStringMember(ENDPOINT_PATTERN_TYPE)); + + StandardPartitionalEndpointsTrait.Builder builder = builder() + .sourceLocation(value) + .endpointPatternType(endpointPatternType); + + if (objectNode.containsMember(PARTITION_ENDPOINT_SPECIAL_CASES)) { + for (Map.Entry entry + : objectNode.expectObjectMember(PARTITION_ENDPOINT_SPECIAL_CASES).getStringMap().entrySet()) { + List partitionEndpointSpecialCases = new ArrayList<>(); + for (Node node: entry.getValue().expectArrayNode().getElements()) { + partitionEndpointSpecialCases.add(PartitionEndpointSpecialCase.fromNode(node)); + } + builder.putPartitionEndpointSpecialCase( + entry.getKey(), Collections.unmodifiableList(partitionEndpointSpecialCases)); + } + } + + StandardPartitionalEndpointsTrait result = builder.build(); + result.setNodeCache(value); + return result; + } + } + + public static final class Builder extends AbstractTraitBuilder { + private final BuilderRef>> partitionEndpointSpecialCases = + BuilderRef.forOrderedMap(); + private EndpointPatternType endpointPatternType; + + /** + * Sets the partition endpoint special cases. + * + * @param partitionEndpointSpecialCases Map of partition string to list of {@link PartitionEndpointSpecialCase} + * @return Returns the builder. + */ + public Builder partitionEndpointSpecialCases( + Map> partitionEndpointSpecialCases + ) { + this.partitionEndpointSpecialCases.clear(); + this.partitionEndpointSpecialCases.get().putAll(partitionEndpointSpecialCases); + return this; + } + + /** + * Sets the list of partition endpoint special cases for a partition. + * + * @param partition the partition to use + * @param partitionEndpointSpecialCases Map of partition string to list of {@link PartitionEndpointSpecialCase} + * @return Returns the builder. + */ + public Builder putPartitionEndpointSpecialCase( + String partition, + List partitionEndpointSpecialCases + ) { + this.partitionEndpointSpecialCases.get().put(partition, partitionEndpointSpecialCases); + return this; + } + + /** + * Sets the endpoint pattern type. + * + * @param endpointPatternType the endpoint pattern type to use + * @return Returns the builder. + */ + public Builder endpointPatternType(EndpointPatternType endpointPatternType) { + this.endpointPatternType = endpointPatternType; + return this; + } + + @Override + public StandardPartitionalEndpointsTrait build() { + return new StandardPartitionalEndpointsTrait(this); + } + } +} diff --git a/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/traits/StandardRegionalEndpointsTrait.java b/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/traits/StandardRegionalEndpointsTrait.java new file mode 100644 index 00000000000..0b867846b77 --- /dev/null +++ b/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/traits/StandardRegionalEndpointsTrait.java @@ -0,0 +1,197 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.rulesengine.aws.traits; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.ObjectNode; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.model.traits.AbstractTrait; +import software.amazon.smithy.model.traits.AbstractTraitBuilder; +import software.amazon.smithy.model.traits.Trait; +import software.amazon.smithy.utils.BuilderRef; +import software.amazon.smithy.utils.SmithyBuilder; +import software.amazon.smithy.utils.ToSmithyBuilder; + +/** + * An endpoints modifier trait that indicates that a service's endpoints should be resolved + * using the standard AWS regional patterns. + */ +public final class StandardRegionalEndpointsTrait extends AbstractTrait + implements ToSmithyBuilder { + public static final ShapeId ID = ShapeId.from("aws.endpoints#standardRegionalEndpoints"); + public static final String PARTITION_SPECIAL_CASES = "partitionSpecialCases"; + public static final String REGION_SPECIAL_CASES = "regionSpecialCases"; + + private final Map> partitionSpecialCases; + private final Map> regionSpecialCases; + + public StandardRegionalEndpointsTrait(StandardRegionalEndpointsTrait.Builder builder) { + super(ID, builder.getSourceLocation()); + partitionSpecialCases = builder.partitionSpecialCases.copy(); + regionSpecialCases = builder.regionSpecialCases.copy(); + } + + /** + * Gets the map of partition string to a list of partition special cases defined in the trait. + * + * @return Returns a map of partition string to a list of {@link PartitionSpecialCase} + */ + public Map> getPartitionSpecialCases() { + return partitionSpecialCases; + } + + /** + * Gets the map of partition string to a list of region special cases defined in the trait. + * + * @return Returns a map of region string to a list of {@link RegionSpecialCase} + */ + public Map> getRegionSpecialCases() { + return regionSpecialCases; + } + + @Override + protected Node createNode() { + ObjectNode.Builder partitionSpecialCasesNodeBuilder = ObjectNode.objectNodeBuilder(); + for (Map.Entry> entry: partitionSpecialCases.entrySet()) { + List nodes = new ArrayList<>(); + for (PartitionSpecialCase partitionSpecialCase : entry.getValue()) { + nodes.add(partitionSpecialCase.toNode()); + } + partitionSpecialCasesNodeBuilder.withMember(entry.getKey(), Node.fromNodes(nodes)); + } + + ObjectNode.Builder regionSpecialCasesNodeBuilder = ObjectNode.objectNodeBuilder(); + for (Map.Entry> entry: regionSpecialCases.entrySet()) { + List nodes = new ArrayList<>(); + for (RegionSpecialCase regionSpecialCase : entry.getValue()) { + nodes.add(regionSpecialCase.toNode()); + } + regionSpecialCasesNodeBuilder.withMember(entry.getKey(), Node.fromNodes(nodes)); + } + + return ObjectNode.objectNodeBuilder() + .sourceLocation(getSourceLocation()) + .withMember(PARTITION_SPECIAL_CASES, partitionSpecialCasesNodeBuilder.build()) + .withMember(REGION_SPECIAL_CASES, partitionSpecialCasesNodeBuilder.build()) + .build(); + } + + @Override + public SmithyBuilder toBuilder() { + return new Builder() + .partitionSpecialCases(partitionSpecialCases) + .regionSpecialCases(regionSpecialCases); + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Provider extends AbstractTrait.Provider { + public Provider() { + super(ID); + } + + @Override + public Trait createTrait(ShapeId target, Node value) { + ObjectNode objectNode = value.expectObjectNode(); + + StandardRegionalEndpointsTrait.Builder builder = builder() + .sourceLocation(value); + + if (objectNode.containsMember(PARTITION_SPECIAL_CASES)) { + for (Map.Entry entry + : objectNode.expectObjectMember(PARTITION_SPECIAL_CASES).getStringMap().entrySet()) { + List partitionSpecialCases = new ArrayList<>(); + for (Node node: entry.getValue().expectArrayNode().getElements()) { + partitionSpecialCases.add(PartitionSpecialCase.fromNode(node)); + } + builder.putPartitionSpecialCases( + entry.getKey(), Collections.unmodifiableList(partitionSpecialCases)); + } + } + + if (objectNode.containsMember(REGION_SPECIAL_CASES)) { + for (Map.Entry entry + : objectNode.expectObjectMember(REGION_SPECIAL_CASES).getStringMap().entrySet()) { + List regionSpecialCases = new ArrayList<>(); + for (Node node: entry.getValue().expectArrayNode().getElements()) { + regionSpecialCases.add(RegionSpecialCase.fromNode(node)); + } + builder.putRegionSpecialCases(entry.getKey(), Collections.unmodifiableList(regionSpecialCases)); + } + } + + StandardRegionalEndpointsTrait result = builder.build(); + result.setNodeCache(value); + return result; + } + } + + public static final class Builder extends AbstractTraitBuilder { + private final BuilderRef>> partitionSpecialCases = + BuilderRef.forOrderedMap(); + private final BuilderRef>> regionSpecialCases = + BuilderRef.forOrderedMap(); + + /** + * Sets the partition special cases. + * + * @param partitionSpecialCases Map of partition string to list of {@link PartitionSpecialCase} + * @return Returns the builder. + */ + public Builder partitionSpecialCases(Map> partitionSpecialCases) { + this.partitionSpecialCases.clear(); + this.partitionSpecialCases.get().putAll(partitionSpecialCases); + return this; + } + + /** + * Sets the list of partition special cases for a partition. + * + * @param partition the partition to use + * @param partitionSpecialCases Map of partition string to list of {@link PartitionSpecialCase} + * @return Returns the builder. + */ + public Builder putPartitionSpecialCases(String partition, List partitionSpecialCases) { + this.partitionSpecialCases.get().put(partition, partitionSpecialCases); + return this; + } + + /** + * Sets the region special cases. + * + * @param regionSpecialCases Map of region string to list of {@link RegionSpecialCase} + * @return Returns the builder. + */ + public Builder regionSpecialCases(Map> regionSpecialCases) { + this.regionSpecialCases.clear(); + this.regionSpecialCases.get().putAll(regionSpecialCases); + return this; + } + + /** + * Sets the list of region special cases for a region. + * + * @param region the region to use + * @param regionSpecialCases Map of region string to list of {@link RegionSpecialCase} + * @return Returns the builder. + */ + public Builder putRegionSpecialCases(String region, List regionSpecialCases) { + this.regionSpecialCases.get().put(region, regionSpecialCases); + return this; + } + + @Override + public StandardRegionalEndpointsTrait build() { + return new StandardRegionalEndpointsTrait(this); + } + } +} diff --git a/smithy-aws-endpoints/src/main/resources/META-INF/services/software.amazon.smithy.model.traits.TraitService b/smithy-aws-endpoints/src/main/resources/META-INF/services/software.amazon.smithy.model.traits.TraitService new file mode 100644 index 00000000000..f7f39421662 --- /dev/null +++ b/smithy-aws-endpoints/src/main/resources/META-INF/services/software.amazon.smithy.model.traits.TraitService @@ -0,0 +1,5 @@ +software.amazon.smithy.rulesengine.aws.traits.EndpointModifierTrait$Provider +software.amazon.smithy.rulesengine.aws.traits.DualStackOnlyEndpointsTrait$Provider +software.amazon.smithy.rulesengine.aws.traits.RuleBasedEndpointsTrait$Provider +software.amazon.smithy.rulesengine.aws.traits.StandardRegionalEndpointsTrait$Provider +software.amazon.smithy.rulesengine.aws.traits.StandardPartitionalEndpointsTrait$Provider diff --git a/smithy-aws-endpoints/src/main/resources/META-INF/smithy/aws.endpoints.smithy b/smithy-aws-endpoints/src/main/resources/META-INF/smithy/aws.endpoints.smithy new file mode 100644 index 00000000000..cfcaf96bde1 --- /dev/null +++ b/smithy-aws-endpoints/src/main/resources/META-INF/smithy/aws.endpoints.smithy @@ -0,0 +1,132 @@ +$version: "2.0" + +namespace aws.endpoints + +/// Marks a trait as an endpoints modifier defining trait. +/// +/// The targeted trait must only be applied to service shapes, +/// must be a structure, and must have the `trait` trait. +@trait( + selector: "structure[trait|trait]", + breakingChanges: [{change: "presence"}] +) +structure endpointsModifier { } + +/// Marks that a services endpoints should be resolved using +/// standard regional endpoint patterns. +@trait( + selector: "service", + conflicts: [standardPartitionalEndpoints], + breakingChanges: [{change: "remove"}] +) +@endpointsModifier +@unstable +structure standardRegionalEndpoints { + /// A list of partition special cases - endpoints for a partition that do not follow the standard patterns. + partitionSpecialCases: PartitionSpecialCaseMap, + /// A list of regional special cases - endpoints for a region that do not follow the standard patterns. + regionSpecialCases: RegionSpecialCaseMap +} + +@private +map PartitionSpecialCaseMap { + key: String, + value: PartitionSpecialCaseList +} + +@private +list PartitionSpecialCaseList { + member: PartitionSpecialCase +} + +@private +structure PartitionSpecialCase { + @required + endpoint: String, + + dualStack: Boolean, + fips: Boolean +} + +@private +map RegionSpecialCaseMap { + key: String, + value: RegionSpecialCaseList +} + +@private +list RegionSpecialCaseList { + member: RegionSpecialCase +} + +@private +structure RegionSpecialCase { + @required + endpoint: String, + + dualStack: Boolean, + fips: Boolean, + signingRegion: String +} + +/// Marks that a services is non-regionalized and has +/// a single endpoint in each partition. +@trait( + selector: "service", + conflicts: [standardRegionalEndpoints], + breakingChanges: [{change: "any"}] +) +@endpointsModifier +@unstable +structure standardPartitionalEndpoints { + /// The pattern type to use for the partition endpoint. + @required + endpointPatternType: PartitionEndpointPattern, + + /// A list of partition endpoint special cases - partitions that do not follow the services standard patterns + /// or are located in a region other than the partition's defaultGlobalRegion. + partitionEndpointSpecialCases: PartitionEndpointSpecialCaseMap, +} + +@private +enum PartitionEndpointPattern { + SERVICE_DNSSUFFIX = "service_dnsSuffix" + SERVICE_REGION_DNSSUFFIX = "service_region_dnsSuffix" +} + +@private +map PartitionEndpointSpecialCaseMap { + key: String, + value: PartitionEndpointSpecialCaseList +} + +@private +list PartitionEndpointSpecialCaseList { + member: PartitionEndpointSpecialCase +} + +@private +structure PartitionEndpointSpecialCase { + endpoint: String, + region: String, + dualStack: Boolean, + fips: Boolean, +} + +/// Marks that a services has only dualStack endpoints. +@trait( + selector: "service", + breakingChanges: [{change: "any"}] +) +@endpointsModifier +@unstable +structure dualStackOnlyEndpoints { } + +/// Marks that a services has hand written endpoint rules. +@trait( + selector: "service", + breakingChanges: [{change: "any"}] +) +@endpointsModifier +@unstable +structure rulesBasedEndpoints { } diff --git a/smithy-aws-endpoints/src/main/resources/META-INF/smithy/manifest b/smithy-aws-endpoints/src/main/resources/META-INF/smithy/manifest new file mode 100644 index 00000000000..665fc53b2e9 --- /dev/null +++ b/smithy-aws-endpoints/src/main/resources/META-INF/smithy/manifest @@ -0,0 +1 @@ +aws.endpoints.smithy diff --git a/smithy-aws-endpoints/src/test/java/software/amazon/smithy/rulesengine/aws/traits/DualStackOnlyEndpointsTraitTest.java b/smithy-aws-endpoints/src/test/java/software/amazon/smithy/rulesengine/aws/traits/DualStackOnlyEndpointsTraitTest.java new file mode 100644 index 00000000000..0af54cc3377 --- /dev/null +++ b/smithy-aws-endpoints/src/test/java/software/amazon/smithy/rulesengine/aws/traits/DualStackOnlyEndpointsTraitTest.java @@ -0,0 +1,27 @@ +package software.amazon.smithy.rulesengine.aws.traits; + +import org.junit.jupiter.api.Test; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.shapes.ShapeId; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; + +class DualStackOnlyEndpointsTraitTest { + @Test + public void loadsFromModel() { + final Model model = Model.assembler() + .discoverModels(getClass().getClassLoader()) + .addImport(getClass().getResource("dualStackOnlyEndpoints.smithy")) + .assemble() + .unwrap(); + + Optional trait = model + .expectShape(ShapeId.from("ns.foo#Service1")) + .asServiceShape().get() + .getTrait(DualStackOnlyEndpointsTrait.class); + + assertTrue(trait.isPresent()); + } +} diff --git a/smithy-aws-endpoints/src/test/java/software/amazon/smithy/rulesengine/aws/traits/EndpointModifierIndexTest.java b/smithy-aws-endpoints/src/test/java/software/amazon/smithy/rulesengine/aws/traits/EndpointModifierIndexTest.java new file mode 100644 index 00000000000..53e79c0c14e --- /dev/null +++ b/smithy-aws-endpoints/src/test/java/software/amazon/smithy/rulesengine/aws/traits/EndpointModifierIndexTest.java @@ -0,0 +1,51 @@ +package software.amazon.smithy.rulesengine.aws.traits; + +import org.junit.jupiter.api.Test; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.shapes.ServiceShape; +import software.amazon.smithy.model.shapes.ShapeId; + +import static org.junit.jupiter.api.Assertions.*; + +class EndpointModifierIndexTest { + @Test + public void loadsFromModel() { + final Model model = Model.assembler() + .discoverModels(getClass().getClassLoader()) + .addImport(getClass().getResource("endpointModifierIndex.smithy")) + .assemble() + .unwrap(); + + ShapeId service1 = getServiceShapeId(model, "ns.foo#Service1"); + ShapeId service2 = getServiceShapeId(model, "ns.foo#Service2"); + ShapeId service3 = getServiceShapeId(model, "ns.foo#Service3"); + ShapeId service4 = getServiceShapeId(model, "ns.foo#Service4"); + + EndpointModifierIndex index = new EndpointModifierIndex(model); + + assertEquals(index.getEndpointModifierTraits(service1).size(), 1); + assertInstanceOf(StandardRegionalEndpointsTrait.class, + index.getEndpointModifierTraits(service1).get(StandardRegionalEndpointsTrait.ID)); + + assertEquals(index.getEndpointModifierTraits(service2).size(), 1); + assertInstanceOf(StandardPartitionalEndpointsTrait.class, + index.getEndpointModifierTraits(service2).get(StandardPartitionalEndpointsTrait.ID)); + + assertEquals(index.getEndpointModifierTraits(service3).size(), 2); + assertInstanceOf(StandardRegionalEndpointsTrait.class, + index.getEndpointModifierTraits(service3).get(StandardRegionalEndpointsTrait.ID)); + assertInstanceOf(DualStackOnlyEndpointsTrait.class, + index.getEndpointModifierTraits(service3).get(DualStackOnlyEndpointsTrait.ID)); + + assertEquals(index.getEndpointModifierTraits(service4).size(), 2); + assertInstanceOf(StandardPartitionalEndpointsTrait.class, + index.getEndpointModifierTraits(service4).get(StandardPartitionalEndpointsTrait.ID)); + assertInstanceOf(RuleBasedEndpointsTrait.class, + index.getEndpointModifierTraits(service4).get(RuleBasedEndpointsTrait.ID)); + } + + private ShapeId getServiceShapeId(Model model, String service) { + return model + .expectShape(ShapeId.from(service), ServiceShape.class).toShapeId(); + } +} diff --git a/smithy-aws-endpoints/src/test/java/software/amazon/smithy/rulesengine/aws/traits/RuleBasedEndpointsTraitTest.java b/smithy-aws-endpoints/src/test/java/software/amazon/smithy/rulesengine/aws/traits/RuleBasedEndpointsTraitTest.java new file mode 100644 index 00000000000..91d9aa4bc47 --- /dev/null +++ b/smithy-aws-endpoints/src/test/java/software/amazon/smithy/rulesengine/aws/traits/RuleBasedEndpointsTraitTest.java @@ -0,0 +1,27 @@ +package software.amazon.smithy.rulesengine.aws.traits; + +import org.junit.jupiter.api.Test; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.shapes.ShapeId; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +class RuleBasedEndpointsTraitTest { + @Test + public void loadsFromModel() { + final Model model = Model.assembler() + .discoverModels(getClass().getClassLoader()) + .addImport(getClass().getResource("ruleBasedEndpoints.smithy")) + .assemble() + .unwrap(); + + Optional trait = model + .expectShape(ShapeId.from("ns.foo#Service1")) + .asServiceShape().get() + .getTrait(RuleBasedEndpointsTrait.class); + + assertTrue(trait.isPresent()); + } +} diff --git a/smithy-aws-endpoints/src/test/java/software/amazon/smithy/rulesengine/aws/traits/StandardPartitionalEndpointsTraitTest.java b/smithy-aws-endpoints/src/test/java/software/amazon/smithy/rulesengine/aws/traits/StandardPartitionalEndpointsTraitTest.java new file mode 100644 index 00000000000..48108e249f5 --- /dev/null +++ b/smithy-aws-endpoints/src/test/java/software/amazon/smithy/rulesengine/aws/traits/StandardPartitionalEndpointsTraitTest.java @@ -0,0 +1,54 @@ +package software.amazon.smithy.rulesengine.aws.traits; + +import org.junit.jupiter.api.Test; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.shapes.ShapeId; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +class StandardPartitionalEndpointsTraitTest { + @Test + public void loadsFromModel() { + final Model model = Model.assembler() + .discoverModels(getClass().getClassLoader()) + .addImport(getClass().getResource("standardPartitionalEndpoints.smithy")) + .assemble() + .unwrap(); + + StandardPartitionalEndpointsTrait trait; + + trait = getTraitFromService(model, "ns.foo#Service1"); + + assertEquals(trait.getEndpointPatternType(), EndpointPatternType.SERVICE_DNSSUFFIX); + assertEquals(trait.getPartitionEndpointSpecialCases().size(), 0); + + trait = getTraitFromService(model, "ns.foo#Service2"); + + assertEquals(trait.getEndpointPatternType(), EndpointPatternType.SERVICE_REGION_DNSSUFFI); + assertEquals(trait.getPartitionEndpointSpecialCases().size(), 1); + + List cases = trait.getPartitionEndpointSpecialCases().get("aws-us-gov"); + + PartitionEndpointSpecialCase case1 = cases.get(0); + assertEquals(case1.getEndpoint(), "myservice.{region}.{dnsSuffix}"); + assertEquals(case1.getFips(), true); + assertEquals(case1.getRegion(), "us-east-1"); + assertNull(case1.getDualStack()); + + PartitionEndpointSpecialCase case2 = cases.get(1); + assertEquals(case2.getEndpoint(), "myservice.global.amazonaws.com"); + assertEquals(case2.getDualStack(), true); + assertEquals(case2.getRegion(), "us-west-2"); + assertNull(case2.getFips()); + } + + private StandardPartitionalEndpointsTrait getTraitFromService(Model model, String service) { + return model + .expectShape(ShapeId.from(service)) + .asServiceShape().get() + .getTrait(StandardPartitionalEndpointsTrait.class).get(); + } +} diff --git a/smithy-aws-endpoints/src/test/java/software/amazon/smithy/rulesengine/aws/traits/StandardRegionalEndpointsTraitTest.java b/smithy-aws-endpoints/src/test/java/software/amazon/smithy/rulesengine/aws/traits/StandardRegionalEndpointsTraitTest.java new file mode 100644 index 00000000000..3fa40fc7d8b --- /dev/null +++ b/smithy-aws-endpoints/src/test/java/software/amazon/smithy/rulesengine/aws/traits/StandardRegionalEndpointsTraitTest.java @@ -0,0 +1,57 @@ +package software.amazon.smithy.rulesengine.aws.traits; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.util.List; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.shapes.ShapeId; + +class StandardRegionalEndpointsTraitTest { + @Test + public void loadsFromModel() { + final Model model = Model.assembler() + .discoverModels(getClass().getClassLoader()) + .addImport(getClass().getResource("standardRegionalEndpoints.smithy")) + .assemble() + .unwrap(); + StandardRegionalEndpointsTrait trait; + + trait = getTraitFromService(model, "ns.foo#Service1"); + + assertEquals(trait.getPartitionSpecialCases().size(), 0); + assertEquals(trait.getRegionSpecialCases().size(), 0); + + trait = getTraitFromService(model, "ns.foo#Service2"); + + assertEquals(trait.getRegionSpecialCases().size(), 0); + assertEquals(trait.getRegionSpecialCases().size(), 0); + + trait = getTraitFromService(model, "ns.foo#Service3"); + + assertEquals(trait.getRegionSpecialCases().size(), 1); + assertEquals(trait.getRegionSpecialCases().size(), 1); + List partitionSpecialCases = trait.getPartitionSpecialCases().get("aws-us-gov"); + + PartitionSpecialCase partitionSpecialCase1 = partitionSpecialCases.get(0); + assertEquals(partitionSpecialCase1.getEndpoint(), "myservice.{region}.{dnsSuffix}"); + assertEquals(partitionSpecialCase1.getFips(), true); + assertNull(partitionSpecialCase1.getDualStack()); + + PartitionSpecialCase partitionSpecialCase2 = partitionSpecialCases.get(1); + assertEquals(partitionSpecialCase2.getEndpoint(), "myservice.global.amazonaws.com"); + assertEquals(partitionSpecialCase2.getDualStack(), true); + assertNull(partitionSpecialCase2.getFips()); + + List regionSpecialCases = trait.getRegionSpecialCases().get("us-east-1"); + assertEquals(regionSpecialCases.size(), 0); + } + + private StandardRegionalEndpointsTrait getTraitFromService(Model model, String service) { + return model + .expectShape(ShapeId.from(service)) + .asServiceShape().get() + .getTrait(StandardRegionalEndpointsTrait.class).get(); + } +} diff --git a/smithy-aws-endpoints/src/test/java/software/amazon/smithy/rulesengine/aws/traits/TestRunnerTest.java b/smithy-aws-endpoints/src/test/java/software/amazon/smithy/rulesengine/aws/traits/TestRunnerTest.java new file mode 100644 index 00000000000..f051cfeceb7 --- /dev/null +++ b/smithy-aws-endpoints/src/test/java/software/amazon/smithy/rulesengine/aws/traits/TestRunnerTest.java @@ -0,0 +1,21 @@ +package software.amazon.smithy.rulesengine.aws.traits; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import software.amazon.smithy.model.validation.testrunner.SmithyTestCase; +import software.amazon.smithy.model.validation.testrunner.SmithyTestSuite; + +import java.util.concurrent.Callable; +import java.util.stream.Stream; + +public class TestRunnerTest { + public static Stream source() { + return SmithyTestSuite.defaultParameterizedTestSource(TestRunnerTest.class); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("source") + public void testRunner(String filename, Callable callable) throws Exception { + callable.call(); + } +} diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/traits/dualStackOnlyEndpoints.smithy b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/traits/dualStackOnlyEndpoints.smithy new file mode 100644 index 00000000000..427f157b200 --- /dev/null +++ b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/traits/dualStackOnlyEndpoints.smithy @@ -0,0 +1,10 @@ +$version: "2.0" + +namespace ns.foo + +use aws.endpoints#dualStackOnlyEndpoints + +@dualStackOnlyEndpoints +service Service1 { + version: "2021-06-29" +} diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/traits/endpointModifierIndex.smithy b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/traits/endpointModifierIndex.smithy new file mode 100644 index 00000000000..8d8cc1be72f --- /dev/null +++ b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/traits/endpointModifierIndex.smithy @@ -0,0 +1,63 @@ +$version: "2.0" + +namespace ns.foo + +use aws.endpoints#standardRegionalEndpoints +use aws.endpoints#standardPartitionalEndpoints +use aws.endpoints#dualStackOnlyEndpoints +use aws.endpoints#rulesBasedEndpoints + +@standardRegionalEndpoints +service Service1 { + version: "2021-06-29" +} + +@standardPartitionalEndpoints(endpointPatternType: "service_dnsSuffix") +service Service2 { + version: "2021-06-29" +} + +@standardRegionalEndpoints( + partitionSpecialCases: { + "aws-us-gov": [ + { + endpoint: "myservice.{region}.{dnsSuffix}", + dualStack: true + }, + { + endpoint: "myservice.global.amazonaws.com", + dualStack: true + } + ] + }, + regionSpecialCases: { + "us-east-1": [ + ] + } +) +@dualStackOnlyEndpoints +service Service3 { + version: "2021-06-29" +} + +@standardPartitionalEndpoints( + endpointPatternType: "service_region_dnsSuffix", + partitionEndpointSpecialCases: { + "aws-us-gov": [ + { + endpoint: "myservice.{region}.{dnsSuffix}", + region: "us-east-1" + fips: true + }, + { + endpoint: "myservice.global.amazonaws.com", + region: "us-west-2" + dualStack: true + } + ] + } +) +@rulesBasedEndpoints +service Service4 { + version: "2021-06-29" +} diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/traits/errorfiles/dualstack-only-endpoints-trait.errors b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/traits/errorfiles/dualstack-only-endpoints-trait.errors new file mode 100644 index 00000000000..aeba2d7eb72 --- /dev/null +++ b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/traits/errorfiles/dualstack-only-endpoints-trait.errors @@ -0,0 +1 @@ +[WARNING] smithy.example#MyService: This shape applies a trait that is unstable: aws.endpoints#dualStackOnlyEndpoints | UnstableTrait.aws.endpoints#dualStackOnlyEndpoints diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/traits/errorfiles/dualstack-only-endpoints-trait.smithy b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/traits/errorfiles/dualstack-only-endpoints-trait.smithy new file mode 100644 index 00000000000..586dcd340d3 --- /dev/null +++ b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/traits/errorfiles/dualstack-only-endpoints-trait.smithy @@ -0,0 +1,10 @@ +$version: "2" + +namespace smithy.example + +use aws.endpoints#dualStackOnlyEndpoints + +@dualStackOnlyEndpoints +service MyService { + version: "2020-04-02" +} diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/traits/errorfiles/rule-based-endpoints-trait.errors b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/traits/errorfiles/rule-based-endpoints-trait.errors new file mode 100644 index 00000000000..780a64af8bf --- /dev/null +++ b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/traits/errorfiles/rule-based-endpoints-trait.errors @@ -0,0 +1 @@ +[WARNING] smithy.example#MyService: This shape applies a trait that is unstable: aws.endpoints#rulesBasedEndpoints | UnstableTrait.aws.endpoints#rulesBasedEndpoints diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/traits/errorfiles/rule-based-endpoints-trait.smithy b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/traits/errorfiles/rule-based-endpoints-trait.smithy new file mode 100644 index 00000000000..cf17ea2846a --- /dev/null +++ b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/traits/errorfiles/rule-based-endpoints-trait.smithy @@ -0,0 +1,10 @@ +$version: "2" + +namespace smithy.example + +use aws.endpoints#rulesBasedEndpoints + +@rulesBasedEndpoints +service MyService { + version: "2020-04-02" +} diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/traits/errorfiles/standard-partitional-endpoints-trait.errors b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/traits/errorfiles/standard-partitional-endpoints-trait.errors new file mode 100644 index 00000000000..c0c29a13595 --- /dev/null +++ b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/traits/errorfiles/standard-partitional-endpoints-trait.errors @@ -0,0 +1 @@ +[WARNING] smithy.example#MyService: This shape applies a trait that is unstable: aws.endpoints#standardPartitionalEndpoints | UnstableTrait.aws.endpoints#standardPartitionalEndpoints diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/traits/errorfiles/standard-partitional-endpoints-trait.smithy b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/traits/errorfiles/standard-partitional-endpoints-trait.smithy new file mode 100644 index 00000000000..8e6bcdaedc5 --- /dev/null +++ b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/traits/errorfiles/standard-partitional-endpoints-trait.smithy @@ -0,0 +1,16 @@ +$version: "2" + +namespace smithy.example + +use aws.endpoints#standardPartitionalEndpoints + +@standardPartitionalEndpoints( + endpointPatternType: "service_dnsSuffix", + partitionEndpointSpecialCases: { + "aws-us": [{endpoint: "myservice.us-west-2.amazonaws.com", region: "us-west-2"}], + "aws-cn": [{endpoint: "myservice.cn-north-1.amazonaws.com", region: "cn-north-1"}] + } +) +service MyService { + version: "2020-04-02" +} diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/traits/errorfiles/standard-regional-endpoints-trait.errors b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/traits/errorfiles/standard-regional-endpoints-trait.errors new file mode 100644 index 00000000000..ee89d604f18 --- /dev/null +++ b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/traits/errorfiles/standard-regional-endpoints-trait.errors @@ -0,0 +1 @@ +[WARNING] smithy.example#MyService: This shape applies a trait that is unstable: aws.endpoints#standardRegionalEndpoints | UnstableTrait.aws.endpoints#standardRegionalEndpoints diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/traits/errorfiles/standard-regional-endpoints-trait.smithy b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/traits/errorfiles/standard-regional-endpoints-trait.smithy new file mode 100644 index 00000000000..7d5bba101f1 --- /dev/null +++ b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/traits/errorfiles/standard-regional-endpoints-trait.smithy @@ -0,0 +1,23 @@ +$version: "2" + +namespace smithy.example + +use aws.endpoints#standardRegionalEndpoints + +@standardRegionalEndpoints( + partitionSpecialCases: { + "aws-us-gov": [ + { + endpoint: "myservice.{region}.{dnsSuffix}", + fips: true + } + ] + }, + regionSpecialCases: { + "us-east-1": [ + ] + } +) +service MyService { + version: "2020-04-02" +} diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/traits/ruleBasedEndpoints.smithy b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/traits/ruleBasedEndpoints.smithy new file mode 100644 index 00000000000..38ee8f62d5d --- /dev/null +++ b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/traits/ruleBasedEndpoints.smithy @@ -0,0 +1,10 @@ +$version: "2.0" + +namespace ns.foo + +use aws.endpoints#rulesBasedEndpoints + +@rulesBasedEndpoints +service Service1 { + version: "2021-06-29" +} diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/traits/standardPartitionalEndpoints.smithy b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/traits/standardPartitionalEndpoints.smithy new file mode 100644 index 00000000000..2597dcec5c9 --- /dev/null +++ b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/traits/standardPartitionalEndpoints.smithy @@ -0,0 +1,31 @@ +$version: "2.0" + +namespace ns.foo + +use aws.endpoints#standardPartitionalEndpoints + +@standardPartitionalEndpoints(endpointPatternType: "service_dnsSuffix") +service Service1 { + version: "2021-06-29" +} + +@standardPartitionalEndpoints( + endpointPatternType: "service_region_dnsSuffix", + partitionEndpointSpecialCases: { + "aws-us-gov": [ + { + endpoint: "myservice.{region}.{dnsSuffix}", + region: "us-east-1" + fips: true + }, + { + endpoint: "myservice.global.amazonaws.com", + region: "us-west-2" + dualStack: true + } + ] + } +) +service Service2 { + version: "2021-06-29" +} diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/traits/standardRegionalEndpoints.smithy b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/traits/standardRegionalEndpoints.smithy new file mode 100644 index 00000000000..2bd3bf3f574 --- /dev/null +++ b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/traits/standardRegionalEndpoints.smithy @@ -0,0 +1,37 @@ +$version: "2.0" + +namespace ns.foo + +use aws.endpoints#standardRegionalEndpoints + +@standardRegionalEndpoints +service Service1 { + version: "2021-06-29" +} + +@standardRegionalEndpoints(partitionSpecialCases: {}, regionSpecialCases: {}) +service Service2 { + version: "2021-06-29" +} + +@standardRegionalEndpoints( + partitionSpecialCases: { + "aws-us-gov": [ + { + endpoint: "myservice.{region}.{dnsSuffix}", + fips: true + }, + { + endpoint: "myservice.global.amazonaws.com", + dualStack: true + } + ] + }, + regionSpecialCases: { + "us-east-1": [ + ] + } +) +service Service3 { + version: "2021-06-29" +}