diff --git a/settings.gradle b/settings.gradle index 89a47440a83..bc0abdddc09 100644 --- a/settings.gradle +++ b/settings.gradle @@ -33,3 +33,4 @@ include ":smithy-rules-engine" include ":smithy-smoke-test-traits" include ":smithy-syntax" include ":smithy-aws-endpoints" +include ":smithy-aws-smoke-test-model" diff --git a/smithy-aws-smoke-test-model/build.gradle b/smithy-aws-smoke-test-model/build.gradle new file mode 100644 index 00000000000..ddc313f6786 --- /dev/null +++ b/smithy-aws-smoke-test-model/build.gradle @@ -0,0 +1,15 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +description = "Defines shapes used by AWS for modeling smoke tests" + +ext { + displayName = "Smithy :: AWS :: Smoke Test :: Model" + moduleName = "software.amazon.smithy.aws.smoketest.model" +} + +dependencies { + api project(":smithy-smoke-test-traits") +} diff --git a/smithy-aws-smoke-test-model/src/main/java/software/amazon/smithy/aws/smoketests/model/AwsSmokeTestModel.java b/smithy-aws-smoke-test-model/src/main/java/software/amazon/smithy/aws/smoketests/model/AwsSmokeTestModel.java new file mode 100644 index 00000000000..2e9803b9b98 --- /dev/null +++ b/smithy-aws-smoke-test-model/src/main/java/software/amazon/smithy/aws/smoketests/model/AwsSmokeTestModel.java @@ -0,0 +1,78 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.aws.smoketests.model; + +import java.util.Optional; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.ObjectNode; +import software.amazon.smithy.smoketests.traits.SmokeTestCase; + +/** + * Provides methods for interfacing with Java representations of the different + * kinds of vendor params shapes used in smoke tests for AWS services. + */ +public final class AwsSmokeTestModel { + private AwsSmokeTestModel() { + } + + /** + * @param testCase The test case to check. + * @return {@code true} if the {@code testCase}'s {@link SmokeTestCase#getVendorParams()} + * are {@link AwsVendorParams}. + */ + public static boolean hasAwsVendorParams(SmokeTestCase testCase) { + return testCase.getVendorParamsShape() + .filter(AwsVendorParams.ID::equals) + .isPresent(); + } + + /** + * @param testCase The test case to check. + * @return {@code true} if the {@code testCase}'s {@link SmokeTestCase#getVendorParams()} + * are {@link S3VendorParams}. + */ + public static boolean hasS3VendorParams(SmokeTestCase testCase) { + return testCase.getVendorParamsShape() + .filter(S3VendorParams.ID::equals) + .isPresent(); + } + + /** + * Gets the vendor params for the given {@code forTestCase} as {@link AwsVendorParams}. + * + *

The vendor params will be present if {@link #hasAwsVendorParams(SmokeTestCase)} + * was {@code true} for the given {@code forTestCase}. + * + * @param forTestCase The test case to get vendor params for. + * @return The optionally present vendor params as {@link S3VendorParams}. + */ + public static Optional getAwsVendorParams(SmokeTestCase forTestCase) { + if (!hasAwsVendorParams(forTestCase)) { + return Optional.empty(); + } + + ObjectNode vendorParams = forTestCase.getVendorParams().orElse(Node.objectNode()); + return Optional.of(new AwsVendorParams(vendorParams)); + } + + /** + * Gets the vendor params for the given {@code forTestCase} as {@link S3VendorParams}. + * + *

The vendor params will be present if {@link #hasS3VendorParams(SmokeTestCase)} + * was {@code true} for the given {@code forTestCase}. + * + * @param forTestCase The test case to get vendor params for. + * @return The optionally present vendor params as {@link S3VendorParams}. + */ + public static Optional getS3VendorParams(SmokeTestCase forTestCase) { + if (!hasS3VendorParams(forTestCase)) { + return Optional.empty(); + } + + ObjectNode vendorParams = forTestCase.getVendorParams().orElse(Node.objectNode()); + return Optional.of(new S3VendorParams(vendorParams)); + } +} diff --git a/smithy-aws-smoke-test-model/src/main/java/software/amazon/smithy/aws/smoketests/model/AwsVendorParams.java b/smithy-aws-smoke-test-model/src/main/java/software/amazon/smithy/aws/smoketests/model/AwsVendorParams.java new file mode 100644 index 00000000000..d0c4747e64d --- /dev/null +++ b/smithy-aws-smoke-test-model/src/main/java/software/amazon/smithy/aws/smoketests/model/AwsVendorParams.java @@ -0,0 +1,20 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.aws.smoketests.model; + +import software.amazon.smithy.model.node.ObjectNode; +import software.amazon.smithy.model.shapes.ShapeId; + +/** + * Concrete vendor params to apply to AWS services by default. + */ +public final class AwsVendorParams extends BaseAwsVendorParams { + public static final ShapeId ID = ShapeId.from("aws.test#AwsVendorParams"); + + AwsVendorParams(ObjectNode node) { + super(node); + } +} diff --git a/smithy-aws-smoke-test-model/src/main/java/software/amazon/smithy/aws/smoketests/model/BaseAwsVendorParams.java b/smithy-aws-smoke-test-model/src/main/java/software/amazon/smithy/aws/smoketests/model/BaseAwsVendorParams.java new file mode 100644 index 00000000000..fdddbebfb9c --- /dev/null +++ b/smithy-aws-smoke-test-model/src/main/java/software/amazon/smithy/aws/smoketests/model/BaseAwsVendorParams.java @@ -0,0 +1,85 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.aws.smoketests.model; + +import java.util.List; +import java.util.Optional; +import software.amazon.smithy.model.node.ObjectNode; + +/** + * Base vendor params for all AWS services. + */ +public abstract class BaseAwsVendorParams { + private static final String DEFAULT_REGION = "us-west-2"; + private static final boolean DEFAULT_USE_FIPS = false; + private static final boolean DEFAULT_USE_DUALSTACK = false; + private static final boolean DEFAULT_USE_ACCOUNT_ID_ROUTING = true; + + private final String region; + private final List sigv4aRegionSet; + private final String uri; + private final boolean useFips; + private final boolean useDualstack; + private final boolean useAccountIdRouting; + + BaseAwsVendorParams(ObjectNode node) { + this.region = node.getStringMemberOrDefault("region", DEFAULT_REGION); + this.sigv4aRegionSet = node.getArrayMember("sigv4aRegionSet") + .map(a -> a.getElementsAs(el -> el.expectStringNode().getValue())) + .orElse(null); + this.uri = node.getStringMemberOrDefault("uri", null); + this.useFips = node.getBooleanMemberOrDefault("useFips", DEFAULT_USE_FIPS); + this.useDualstack = node.getBooleanMemberOrDefault("useDualstack", DEFAULT_USE_DUALSTACK); + this.useAccountIdRouting = node.getBooleanMemberOrDefault( + "useAccountIdRouting", DEFAULT_USE_ACCOUNT_ID_ROUTING); + } + + /** + * @return The AWS region to sign the request for and to resolve the default + * endpoint with. Defaults to {@code us-west-2}. + */ + public String getRegion() { + return region; + } + + /** + * @return The set of regions to sign a sigv4a request with, if present. + */ + public Optional> getSigv4aRegionSet() { + return Optional.ofNullable(sigv4aRegionSet); + } + + /** + * @return The static endpoint to send the request to, if present. + */ + public Optional getUri() { + return Optional.ofNullable(uri); + } + + /** + * @return Whether to resolve a FIPS compliant endpoint or not. Defaults to + * {@code false}. + */ + public boolean useFips() { + return useFips; + } + + /** + * @return Whether to resolve a dualstack endpoint or not. Defaults to + * {@code false}. + */ + public boolean useDualstack() { + return useDualstack; + } + + /** + * @return Whether to use account ID based routing where applicable. + * Defaults to {@code true}. + */ + public boolean useAccountIdRouting() { + return useAccountIdRouting; + } +} diff --git a/smithy-aws-smoke-test-model/src/main/java/software/amazon/smithy/aws/smoketests/model/S3VendorParams.java b/smithy-aws-smoke-test-model/src/main/java/software/amazon/smithy/aws/smoketests/model/S3VendorParams.java new file mode 100644 index 00000000000..845ac4f3367 --- /dev/null +++ b/smithy-aws-smoke-test-model/src/main/java/software/amazon/smithy/aws/smoketests/model/S3VendorParams.java @@ -0,0 +1,77 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.aws.smoketests.model; + +import software.amazon.smithy.model.node.ObjectNode; +import software.amazon.smithy.model.shapes.ShapeId; + +/** + * Vendor params for S3. + */ +public final class S3VendorParams extends BaseAwsVendorParams { + public static final ShapeId ID = ShapeId.from("aws.test#S3VendorParams"); + + private static final boolean DEFAULT_USE_ACCELERATE = false; + private static final boolean DEFAULT_USE_GLOBAL_ENDPOINT = false; + private static final boolean DEFAULT_FORCE_PATH_STYLE = false; + private static final boolean DEFAULT_USE_ARN_REGION = true; + private static final boolean DEFAULT_USE_MULTI_REGION_ACCESS_POINTS = true; + + private final boolean useAccelerate; + private final boolean useGlobalEndpoint; + private final boolean forcePathStyle; + private final boolean useArnRegion; + private final boolean useMultiRegionAccessPoints; + + S3VendorParams(ObjectNode node) { + super(node); + this.useAccelerate = node.getBooleanMemberOrDefault("useAccelerate", DEFAULT_USE_ACCELERATE); + this.useGlobalEndpoint = node.getBooleanMemberOrDefault("useGlobalEndpoint", DEFAULT_USE_GLOBAL_ENDPOINT); + this.forcePathStyle = node.getBooleanMemberOrDefault("forcePathStyle", DEFAULT_FORCE_PATH_STYLE); + this.useArnRegion = node.getBooleanMemberOrDefault("useArnRegion", DEFAULT_USE_ARN_REGION); + this.useMultiRegionAccessPoints = node.getBooleanMemberOrDefault( + "useMultiRegionAccessPoints", DEFAULT_USE_MULTI_REGION_ACCESS_POINTS); + } + + /** + * @return Whether to resolve an accelerate endpoint or not. Defaults to + * {@code false}. + */ + public boolean useAccelerate() { + return useAccelerate; + } + + /** + * @return Whether to use the global endpoint for {@code us-east-1}. + * Defaults to {@code false}. + */ + public boolean useGlobalEndpoint() { + return useGlobalEndpoint; + } + + /** + * @return Whether to force path-style addressing. Defaults to {@code false}. + */ + public boolean forcePathStyle() { + return forcePathStyle; + } + + /** + * @return Whether to use the region in the bucket ARN to override the set + * region. Defaults to {@code true}. + */ + public boolean useArnRegion() { + return useArnRegion; + } + + /** + * @return Whether to use S3's multi-region access points. Defaults to + * {@code true}. + */ + public boolean useMultiRegionAccessPoints() { + return useMultiRegionAccessPoints; + } +} diff --git a/smithy-aws-smoke-test-model/src/main/resources/META-INF/smithy/aws.test.smithy b/smithy-aws-smoke-test-model/src/main/resources/META-INF/smithy/aws.test.smithy new file mode 100644 index 00000000000..2a1050de593 --- /dev/null +++ b/smithy-aws-smoke-test-model/src/main/resources/META-INF/smithy/aws.test.smithy @@ -0,0 +1,58 @@ +$version: "2.0" + +namespace aws.test + +/// Base vendor params for all aws services. +@mixin +@suppress(["UnreferencedShape"]) +structure BaseAwsVendorParams { + /// The AWS region to sign the request for and to resolve the default + /// endpoint with. + region: String = "us-west-2" + + /// The set of regions to sign a sigv4a request with. + sigv4aRegionSet: NonEmptyStringList + + /// A static endpoint to send the request to. + uri: String + + /// Whether to resolve a FIPS compliant endpoint or not. + useFips: Boolean = false + + /// Whether to resolve a dualstack endpoint or not. + useDualstack: Boolean = false + + /// Whether to use account ID based routing where applicable. + useAccountIdRouting: Boolean = true +} + +@private +@length(min: 1) +@suppress(["UnreferencedShape"]) +list NonEmptyStringList { + member: String +} + +/// Concrete vendor params to apply to AWS services by default. +@suppress(["UnreferencedShape"]) +structure AwsVendorParams with [BaseAwsVendorParams] {} + +/// Vendor params for S3. +@suppress(["UnreferencedShape"]) +structure S3VendorParams with [BaseAwsVendorParams] { + /// Whether to resolve an accelerate endpoint or not. + useAccelerate: Boolean = false + + /// Whether to use the global endpoint for us-east-1. + useGlobalEndpoint: Boolean = false + + /// Whether to force path-style addressing. + forcePathStyle: Boolean = false + + /// Whether to use the region in the bucket arn to override the set + /// region. + useArnRegion: Boolean = true + + /// Whether to use S3's multi-region access points. + useMultiRegionAccessPoints: Boolean = true +} diff --git a/smithy-aws-smoke-test-model/src/main/resources/META-INF/smithy/manifest b/smithy-aws-smoke-test-model/src/main/resources/META-INF/smithy/manifest new file mode 100644 index 00000000000..7d0b1ba0bf3 --- /dev/null +++ b/smithy-aws-smoke-test-model/src/main/resources/META-INF/smithy/manifest @@ -0,0 +1 @@ +aws.test.smithy diff --git a/smithy-aws-smoke-test-model/src/test/java/software/amazon/smithy/aws/smoketests/model/AwsSmokeTestModelTest.java b/smithy-aws-smoke-test-model/src/test/java/software/amazon/smithy/aws/smoketests/model/AwsSmokeTestModelTest.java new file mode 100644 index 00000000000..fba8d8ff62e --- /dev/null +++ b/smithy-aws-smoke-test-model/src/test/java/software/amazon/smithy/aws/smoketests/model/AwsSmokeTestModelTest.java @@ -0,0 +1,169 @@ +package software.amazon.smithy.aws.smoketests.model; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; + +import java.util.Optional; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.smoketests.traits.Expectation; +import software.amazon.smithy.smoketests.traits.SmokeTestCase; +import software.amazon.smithy.smoketests.traits.SmokeTestsTrait; + +public class AwsSmokeTestModelTest { + @Test + public void determinesIfTestCaseHasAwsVendorParams() { + SmokeTestCase withAwsVendorParamsShape = SmokeTestCase.builder() + .id("foo") + .expectation(Expectation.success()) + .vendorParamsShape(AwsVendorParams.ID) + .build(); + SmokeTestCase withNoVendorParamsShape = withAwsVendorParamsShape.toBuilder() + .vendorParamsShape(null) + .build(); + SmokeTestCase withOtherVendorParamsShape = withNoVendorParamsShape.toBuilder() + .vendorParamsShape(S3VendorParams.ID) + .build(); + + assertThat(AwsSmokeTestModel.hasAwsVendorParams(withAwsVendorParamsShape), is(true)); + assertThat(AwsSmokeTestModel.hasAwsVendorParams(withNoVendorParamsShape), is(false)); + assertThat(AwsSmokeTestModel.hasAwsVendorParams(withOtherVendorParamsShape), is(false)); + } + + @Test + public void determinesIfTestCaseHasS3VendorParams() { + SmokeTestCase withS3VendorParamsShape = SmokeTestCase.builder() + .id("foo") + .expectation(Expectation.success()) + .vendorParamsShape(S3VendorParams.ID) + .build(); + SmokeTestCase withNoVendorParamsShape = withS3VendorParamsShape.toBuilder() + .vendorParamsShape(null) + .build(); + SmokeTestCase withOtherVendorParamsShape = withNoVendorParamsShape.toBuilder() + .vendorParamsShape(AwsVendorParams.ID) + .build(); + + assertThat(AwsSmokeTestModel.hasS3VendorParams(withS3VendorParamsShape), is(true)); + assertThat(AwsSmokeTestModel.hasS3VendorParams(withNoVendorParamsShape), is(false)); + assertThat(AwsSmokeTestModel.hasS3VendorParams(withOtherVendorParamsShape), is(false)); + } + + @Test + public void canGetAwsVendorParamsFromTestCase() { + SmokeTestCase withoutVendorParams = SmokeTestCase.builder() + .id("foo") + .expectation(Expectation.success()) + .vendorParamsShape(AwsVendorParams.ID) + .build(); + SmokeTestCase withNoVendorParamsShape = withoutVendorParams.toBuilder() + .vendorParamsShape(null) + .build(); + SmokeTestCase withOtherVendorParamsShape = withNoVendorParamsShape.toBuilder() + .vendorParamsShape(S3VendorParams.ID) + .build(); + + assertThat(AwsSmokeTestModel.getAwsVendorParams(withoutVendorParams), not(equalTo(Optional.empty()))); + assertThat(AwsSmokeTestModel.getAwsVendorParams(withNoVendorParamsShape), equalTo(Optional.empty())); + assertThat(AwsSmokeTestModel.getAwsVendorParams(withOtherVendorParamsShape), equalTo(Optional.empty())); + } + + @Test + public void fillsInDefaultsForAwsVendorParams() { + AwsVendorParams vendorParams = AwsSmokeTestModel.getAwsVendorParams(SmokeTestCase.builder() + .id("foo") + .expectation(Expectation.success()) + .vendorParamsShape(AwsVendorParams.ID) + .build()).get(); + + assertThat(vendorParams.getRegion(), equalTo("us-west-2")); + assertThat(vendorParams.getSigv4aRegionSet(), equalTo(Optional.empty())); + assertThat(vendorParams.getUri(), equalTo(Optional.empty())); + assertThat(vendorParams.useFips(), is(false)); + assertThat(vendorParams.useDualstack(), is(false)); + assertThat(vendorParams.useAccountIdRouting(), is(true)); + } + + @Test + public void fillsInDefaultsForS3VendorParams() { + S3VendorParams vendorParams = AwsSmokeTestModel.getS3VendorParams(SmokeTestCase.builder() + .id("foo") + .expectation(Expectation.success()) + .vendorParamsShape(S3VendorParams.ID) + .build()).get(); + + assertThat(vendorParams.getRegion(), equalTo("us-west-2")); + assertThat(vendorParams.getSigv4aRegionSet(), equalTo(Optional.empty())); + assertThat(vendorParams.getUri(), equalTo(Optional.empty())); + assertThat(vendorParams.useFips(), is(false)); + assertThat(vendorParams.useDualstack(), is(false)); + assertThat(vendorParams.useAccountIdRouting(), is(true)); + + assertThat(vendorParams.useAccelerate(), is(false)); + assertThat(vendorParams.useGlobalEndpoint(), is(false)); + assertThat(vendorParams.forcePathStyle(), is(false)); + assertThat(vendorParams.useArnRegion(), is(true)); + assertThat(vendorParams.useMultiRegionAccessPoints(), is(true)); + } + + @Test + public void loadsAwsVendorParamsFromModel() { + Model model = Model.assembler() + .discoverModels() + .addImport(getClass().getResource("vendor-params.smithy")) + .assemble() + .unwrap(); + SmokeTestsTrait trait = model.expectShape(ShapeId.from("com.foo#GetFoo")).expectTrait(SmokeTestsTrait.class); + SmokeTestCase awsCase = trait.getTestCases().stream() + .filter(testCase -> testCase.getId().equals("AwsVendorParamsCase")) + .findAny() + .get(); + + assertThat(AwsSmokeTestModel.hasAwsVendorParams(awsCase), is(true)); + assertThat(AwsSmokeTestModel.getAwsVendorParams(awsCase), not(equalTo(Optional.empty()))); + AwsVendorParams vendorParams = AwsSmokeTestModel.getAwsVendorParams(awsCase).get(); + assertThat(vendorParams.getRegion(), equalTo("us-east-1")); + assertThat(vendorParams.getSigv4aRegionSet(), not(equalTo(Optional.empty()))); + assertThat(vendorParams.getSigv4aRegionSet().get(), containsInAnyOrder("us-east-1")); + assertThat(vendorParams.getUri(), equalTo(Optional.of("foo"))); + assertThat(vendorParams.useFips(), is(true)); + assertThat(vendorParams.useDualstack(), is(true)); + assertThat(vendorParams.useAccountIdRouting(), is(false)); + } + + @Test + public void loadsS3VendorParamsFromModel() { + Model model = Model.assembler() + .discoverModels() + .addImport(getClass().getResource("vendor-params.smithy")) + .assemble() + .unwrap(); + SmokeTestCase s3Case = model.expectShape(ShapeId.from("com.foo#GetFoo")) + .expectTrait(SmokeTestsTrait.class) + .getTestCases() + .stream() + .filter(testCase -> testCase.getId().equals("S3VendorParamsCase")) + .findAny() + .get(); + + assertThat(AwsSmokeTestModel.hasS3VendorParams(s3Case), is(true)); + assertThat(AwsSmokeTestModel.getS3VendorParams(s3Case), not(equalTo(Optional.empty()))); + S3VendorParams vendorParams = AwsSmokeTestModel.getS3VendorParams(s3Case).get(); + assertThat(vendorParams.getRegion(), equalTo("us-east-2")); + assertThat(vendorParams.getSigv4aRegionSet(), not(equalTo(Optional.empty()))); + assertThat(vendorParams.getSigv4aRegionSet().get(), containsInAnyOrder("us-east-2")); + assertThat(vendorParams.getUri(), equalTo(Optional.of("bar"))); + assertThat(vendorParams.useFips(), is(true)); + assertThat(vendorParams.useDualstack(), is(true)); + assertThat(vendorParams.useAccountIdRouting(), is(false)); + assertThat(vendorParams.useAccelerate(), is(true)); + assertThat(vendorParams.useGlobalEndpoint(), is(true)); + assertThat(vendorParams.forcePathStyle(), is(true)); + assertThat(vendorParams.useArnRegion(), is(false)); + assertThat(vendorParams.useMultiRegionAccessPoints(), is(false)); + } +} diff --git a/smithy-aws-smoke-test-model/src/test/resources/software/amazon/smithy/aws/smoketests/model/vendor-params.smithy b/smithy-aws-smoke-test-model/src/test/resources/software/amazon/smithy/aws/smoketests/model/vendor-params.smithy new file mode 100644 index 00000000000..2d3d7b78631 --- /dev/null +++ b/smithy-aws-smoke-test-model/src/test/resources/software/amazon/smithy/aws/smoketests/model/vendor-params.smithy @@ -0,0 +1,48 @@ +$version: "2.0" + +namespace com.foo + +use smithy.test#smokeTests +use aws.test#AwsVendorParams +use aws.test#S3VendorParams + +@smokeTests([ + { + id: "AwsVendorParamsCase" + params: {} + vendorParams: { + region: "us-east-1" + sigv4aRegionSet: ["us-east-1"] + uri: "foo" + useFips: true + useDualstack: true + useAccountIdRouting: false + } + vendorParamsShape: AwsVendorParams + expect: { + success: {} + } + } + { + id: "S3VendorParamsCase" + params: {} + vendorParams: { + region: "us-east-2" + sigv4aRegionSet: ["us-east-2"] + uri: "bar" + useFips: true + useDualstack: true + useAccountIdRouting: false + useAccelerate: true + useGlobalEndpoint: true + forcePathStyle: true + useArnRegion: false + useMultiRegionAccessPoints: false + } + vendorParamsShape: S3VendorParams + expect: { + success: {} + } + } +]) +operation GetFoo {}