From 77b09ad2c002520f226c71115de1717dd2d5113b Mon Sep 17 00:00:00 2001 From: Alex Woods Date: Thu, 15 Sep 2022 13:13:22 -0700 Subject: [PATCH] Implement aws.IsVirtualHostableS3Bucket function. (#16) --- .../EndpointRulesetCustomization.java | 125 ++++++++++++++++++ .../stdlib/IsVirtualHostableS3Bucket.java | 54 ++++++++ .../language/syntax/fn/FunctionRegistry.java | 11 +- .../is-virtual-hostable-s3-bucket.json | 121 +++++++++++++++++ .../testutil/test-cases/manifest.txt | 3 +- .../is-virtual-hostable-s3-bucket.json | 48 +++++++ .../testutil/valid-rules/manifest.txt | 1 + 7 files changed, 358 insertions(+), 5 deletions(-) create mode 100644 smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/customize/EndpointRulesetCustomization.java create mode 100644 smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/stdlib/IsVirtualHostableS3Bucket.java create mode 100644 smithy-rules-engine/src/main/resources/software/amazon/smithy/rulesengine/testutil/test-cases/is-virtual-hostable-s3-bucket.json create mode 100644 smithy-rules-engine/src/main/resources/software/amazon/smithy/rulesengine/testutil/valid-rules/is-virtual-hostable-s3-bucket.json diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/customize/EndpointRulesetCustomization.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/customize/EndpointRulesetCustomization.java new file mode 100644 index 00000000000..7245a4fcb07 --- /dev/null +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/customize/EndpointRulesetCustomization.java @@ -0,0 +1,125 @@ +/* + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.smithy.rulesengine.customize; + +import software.amazon.smithy.rulesengine.language.EndpointRuleset; +import software.amazon.smithy.rulesengine.traits.EndpointTestsTrait; + +/** + * Customize generated EndpointRulesets and EndpointTests. + *

+ * To customize the Endpoint rules for a service, implement this interface + * and update the `rule-set-synthesis-configuration.json` to add the + * fully qualified classname of your implementation as `customization` on the service definition. + */ +public interface EndpointRulesetCustomization { + + /** + * Customize the base, generated EndpointRuleset (the ruleset generated from endpoints.json). + *

+ * Most Customizations will need to add additional rules and parameters. The generated rules + * contains a single, top level TreeRule which you can use to insert additional rules into: + *

{@code
+     *     public EndpointRuleset customizeRuleset(EndpointRuleset ruleset) {
+     *         assert ruleset.getRules().size() == 1;
+     *         TreeRule rootRule = (TreeRule) ruleset.getRules().get(0);
+     *
+     *         List subRules = new ArrayList<>();
+     *         // Add our new custom rules first
+     *         subRules.add(myNewCustomRules());
+     *         // ensure the base/generated rule are added
+     *         subRules.addAll(ruleset.getRules());
+     *
+     *         // create a new root rule from the old rule (with our new subRules)
+     *         Rule newRootRule = TreeRule.builder()
+     *                 .conditions(rootRule.getConditions())
+     *                 .treeRule(subRules);
+     *
+     *         // add a new custom parameter
+     *         Parameters newParameters = ruleset.getParameters().toBuilder()
+     *                 .addParameter(myNewParameter()).build();
+     *
+     *         // use the Rulset builder with our modified parameters and rules
+     *         return EndpointRuleset.builder()
+     *                 .parameters(newParameters)
+     *                 .addRule(newRootRule)
+     *                 .build();
+     *     }
+     * }
+ * + * @param ruleset the base generated ruleset + * @return ruleset customized for the service + */ + default EndpointRuleset customizeRuleset(EndpointRuleset ruleset) { + return ruleset; + } + + /** + * Customize the base, generated Test Suite (generated from endpoints.json). + *

+ * Most customizations will simply need to add additional test cases: + *

{@code
+     *     public EndpointTestsTrait customizeTestSuite(EndpointTestsTrait testSuite) {
+     *         List testCases = new ArrayList<>();
+     *         testCases.addAll(testSuite.getTestCases());
+     *         testCases.add(myCustomTestCase());
+     *         return ((EndpointTestsTrait.Builder)testSuite.toBuilder()).testCases(testCases).build();
+     *     }
+     * }
+ * + * @param testSuite the base generated test suite + * @return test suite customized for the service + */ + default EndpointTestsTrait customizeTestSuite(EndpointTestsTrait testSuite) { + return testSuite; + } + + /** + * If a service has private/internal/development only features, return true. + * The {@link #developmentRuleset(EndpointRuleset)} developmentRuleset} and + * {@link #developmentTestSuite(EndpointTestsTrait)} methods will be called + * and the resulting rules/tests will be saved in a separate output folder. + * + * @return true if the service has development (internal only) endpoint rules + */ + default boolean hasDevelopmentFeatures() { + return false; + } + + /** + * Called when hasDevelopmentFeatures is true to produce rules with internal/development + * features enabled. + * See {@link #customizeRuleset(EndpointRuleset)} + * + * @param ruleset the base generated ruleset + * @return ruleset customized for the service including development (internal) features + */ + default EndpointRuleset developmentRuleset(EndpointRuleset ruleset) { + return ruleset; + } + + /** + * Called when hasDevelopmentFeatures is true to produce test cases with internal/development + * features enabled. + * See {@link #customizeTestSuite(EndpointTestsTrait)} + * + * @param testSuite the base generated test suite + * @return test suite customized for the service including development (internal) features + */ + default EndpointTestsTrait developmentTestSuite(EndpointTestsTrait testSuite) { + return testSuite; + } +} diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/stdlib/IsVirtualHostableS3Bucket.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/stdlib/IsVirtualHostableS3Bucket.java new file mode 100644 index 00000000000..fbb9e008ee1 --- /dev/null +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/stdlib/IsVirtualHostableS3Bucket.java @@ -0,0 +1,54 @@ +/* + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.smithy.rulesengine.language.stdlib; + +import java.util.Arrays; +import java.util.List; +import software.amazon.smithy.rulesengine.language.eval.Type; +import software.amazon.smithy.rulesengine.language.eval.Value; +import software.amazon.smithy.rulesengine.language.syntax.fn.FunctionDefinition; +import software.amazon.smithy.utils.SmithyUnstableApi; + +@SmithyUnstableApi +public class IsVirtualHostableS3Bucket extends FunctionDefinition { + public static final String ID = "aws.isVirtualHostableS3Bucket"; + + @Override + public String id() { + return ID; + } + + @Override + public List arguments() { + return Arrays.asList(Type.str(), Type.bool()); + } + + @Override + public Type returnType() { + return Type.bool(); + } + + @Override + public Value eval(List arguments) { + String hostLabel = arguments.get(0).expectString(); + boolean allowDots = arguments.get(1).expectBool(); + if (allowDots) { + return Value.bool(hostLabel.matches("[a-z\\d][a-z\\d\\-.]{1,61}[a-z\\d]")); + } else { + return Value.bool(hostLabel.matches("[a-z\\d][a-z\\d\\-]{1,61}[a-z\\d]")); + } + } +} diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/syntax/fn/FunctionRegistry.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/syntax/fn/FunctionRegistry.java index f16db642251..e66491eba06 100644 --- a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/syntax/fn/FunctionRegistry.java +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/syntax/fn/FunctionRegistry.java @@ -18,6 +18,7 @@ import java.util.HashMap; import java.util.Optional; import software.amazon.smithy.rulesengine.language.stdlib.IsValidHostLabel; +import software.amazon.smithy.rulesengine.language.stdlib.IsVirtualHostableS3Bucket; import software.amazon.smithy.rulesengine.language.stdlib.ParseArn; import software.amazon.smithy.rulesengine.language.stdlib.ParseUrl; import software.amazon.smithy.rulesengine.language.stdlib.PartitionFn; @@ -26,6 +27,7 @@ import software.amazon.smithy.rulesengine.language.util.LazyValue; import software.amazon.smithy.utils.SmithyUnstableApi; + /** * Collection of registered functions. */ @@ -40,6 +42,7 @@ public final class FunctionRegistry { registry.registerFunction(new ParseUrl()); registry.registerFunction(new Substring()); registry.registerFunction(new UriEncode()); + registry.registerFunction(new IsVirtualHostableS3Bucket()); return registry; }).build(); @@ -48,6 +51,10 @@ public final class FunctionRegistry { private FunctionRegistry() { } + static FunctionRegistry getGlobalRegistry() { + return GLOBAL_REGISTRY.value(); + } + public void registerFunction(FunctionDefinition definition) { registry.put(definition.id(), definition); } @@ -59,8 +66,4 @@ public Optional forNode(FnNode node) { return Optional.empty(); } } - - static FunctionRegistry getGlobalRegistry() { - return GLOBAL_REGISTRY.value(); - } } diff --git a/smithy-rules-engine/src/main/resources/software/amazon/smithy/rulesengine/testutil/test-cases/is-virtual-hostable-s3-bucket.json b/smithy-rules-engine/src/main/resources/software/amazon/smithy/rulesengine/testutil/test-cases/is-virtual-hostable-s3-bucket.json new file mode 100644 index 00000000000..9641304ace1 --- /dev/null +++ b/smithy-rules-engine/src/main/resources/software/amazon/smithy/rulesengine/testutil/test-cases/is-virtual-hostable-s3-bucket.json @@ -0,0 +1,121 @@ +{ + "version": "1.0", + "testCases": [ + { + "documentation": "bucket-name: isVirtualHostable", + "params": { + "BucketName": "bucket-name" + }, + "expect": { + "endpoint": { + "url": "https://bucket-name.s3.amazonaws.com" + } + } + }, + { + "documentation": "bucket-with-number-1: isVirtualHostable", + "params": { + "BucketName": "bucket-with-number-1" + }, + "expect": { + "endpoint": { + "url": "https://bucket-with-number-1.s3.amazonaws.com" + } + } + }, + { + "documentation": "BucketName: not isVirtualHostable (uppercase characters)", + "params": { + "BucketName": "BucketName" + }, + "expect": { + "error": "not isVirtualHostableS3Bucket" + } + }, + { + "documentation": "bucket_name: not isVirtualHostable (underscore)", + "params": { + "BucketName": "bucket_name" + }, + "expect": { + "error": "not isVirtualHostableS3Bucket" + } + }, + { + "documentation": "bucket.name: isVirtualHostable (http only)", + "params": { + "BucketName": "bucket.name" + }, + "expect": { + "endpoint": { + "url": "http://bucket.name.s3.amazonaws.com" + } + } + }, + { + "documentation": "bucket.name.multiple.dots1: isVirtualHostable (http only)", + "params": { + "BucketName": "bucket.name.multiple.dots1" + }, + "expect": { + "endpoint": { + "url": "http://bucket.name.multiple.dots1.s3.amazonaws.com" + } + } + }, + { + "documentation": "-bucket-name: not isVirtualHostable (leading dash)", + "params": { + "BucketName": "-bucket-name" + }, + "expect": { + "error": "not isVirtualHostableS3Bucket" + } + }, + { + "documentation": "bucket-name-: not isVirtualHostable (trailing dash)", + "params": { + "BucketName": "bucket-name-" + }, + "expect": { + "error": "not isVirtualHostableS3Bucket" + } + }, + { + "documentation": "aa: not isVirtualHostable (< 3 characters)", + "params": { + "BucketName": "aa" + }, + "expect": { + "error": "not isVirtualHostableS3Bucket" + } + }, + { + "documentation": "'a'*64: not isVirtualHostable (> 63 characters)", + "params": { + "BucketName": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + }, + "expect": { + "error": "not isVirtualHostableS3Bucket" + } + }, + { + "documentation": ".bucket-name: not isVirtualHostable (leading dot)", + "params": { + "BucketName": ".bucket-name" + }, + "expect": { + "error": "not isVirtualHostableS3Bucket" + } + }, + { + "documentation": "bucket-name.: not isVirtualHostable (trailing dot)", + "params": { + "BucketName": "bucket-name." + }, + "expect": { + "error": "not isVirtualHostableS3Bucket" + } + } + ] +} \ No newline at end of file diff --git a/smithy-rules-engine/src/main/resources/software/amazon/smithy/rulesengine/testutil/test-cases/manifest.txt b/smithy-rules-engine/src/main/resources/software/amazon/smithy/rulesengine/testutil/test-cases/manifest.txt index b3d9796fca1..7e305489d3f 100644 --- a/smithy-rules-engine/src/main/resources/software/amazon/smithy/rulesengine/testutil/test-cases/manifest.txt +++ b/smithy-rules-engine/src/main/resources/software/amazon/smithy/rulesengine/testutil/test-cases/manifest.txt @@ -9,4 +9,5 @@ valid-hostlabel.json substring.json uri-encode.json fns.json -partition-fn.json \ No newline at end of file +partition-fn.json +is-virtual-hostable-s3-bucket.json \ No newline at end of file diff --git a/smithy-rules-engine/src/main/resources/software/amazon/smithy/rulesengine/testutil/valid-rules/is-virtual-hostable-s3-bucket.json b/smithy-rules-engine/src/main/resources/software/amazon/smithy/rulesengine/testutil/valid-rules/is-virtual-hostable-s3-bucket.json new file mode 100644 index 00000000000..7ece9b5342a --- /dev/null +++ b/smithy-rules-engine/src/main/resources/software/amazon/smithy/rulesengine/testutil/valid-rules/is-virtual-hostable-s3-bucket.json @@ -0,0 +1,48 @@ +{ + "version": "1.3", + "parameters": { + "BucketName": { + "type": "string", + "required": true, + "documentation": "the input used to test isVirtualHostableS3Bucket" + } + }, + "rules": [ + { + "conditions": [ + { + "fn": "aws.isVirtualHostableS3Bucket", + "argv": [ + "{BucketName}", + false + ] + } + ], + "endpoint": { + "url": "https://{BucketName}.s3.amazonaws.com" + }, + "type": "endpoint" + }, + { + "conditions": [ + { + "fn": "aws.isVirtualHostableS3Bucket", + "argv": [ + "{BucketName}", + true + ] + } + ], + "endpoint": { + "url": "http://{BucketName}.s3.amazonaws.com" + }, + "type": "endpoint" + }, + { + "conditions": [ + ], + "error": "not isVirtualHostableS3Bucket", + "type": "error" + } + ] +} diff --git a/smithy-rules-engine/src/main/resources/software/amazon/smithy/rulesengine/testutil/valid-rules/manifest.txt b/smithy-rules-engine/src/main/resources/software/amazon/smithy/rulesengine/testutil/valid-rules/manifest.txt index 62352022bf1..dd8f5b62690 100644 --- a/smithy-rules-engine/src/main/resources/software/amazon/smithy/rulesengine/testutil/valid-rules/manifest.txt +++ b/smithy-rules-engine/src/main/resources/software/amazon/smithy/rulesengine/testutil/valid-rules/manifest.txt @@ -13,3 +13,4 @@ valid-hostlabel.json substring.json uri-encode.json fns.json +is-virtual-hostable-s3-bucket.json