From bc01b57c401e8c129bcbdd96827951d44ce05bde Mon Sep 17 00:00:00 2001 From: kstich Date: Tue, 28 Feb 2023 10:06:18 -0800 Subject: [PATCH] Significant refactoring of smithy-rules-engine This commit collects many changes to limit the public interface, align with Java standards, align with idioms from the rest of the Smithy codebase, and generally clean up the codebase. Several packages, classes, and methods have been removed, renamed, moved, or changed visibility. Use of `assert` has been removed and the usage of `stream` has been reduced. Classes with multiple subtypes have had their subtype names adjusted for compatibility with Java and been broken out to their own packages and classes. --- .../rulesengine/analysis/CoverageChecker.java | 106 +-- .../smithy/rulesengine/language/Endpoint.java | 222 +++--- .../rulesengine/language/EndpointRuleSet.java | 53 +- ...nUtils.java => RulesComponentBuilder.java} | 64 +- .../language/error/InnerParseError.java | 2 +- .../language/error/InvalidRulesException.java | 19 +- .../rulesengine/language/error/RuleError.java | 2 +- .../language/eval/RuleEvaluator.java | 111 ++- .../rulesengine/language/eval/Scope.java | 22 +- .../rulesengine/language/eval/ScopeLayer.java | 7 +- .../language/eval/TestEvaluator.java | 19 +- .../rulesengine/language/eval/Type.java | 436 ----------- .../rulesengine/language/eval/TypeCheck.java | 1 + .../rulesengine/language/eval/Value.java | 619 ---------------- .../language/eval/type/AbstractType.java | 33 + .../eval/type/AnyType.java} | 20 +- .../language/eval/type/ArrayType.java | 58 ++ .../eval/type/BooleanType.java} | 15 +- .../language/eval/type/EmptyType.java | 25 + .../language/eval/type/EndpointType.java | 25 + .../language/eval/type/IntegerType.java | 25 + .../language/eval/type/OptionalType.java | 84 +++ .../language/eval/type/RecordType.java | 69 ++ .../language/eval/type/StringType.java | 25 + .../language/eval/type/TupleType.java | 54 ++ .../rulesengine/language/eval/type/Type.java | 119 +++ .../language/eval/value/ArrayValue.java | 105 +++ .../language/eval/value/BooleanValue.java | 74 ++ .../language/eval/value/EmptyValue.java | 41 ++ .../language/eval/value/EndpointValue.java | 192 +++++ .../language/eval/value/IntegerValue.java | 48 ++ .../language/eval/value/RecordValue.java | 95 +++ .../language/eval/value/StringValue.java | 72 ++ .../language/eval/value/Value.java | 156 ++++ .../rulesengine/language/impl/AwsArn.java | 16 +- .../rulesengine/language/model/Partition.java | 10 +- .../language/model/PartitionOutputs.java | 8 +- .../language/model/Partitions.java | 11 +- .../language/model/RegionOverride.java | 4 +- .../stdlib/AwsIsVirtualHostableS3Bucket.java | 26 +- .../language/stdlib/AwsPartition.java | 91 +-- .../language/stdlib/BooleanEquals.java | 23 +- .../language/stdlib/IsValidHostLabel.java | 26 +- .../rulesengine/language/stdlib/ParseArn.java | 59 +- .../rulesengine/language/stdlib/ParseUrl.java | 66 +- .../language/stdlib/StringEquals.java | 23 +- .../language/stdlib/Substring.java | 34 +- .../language/stdlib/UriEncode.java | 36 +- .../language/syntax/Identifier.java | 20 +- .../language/syntax/expr/Literal.java | 678 ------------------ .../{expr => expressions}/Expression.java | 57 +- .../{expr => expressions}/Reference.java | 8 +- .../{expr => expressions}/Template.java | 63 +- .../expressions/literal/BooleanLiteral.java | 72 ++ .../expressions/literal/IntegerLiteral.java | 71 ++ .../syntax/expressions/literal/Literal.java | 292 ++++++++ .../expressions/literal/LiteralVisitor.java | 33 + .../expressions/literal/RecordLiteral.java | 78 ++ .../expressions/literal/StringLiteral.java | 81 +++ .../expressions/literal/TupleLiteral.java | 84 +++ .../syntax/{fn => functions}/Function.java | 27 +- .../{fn => functions}/FunctionDefinition.java | 19 +- .../{fn => functions}/FunctionNode.java | 33 +- .../{fn => functions}/FunctionRegistry.java | 52 +- .../syntax/{fn => functions}/GetAttr.java | 52 +- .../syntax/{fn => functions}/IsSet.java | 17 +- .../{fn => functions}/LibraryFunction.java | 17 +- .../syntax/{fn => functions}/Not.java | 24 +- .../{fn => functions}/SingleArgFunction.java | 6 +- .../language/syntax/parameters/Builtins.java | 21 +- .../language/syntax/parameters/Parameter.java | 58 +- .../syntax/parameters/ParameterType.java | 6 +- .../syntax/parameters/Parameters.java | 41 +- .../language/syntax/rule/Condition.java | 14 +- .../language/syntax/rule/EndpointRule.java | 7 +- .../language/syntax/rule/ErrorRule.java | 8 +- .../language/syntax/rule/Rule.java | 87 +-- .../language/syntax/rule/TreeRule.java | 18 +- .../rulesengine/language/util/LazyValue.java | 61 -- .../util/MandatorySourceLocation.java | 38 - .../rulesengine/language/util/PathFinder.java | 188 ----- .../util/SourceLocationTrackingBuilder.java | 56 -- .../language/util/StringUtils.java | 60 -- .../language/visit/DefaultVisitor.java | 48 +- .../language/visit/ExpressionVisitor.java | 11 +- .../language/visit/RuleValueVisitor.java | 2 +- .../language/visit/TemplateVisitor.java | 2 +- .../language/visit/TraversingVisitor.java | 2 +- .../rulesengine/testutil/TestDiscovery.java | 231 ------ .../rulesengine/traits/ContextIndex.java | 28 +- .../traits/EndpointTestsTraitValidator.java | 1 + .../rulesengine/traits/ExpectedEndpoint.java | 2 +- .../validators/AuthSchemesValidator.java | 4 +- .../validators/BuiltInsValidator.java | 1 - .../validators/ParametersValidator.java | 57 +- .../validators/ParamsHaveDocs.java | 3 +- .../StandaloneRulesetValidator.java | 23 +- .../validators/ValidateUriScheme.java | 9 +- .../testutil/test-cases/manifest.txt | 13 - .../testutil/valid-rules/manifest.txt | 17 - .../smithy/rulesengine/RulesetTestUtil.java | 25 - .../analysis/CoverageCheckerTest.java | 29 +- .../language/EndpointRuleSetTest.java | 54 +- .../rulesengine/language/IntegrationTest.java | 146 ++-- .../language/ParametersValidationTest.java | 45 +- .../rulesengine/language/TestDiscovery.java | 200 ++++++ .../language/TypeIntrospectionTest.java | 24 +- .../language/eval/RuleEngineTest.java | 29 +- .../language/fn/AwsPartitionFunctionTest.java | 28 +- .../language/fn/FunctionOfExprsTest.java | 4 +- .../rulesengine/language/impl/AwsArnTest.java | 6 +- .../language/syntax/ParameterTest.java | 4 +- .../syntax/{fn => functions}/GetAttrTest.java | 4 +- .../language/syntax/rule/RuleTest.java | 38 +- .../language/value/TemplateTest.java | 23 +- .../invalid-rules/bool-equals-unset.json5 | 4 +- .../invalid-rules/cant-template-bool.json5 | 6 +- .../invalid-rules/field-in-wrong-tree.json5 | 4 +- .../invalid-rules/invalid-param-type.json5 | 2 +- .../invalid-rules/invalid-slice.json5 | 6 +- .../only-refs-coerce-isset.json5 | 6 +- .../invalid-rules/string-equals-unset.json5 | 4 +- .../invalid-rules/unknown-field.json5 | 6 +- .../invalid-rules/unset-in-wrong-tree.json5 | 6 +- .../invalid-rules/wrong-default-type.json5 | 4 +- .../language}/test-cases/aws-region.json | 0 .../language}/test-cases/default-values.json | 0 .../language}/test-cases/eventbridge.json | 0 .../rulesengine/language}/test-cases/fns.json | 0 .../language}/test-cases/headers.json | 0 .../is-virtual-hostable-s3-bucket.json | 0 .../test-cases/local-region-override.json | 0 .../language}/test-cases/parse-arn.json | 0 .../language}/test-cases/parse-url.json | 0 .../language}/test-cases/partition-fn.json | 0 .../language}/test-cases/substring.json | 0 .../language}/test-cases/uri-encode.json | 0 .../language}/test-cases/valid-hostlabel.json | 0 .../language}/valid-rules/aws-region.json | 0 .../valid-rules/beta-auth-scheme.json | 0 .../language}/valid-rules/default-values.json | 0 .../valid-rules/deprecated-param.json | 0 .../language}/valid-rules/eventbridge.json | 0 .../language}/valid-rules/fns.json | 0 .../valid-rules/get-attr-type-inference.json | 0 .../language}/valid-rules/headers.json | 0 .../is-virtual-hostable-s3-bucket.json | 0 .../valid-rules/local-region-override.json | 0 .../valid-rules/minimal-ruleset.json | 0 .../language}/valid-rules/parse-arn.json | 0 .../language}/valid-rules/parse-url.json | 0 .../language}/valid-rules/partition-fn.json | 0 .../language}/valid-rules/substring.json | 0 .../language}/valid-rules/uri-encode.json | 0 .../valid-rules/valid-hostlabel.json | 0 .../amazon/smithy/utils/StringUtils.java | 17 + .../amazon/smithy/utils/StringUtilsTest.java | 11 + 157 files changed, 3384 insertions(+), 3703 deletions(-) rename smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/{util/SourceLocationUtils.java => RulesComponentBuilder.java} (57%) delete mode 100644 smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/Type.java delete mode 100644 smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/Value.java create mode 100644 smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/AbstractType.java rename smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/{IntoSelf.java => language/eval/type/AnyType.java} (64%) create mode 100644 smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/ArrayType.java rename smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/{Into.java => language/eval/type/BooleanType.java} (68%) create mode 100644 smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/EmptyType.java create mode 100644 smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/EndpointType.java create mode 100644 smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/IntegerType.java create mode 100644 smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/OptionalType.java create mode 100644 smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/RecordType.java create mode 100644 smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/StringType.java create mode 100644 smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/TupleType.java create mode 100644 smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/Type.java create mode 100644 smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/value/ArrayValue.java create mode 100644 smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/value/BooleanValue.java create mode 100644 smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/value/EmptyValue.java create mode 100644 smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/value/EndpointValue.java create mode 100644 smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/value/IntegerValue.java create mode 100644 smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/value/RecordValue.java create mode 100644 smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/value/StringValue.java create mode 100644 smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/value/Value.java delete mode 100644 smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/syntax/expr/Literal.java rename smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/syntax/{expr => expressions}/Expression.java (83%) rename smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/syntax/{expr => expressions}/Reference.java (91%) rename smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/syntax/{expr => expressions}/Template.java (84%) create mode 100644 smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/syntax/expressions/literal/BooleanLiteral.java create mode 100644 smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/syntax/expressions/literal/IntegerLiteral.java create mode 100644 smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/syntax/expressions/literal/Literal.java create mode 100644 smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/syntax/expressions/literal/LiteralVisitor.java create mode 100644 smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/syntax/expressions/literal/RecordLiteral.java create mode 100644 smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/syntax/expressions/literal/StringLiteral.java create mode 100644 smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/syntax/expressions/literal/TupleLiteral.java rename smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/syntax/{fn => functions}/Function.java (82%) rename smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/syntax/{fn => functions}/FunctionDefinition.java (71%) rename smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/syntax/{fn => functions}/FunctionNode.java (86%) rename smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/syntax/{fn => functions}/FunctionRegistry.java (51%) rename smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/syntax/{fn => functions}/GetAttr.java (86%) rename smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/syntax/{fn => functions}/IsSet.java (75%) rename smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/syntax/{fn => functions}/LibraryFunction.java (86%) rename smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/syntax/{fn => functions}/Not.java (75%) rename smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/syntax/{fn => functions}/SingleArgFunction.java (89%) delete mode 100644 smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/util/LazyValue.java delete mode 100644 smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/util/MandatorySourceLocation.java delete mode 100644 smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/util/PathFinder.java delete mode 100644 smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/util/SourceLocationTrackingBuilder.java delete mode 100644 smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/util/StringUtils.java delete mode 100644 smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/testutil/TestDiscovery.java delete mode 100644 smithy-rules-engine/src/main/resources/software/amazon/smithy/rulesengine/testutil/test-cases/manifest.txt delete mode 100644 smithy-rules-engine/src/main/resources/software/amazon/smithy/rulesengine/testutil/valid-rules/manifest.txt delete mode 100644 smithy-rules-engine/src/test/java/software/amazon/smithy/rulesengine/RulesetTestUtil.java create mode 100644 smithy-rules-engine/src/test/java/software/amazon/smithy/rulesengine/language/TestDiscovery.java rename smithy-rules-engine/src/test/java/software/amazon/smithy/rulesengine/language/syntax/{fn => functions}/GetAttrTest.java (90%) rename smithy-rules-engine/src/{main/resources/software/amazon/smithy/rulesengine/testutil => test/resources/software/amazon/smithy/rulesengine/language}/test-cases/aws-region.json (100%) rename smithy-rules-engine/src/{main/resources/software/amazon/smithy/rulesengine/testutil => test/resources/software/amazon/smithy/rulesengine/language}/test-cases/default-values.json (100%) rename smithy-rules-engine/src/{main/resources/software/amazon/smithy/rulesengine/testutil => test/resources/software/amazon/smithy/rulesengine/language}/test-cases/eventbridge.json (100%) rename smithy-rules-engine/src/{main/resources/software/amazon/smithy/rulesengine/testutil => test/resources/software/amazon/smithy/rulesengine/language}/test-cases/fns.json (100%) rename smithy-rules-engine/src/{main/resources/software/amazon/smithy/rulesengine/testutil => test/resources/software/amazon/smithy/rulesengine/language}/test-cases/headers.json (100%) rename smithy-rules-engine/src/{main/resources/software/amazon/smithy/rulesengine/testutil => test/resources/software/amazon/smithy/rulesengine/language}/test-cases/is-virtual-hostable-s3-bucket.json (100%) rename smithy-rules-engine/src/{main/resources/software/amazon/smithy/rulesengine/testutil => test/resources/software/amazon/smithy/rulesengine/language}/test-cases/local-region-override.json (100%) rename smithy-rules-engine/src/{main/resources/software/amazon/smithy/rulesengine/testutil => test/resources/software/amazon/smithy/rulesengine/language}/test-cases/parse-arn.json (100%) rename smithy-rules-engine/src/{main/resources/software/amazon/smithy/rulesengine/testutil => test/resources/software/amazon/smithy/rulesengine/language}/test-cases/parse-url.json (100%) rename smithy-rules-engine/src/{main/resources/software/amazon/smithy/rulesengine/testutil => test/resources/software/amazon/smithy/rulesengine/language}/test-cases/partition-fn.json (100%) rename smithy-rules-engine/src/{main/resources/software/amazon/smithy/rulesengine/testutil => test/resources/software/amazon/smithy/rulesengine/language}/test-cases/substring.json (100%) rename smithy-rules-engine/src/{main/resources/software/amazon/smithy/rulesengine/testutil => test/resources/software/amazon/smithy/rulesengine/language}/test-cases/uri-encode.json (100%) rename smithy-rules-engine/src/{main/resources/software/amazon/smithy/rulesengine/testutil => test/resources/software/amazon/smithy/rulesengine/language}/test-cases/valid-hostlabel.json (100%) rename smithy-rules-engine/src/{main/resources/software/amazon/smithy/rulesengine/testutil => test/resources/software/amazon/smithy/rulesengine/language}/valid-rules/aws-region.json (100%) rename smithy-rules-engine/src/{main/resources/software/amazon/smithy/rulesengine/testutil => test/resources/software/amazon/smithy/rulesengine/language}/valid-rules/beta-auth-scheme.json (100%) rename smithy-rules-engine/src/{main/resources/software/amazon/smithy/rulesengine/testutil => test/resources/software/amazon/smithy/rulesengine/language}/valid-rules/default-values.json (100%) rename smithy-rules-engine/src/{main/resources/software/amazon/smithy/rulesengine/testutil => test/resources/software/amazon/smithy/rulesengine/language}/valid-rules/deprecated-param.json (100%) rename smithy-rules-engine/src/{main/resources/software/amazon/smithy/rulesengine/testutil => test/resources/software/amazon/smithy/rulesengine/language}/valid-rules/eventbridge.json (100%) rename smithy-rules-engine/src/{main/resources/software/amazon/smithy/rulesengine/testutil => test/resources/software/amazon/smithy/rulesengine/language}/valid-rules/fns.json (100%) rename smithy-rules-engine/src/{main/resources/software/amazon/smithy/rulesengine/testutil => test/resources/software/amazon/smithy/rulesengine/language}/valid-rules/get-attr-type-inference.json (100%) rename smithy-rules-engine/src/{main/resources/software/amazon/smithy/rulesengine/testutil => test/resources/software/amazon/smithy/rulesengine/language}/valid-rules/headers.json (100%) rename smithy-rules-engine/src/{main/resources/software/amazon/smithy/rulesengine/testutil => test/resources/software/amazon/smithy/rulesengine/language}/valid-rules/is-virtual-hostable-s3-bucket.json (100%) rename smithy-rules-engine/src/{main/resources/software/amazon/smithy/rulesengine/testutil => test/resources/software/amazon/smithy/rulesengine/language}/valid-rules/local-region-override.json (100%) rename smithy-rules-engine/src/{main/resources/software/amazon/smithy/rulesengine/testutil => test/resources/software/amazon/smithy/rulesengine/language}/valid-rules/minimal-ruleset.json (100%) rename smithy-rules-engine/src/{main/resources/software/amazon/smithy/rulesengine/testutil => test/resources/software/amazon/smithy/rulesengine/language}/valid-rules/parse-arn.json (100%) rename smithy-rules-engine/src/{main/resources/software/amazon/smithy/rulesengine/testutil => test/resources/software/amazon/smithy/rulesengine/language}/valid-rules/parse-url.json (100%) rename smithy-rules-engine/src/{main/resources/software/amazon/smithy/rulesengine/testutil => test/resources/software/amazon/smithy/rulesengine/language}/valid-rules/partition-fn.json (100%) rename smithy-rules-engine/src/{main/resources/software/amazon/smithy/rulesengine/testutil => test/resources/software/amazon/smithy/rulesengine/language}/valid-rules/substring.json (100%) rename smithy-rules-engine/src/{main/resources/software/amazon/smithy/rulesengine/testutil => test/resources/software/amazon/smithy/rulesengine/language}/valid-rules/uri-encode.json (100%) rename smithy-rules-engine/src/{main/resources/software/amazon/smithy/rulesengine/testutil => test/resources/software/amazon/smithy/rulesengine/language}/valid-rules/valid-hostlabel.json (100%) diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/analysis/CoverageChecker.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/analysis/CoverageChecker.java index de8099da1de..0f5752b51eb 100644 --- a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/analysis/CoverageChecker.java +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/analysis/CoverageChecker.java @@ -17,21 +17,16 @@ import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Objects; import java.util.stream.Collectors; import java.util.stream.Stream; import software.amazon.smithy.rulesengine.language.EndpointRuleSet; import software.amazon.smithy.rulesengine.language.eval.RuleEvaluator; -import software.amazon.smithy.rulesengine.language.eval.Value; +import software.amazon.smithy.rulesengine.language.eval.value.Value; import software.amazon.smithy.rulesengine.language.syntax.Identifier; import software.amazon.smithy.rulesengine.language.syntax.rule.Condition; -import software.amazon.smithy.rulesengine.language.syntax.rule.Rule; -import software.amazon.smithy.rulesengine.language.util.PathFinder; -import software.amazon.smithy.rulesengine.language.util.StringUtils; import software.amazon.smithy.rulesengine.language.visit.TraversingVisitor; import software.amazon.smithy.rulesengine.traits.EndpointTestCase; import software.amazon.smithy.utils.SmithyUnstableApi; @@ -64,7 +59,7 @@ public void evaluateInput(Map input) { * @param testCase the test case to evaluate. */ public void evaluateTestCase(EndpointTestCase testCase) { - HashMap map = new HashMap<>(); + Map map = new LinkedHashMap<>(); testCase.getParams().getStringMap().forEach((s, node) -> map.put(Identifier.of(s), Value.fromNode(node))); this.checkerCore.evaluateRuleSet(ruleSet, map); } @@ -79,21 +74,10 @@ public Stream checkCoverage() { return coverageForConditions(conditions); } - /** - * Analyze and provides the coverage results for a specific rule. - * - * @return stream of {@link CoverageResult}. - */ - public Stream checkCoverageFromRule(Rule rule) { - Stream conditions = rule.accept(new CollectConditions()); - return coverageForConditions(conditions); - - } - private Stream coverageForConditions(Stream conditions) { return conditions.distinct().flatMap(condition -> { - Wrapper w = new Wrapper<>(condition); - ArrayList conditionResults = checkerCore.conditionResults.getOrDefault(w, new ArrayList<>()); + List conditionResults = checkerCore.conditionResults.getOrDefault(condition, + new ArrayList<>()); List branches = conditionResults.stream() .map(c -> c.result) .distinct() @@ -111,44 +95,19 @@ private Stream coverageForConditions(Stream condition }); } - private static class Wrapper { - private final T inner; - - Wrapper(T inner) { - this.inner = inner; - } - - @Override - public int hashCode() { - return Objects.hash(inner); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Wrapper wrapper = (Wrapper) o; - return inner.equals(wrapper.inner); - } - } - - public static class BranchResult { + private static class BranchResult { private final boolean result; private final CoverageCheckerCore.Context context; - public BranchResult(boolean result, CoverageCheckerCore.Context context) { + BranchResult(boolean result, CoverageCheckerCore.Context context) { this.result = result; this.context = context; } } - static class CoverageCheckerCore extends RuleEvaluator { - HashMap, ArrayList> conditionResults = new HashMap<>(); - Context context = null; + private static class CoverageCheckerCore extends RuleEvaluator { + private final Map> conditionResults = new LinkedHashMap<>(); + private Context context = null; @Override public Value evaluateRuleSet(EndpointRuleSet ruleset, Map parameterArguments) { @@ -162,20 +121,22 @@ public Value evaluateRuleSet(EndpointRuleSet ruleset, Map par @Override public Value evaluateCondition(Condition condition) { - assert context != null; + if (context == null) { + throw new RuntimeException("Must call `evaluateRuleSet` before calling `evaluateCondition`"); + } + Value result = super.evaluateCondition(condition); - Wrapper cond = new Wrapper<>(condition); - ArrayList list = conditionResults.getOrDefault(cond, new ArrayList<>()); - if (result.isNone() || result.equals(Value.bool(false))) { + List list = conditionResults.getOrDefault(condition, new ArrayList<>()); + if (result.isEmpty() || result.equals(Value.booleanValue(false))) { list.add(new BranchResult(false, context)); } else { list.add(new BranchResult(true, context)); } - conditionResults.put(cond, list); + conditionResults.put(condition, list); return result; } - static class Context { + private static class Context { private final Map input; Context(Map input) { @@ -184,7 +145,7 @@ static class Context { } } - static class CollectConditions extends TraversingVisitor { + private static class CollectConditions extends TraversingVisitor { @Override public Stream visitConditions(List conditions) { return conditions.stream(); @@ -202,15 +163,15 @@ public CoverageResult(Condition condition, boolean result, List> otherUsages() { + public List> getOtherUsages() { return otherUsages; } @@ -221,27 +182,8 @@ public String pretty() { } private String pretty(Condition condition) { - return new StringBuilder() - .append(condition) - .append("(") - .append(condition.getSourceLocation().getFilename()) - .append(":") - .append(condition.getSourceLocation().getLine()) - .append(")") - .toString(); - } - - public String prettyWithPath(EndpointRuleSet ruleset) { - PathFinder.Path path = PathFinder.findPath(ruleset, condition).orElseThrow(NoSuchElementException::new); - StringBuilder sb = new StringBuilder(); - sb.append(pretty()).append("\n"); - for (List cond : path.negated()) { - sb.append(StringUtils.indent(String.format("!%s", cond.toString()), 2)); - } - for (Condition cond : path.positive()) { - sb.append(StringUtils.indent(cond.toString(), 2)); - } - return sb.toString(); + return condition + "(" + condition.getSourceLocation().getFilename() + ":" + + condition.getSourceLocation().getLine() + ")"; } } } diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/Endpoint.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/Endpoint.java index 3f1b19da784..42d09d259db 100644 --- a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/Endpoint.java +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/Endpoint.java @@ -15,45 +15,42 @@ package software.amazon.smithy.rulesengine.language; -import static software.amazon.smithy.rulesengine.language.error.RuleError.context; - +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; -import java.util.LinkedHashMap; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.TreeMap; -import java.util.stream.Collectors; import software.amazon.smithy.model.FromSourceLocation; import software.amazon.smithy.model.SourceLocation; import software.amazon.smithy.model.node.ArrayNode; import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.node.ObjectNode; +import software.amazon.smithy.model.node.StringNode; import software.amazon.smithy.model.node.ToNode; import software.amazon.smithy.rulesengine.language.error.RuleError; import software.amazon.smithy.rulesengine.language.eval.Scope; -import software.amazon.smithy.rulesengine.language.eval.Type; import software.amazon.smithy.rulesengine.language.eval.TypeCheck; +import software.amazon.smithy.rulesengine.language.eval.type.Type; import software.amazon.smithy.rulesengine.language.syntax.Identifier; -import software.amazon.smithy.rulesengine.language.syntax.expr.Expression; -import software.amazon.smithy.rulesengine.language.syntax.expr.Literal; -import software.amazon.smithy.rulesengine.language.util.MandatorySourceLocation; -import software.amazon.smithy.rulesengine.language.util.SourceLocationTrackingBuilder; -import software.amazon.smithy.rulesengine.language.util.StringUtils; +import software.amazon.smithy.rulesengine.language.syntax.expressions.Expression; +import software.amazon.smithy.rulesengine.language.syntax.expressions.literal.Literal; import software.amazon.smithy.utils.BuilderRef; import software.amazon.smithy.utils.MapUtils; import software.amazon.smithy.utils.Pair; import software.amazon.smithy.utils.SmithyBuilder; import software.amazon.smithy.utils.SmithyUnstableApi; +import software.amazon.smithy.utils.StringUtils; import software.amazon.smithy.utils.ToSmithyBuilder; /** - * An Endpoint as returned by EndpointRules. + * An EndpointType as returned by EndpointRules. */ @SmithyUnstableApi -public final class Endpoint extends MandatorySourceLocation implements ToSmithyBuilder, TypeCheck, ToNode { +public final class Endpoint implements FromSourceLocation, ToNode, ToSmithyBuilder, TypeCheck { private static final String URL = "url"; private static final String PROPERTIES = "properties"; private static final String HEADERS = "headers"; @@ -61,31 +58,29 @@ public final class Endpoint extends MandatorySourceLocation implements ToSmithyB private static final String SIG_V4A = "sigv4a"; private static final String SIGNING_REGION = "signingRegion"; - private final Expression url; private final Map> headers; private final Map properties; + private final SourceLocation sourceLocation; + private final Expression url; private Endpoint(Builder builder) { - super(builder.getSourceLocation()); + super(); + this.headers = builder.headers.copy(); + this.sourceLocation = SmithyBuilder.requiredState("source", builder.getSourceLocation()); this.url = SmithyBuilder.requiredState("url", builder.url); - Map properties = new LinkedHashMap<>(builder.properties.copy()); - List authSchemes = - builder.authSchemes.copy().stream() - .map( - authScheme -> { - Map base = new TreeMap<>( - Comparator.comparing(Identifier::asString)); - base.put(Identifier.of("name"), Literal.of(authScheme.left.asString())); - base.putAll(authScheme.right); - return Literal.record(base); - }) - .collect(Collectors.toList()); + + List authSchemes = new ArrayList<>(); + for (Pair> authScheme : builder.authSchemes.get()) { + Map base = new TreeMap<>(Comparator.comparing(Identifier::toString)); + base.put(Identifier.of("name"), Literal.of(authScheme.left.asString())); + base.putAll(authScheme.right); + authSchemes.add(Literal.recordLiteral(base)); + } if (!authSchemes.isEmpty()) { - properties.put(Identifier.of("authSchemes"), Literal.tuple(authSchemes)); + builder.putProperty(Identifier.of("authSchemes"), Literal.tupleLiteral(authSchemes)); } - this.properties = properties; - this.headers = builder.headers.copy(); + this.properties = builder.properties.copy(); } /** @@ -95,45 +90,47 @@ private Endpoint(Builder builder) { * @return the node as an {@link Endpoint}. */ public static Endpoint fromNode(Node node) { - ObjectNode on = node.expectObjectNode(); + ObjectNode objectNode = node.expectObjectNode(); Builder builder = builder() .sourceLocation(node); - builder.url(Expression.fromNode(on.expectMember(URL, "URL must be included in endpoint"))); - on.expectNoAdditionalProperties(Arrays.asList(PROPERTIES, HEADERS, URL)); - - on.getObjectMember(PROPERTIES) - .ifPresent( - props -> { - Map members = new LinkedHashMap<>(); - props.getMembers() - .forEach((k, v) -> members.put(Identifier.of(k), Literal.fromNode(v))); - builder.properties(members); - }); - - on.getObjectMember(HEADERS).ifPresent(objectNode -> { - objectNode.getMembers().forEach((headerName, headerValues) -> { - builder.addHeader(headerName.getValue(), - headerValues.expectArrayNode("header values should be an array") - .getElements().stream().map(Expression::fromNode).collect(Collectors.toList())); - }); + builder.url(Expression.fromNode(objectNode.expectMember(URL, "URL must be included in endpoint"))); + objectNode.expectNoAdditionalProperties(Arrays.asList(PROPERTIES, HEADERS, URL)); + + objectNode.getObjectMember(PROPERTIES, properties -> { + for (Map.Entry member : properties.getMembers().entrySet()) { + builder.putProperty(Identifier.of(member.getKey()), Literal.fromNode(member.getValue())); + } + }); + + objectNode.getObjectMember(HEADERS, headers -> { + for (Map.Entry header : headers.getMembers().entrySet()) { + builder.putHeader(header.getKey().getValue(), + header.getValue().expectArrayNode("header values should be an array") + .getElementsAs(Expression::fromNode)); + } }); return builder.build(); } /** - * Create a new Endpoint builder. + * Create a new EndpointType builder. * - * @return Endpoint builder + * @return EndpointType builder */ public static Builder builder() { return new Builder(SourceLocation.none()); } + @Override + public SourceLocation getSourceLocation() { + return sourceLocation; + } + /** - * Returns the Endpoint URL as an expression. + * Returns the EndpointType URL as an expression. * * @return the endpoint URL expression. */ @@ -141,11 +138,27 @@ public Expression getUrl() { return url; } + /** + * Get the endpoint headers as a map of {@link String} to list of {@link Expression} values. + * + * @return the endpoint headers. + */ + public Map> getHeaders() { + return headers; + } + + /** + * Get the endpoint properties as a map of {@link Identifier} to {@link Literal} values. + * + * @return the endpoint properties. + */ + public Map getProperties() { + return properties; + } + @Override public Builder toBuilder() { - return builder() - .sourceLocation(this.getSourceLocation()) - .url(url).properties(properties); + return builder().sourceLocation(sourceLocation).url(url).headers(headers).properties(properties); } @Override @@ -169,37 +182,47 @@ public boolean equals(Object o) { public String toString() { StringBuilder sb = new StringBuilder(); sb.append("url: ").append(url).append("\n"); + if (!headers.isEmpty()) { - headers.forEach( - (key, value) -> { - sb.append(StringUtils.indent(String.format("%s:%s", key, value), 2)); - }); + sb.append("headers:\n"); + for (Map.Entry> entry : headers.entrySet()) { + sb.append(StringUtils.indent(String.format("%s: %s", entry.getKey(), entry.getValue()), 2)) + .append("\n"); + } } + if (!properties.isEmpty()) { sb.append("properties:\n"); - properties.forEach((k, v) -> sb.append(StringUtils.indent(String.format("%s: %s", k, v), 2))); + for (Map.Entry entry : properties.entrySet()) { + sb.append(StringUtils.indent(String.format("%s: %s", entry.getKey(), entry.getValue()), 2)) + .append("\n"); + } } + return sb.toString(); } @Override public Type typeCheck(Scope scope) { - context("while checking the URL", url, () -> url.typeCheck(scope).expectString()); + RuleError.context("while checking the URL", url, () -> url.typeCheck(scope).expectStringType()); + RuleError.context("while checking properties", () -> { - properties.forEach((k, lit) -> { - lit.typeCheck(scope); - }); + for (Literal literal : properties.values()) { + literal.typeCheck(scope); + } return null; }); + RuleError.context("while checking headers", () -> { for (List headerList : headers.values()) { for (Expression header : headerList) { - header.typeCheck(scope).expectString(); + header.typeCheck(scope).expectStringType(); } } return null; }); - return Type.endpoint(); + + return Type.endpointType(); } @Override @@ -212,52 +235,35 @@ public Node toNode() { } private Node propertiesNode() { - ObjectNode.Builder on = ObjectNode.builder(); - properties.forEach((k, v) -> - on.withMember(k.toString(), v.toNode()) - ); - return on.build(); + ObjectNode.Builder builder = ObjectNode.builder(); + for (Map.Entry entry : properties.entrySet()) { + builder.withMember(entry.getKey().toString(), entry.getValue().toNode()); + } + return builder.build(); } private Node headersNode() { - return exprMapNode(headers); - } - - private Node exprMapNode(Map> m) { - ObjectNode.Builder mapNode = ObjectNode.builder(); - m.forEach((k, v) -> mapNode.withMember(k, ArrayNode.fromNodes(v.stream() - .map(Expression::toNode) - .collect(Collectors.toList())))); - return mapNode.build(); - } - - /** - * Get the endpoint properties as a map of {@link Identifier} to {@link Literal} values. - * - * @return the endpoint properties. - */ - public Map getProperties() { - return properties; - } - - /** - * Get the endpoint headers as a map of {@link String} to list of {@link Expression} values. - * - * @return the endpoint headers. - */ - public Map> getHeaders() { - return headers; + ObjectNode.Builder builder = ObjectNode.builder(); + for (Map.Entry> entry : headers.entrySet()) { + List expressionNodes = new ArrayList<>(); + for (Expression expression : entry.getValue()) { + expressionNodes.add(expression.toNode()); + } + builder.withMember(entry.getKey(), ArrayNode.fromNodes(expressionNodes)); + } + return builder.build(); } /** * Builder for {@link Endpoint}. */ - public static class Builder extends SourceLocationTrackingBuilder { + public static class Builder extends RulesComponentBuilder { private static final String SIGNING_NAME = "signingName"; private static final String SIGNING_REGION_SET = "signingRegionSet"; private final BuilderRef>> headers = BuilderRef.forOrderedMap(); private final BuilderRef> properties = BuilderRef.forOrderedMap(); + // TODO Why a list of pairs and not a map? Are duplicate IDs allowed? private final BuilderRef>>> authSchemes = BuilderRef.forList(); private Expression url; @@ -270,6 +276,11 @@ public Builder url(Expression url) { return this; } + public Builder putProperty(Identifier identifier, Literal value) { + this.properties.get().put(identifier, value); + return this; + } + public Builder properties(Map properties) { this.properties.clear(); this.properties.get().putAll(properties); @@ -288,10 +299,11 @@ public Builder addAuthScheme(Identifier scheme, Map paramet } public Builder addAuthScheme(String scheme, Map parameters) { - this.authSchemes.get().add(Pair.of(Identifier.of(scheme), - parameters.entrySet().stream() - .collect(Collectors.toMap(k -> Identifier.of(k.getKey()), Map.Entry::getValue)))); - return this; + Map transformedParameters = new HashMap<>(); + for (Map.Entry parameter : parameters.entrySet()) { + transformedParameters.put(Identifier.of(parameter.getKey()), parameter.getValue()); + } + return addAuthScheme(Identifier.of(scheme), transformedParameters); } public Builder sigv4(Literal signingRegion, Literal signingService) { @@ -299,7 +311,7 @@ public Builder sigv4(Literal signingRegion, Literal signingService) { } public Builder sigv4a(List signingRegionSet, Literal signingService) { - return addAuthScheme(SIG_V4A, MapUtils.of(SIGNING_REGION_SET, Literal.tuple(signingRegionSet), + return addAuthScheme(SIG_V4A, MapUtils.of(SIGNING_REGION_SET, Literal.tupleLiteral(signingRegionSet), SIGNING_NAME, signingService)); } @@ -309,12 +321,12 @@ public Builder headers(Map> headers) { return this; } - public Builder addHeader(String name, List value) { + public Builder putHeader(String name, List value) { this.headers.get().put(name, value); return this; } - public Builder addHeader(String name, Literal value) { + public Builder putHeader(String name, Literal value) { // Note: if we want to add multi-header support in the future we'll need to tackle that separately if (this.headers.get().containsKey(name)) { throw new RuntimeException(String.format("A header already exists for %s", name)); diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/EndpointRuleSet.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/EndpointRuleSet.java index f17fbadfa18..fda00e2141a 100644 --- a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/EndpointRuleSet.java +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/EndpointRuleSet.java @@ -16,7 +16,6 @@ package software.amazon.smithy.rulesengine.language; import static software.amazon.smithy.rulesengine.language.error.RuleError.context; -import static software.amazon.smithy.rulesengine.language.util.StringUtils.indent; import java.util.Collection; import java.util.List; @@ -26,39 +25,39 @@ import software.amazon.smithy.model.node.ArrayNode; import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.node.ObjectNode; -import software.amazon.smithy.model.node.StringNode; import software.amazon.smithy.model.node.ToNode; import software.amazon.smithy.rulesengine.language.error.RuleError; import software.amazon.smithy.rulesengine.language.eval.Scope; -import software.amazon.smithy.rulesengine.language.eval.Type; import software.amazon.smithy.rulesengine.language.eval.TypeCheck; +import software.amazon.smithy.rulesengine.language.eval.type.Type; import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameters; import software.amazon.smithy.rulesengine.language.syntax.rule.EndpointRule; import software.amazon.smithy.rulesengine.language.syntax.rule.Rule; -import software.amazon.smithy.rulesengine.language.util.MandatorySourceLocation; -import software.amazon.smithy.rulesengine.language.util.SourceLocationTrackingBuilder; import software.amazon.smithy.utils.BuilderRef; import software.amazon.smithy.utils.SmithyBuilder; import software.amazon.smithy.utils.SmithyUnstableApi; +import software.amazon.smithy.utils.StringUtils; /** - * A set of EndpointRules. Endpoint Rules describe the endpoint resolution behavior for a service. + * A set of EndpointRules. EndpointType Rules describe the endpoint resolution behavior for a service. */ @SmithyUnstableApi -public final class EndpointRuleSet extends MandatorySourceLocation implements TypeCheck, ToNode { +public final class EndpointRuleSet implements FromSourceLocation, ToNode, TypeCheck { private static final String LATEST_VERSION = "1.3"; private static final String VERSION = "version"; private static final String PARAMETERS = "parameters"; private static final String RULES = "rules"; - private final List rules; private final Parameters parameters; + private final List rules; + private final SourceLocation sourceLocation; private final String version; private EndpointRuleSet(Builder builder) { - super(builder.getSourceLocation()); - rules = builder.rules.copy(); + super(); parameters = SmithyBuilder.requiredState("parameters", builder.parameters); + rules = builder.rules.copy(); + sourceLocation = SmithyBuilder.requiredState("source", builder.getSourceLocation()); version = SmithyBuilder.requiredState("version", builder.version); } @@ -68,21 +67,27 @@ public static EndpointRuleSet fromNode(Node node) throws RuleError { private static EndpointRuleSet newFromNode(Node node) throws RuleError { ObjectNode on = node.expectObjectNode("The root of a ruleset must be an object"); + EndpointRuleSet.Builder builder = new Builder(node); - Parameters parameters = Parameters.fromNode(on.expectObjectMember(PARAMETERS)); - StringNode version = on.expectStringMember(VERSION); + builder.parameters(Parameters.fromNode(on.expectObjectMember(PARAMETERS))); + builder.version(on.expectStringMember(VERSION).getValue()); on.expectArrayMember(RULES) .getElements().forEach(n -> { builder.addRule(context("while parsing rule", n, () -> EndpointRule.fromNode(n))); }); - return builder.version(version.getValue()).parameters(parameters).build(); + return builder.build(); } public static Builder builder() { return new Builder(SourceLocation.none()); } + @Override + public SourceLocation getSourceLocation() { + return sourceLocation; + } + public Parameters getParameters() { return parameters; } @@ -91,6 +96,10 @@ public List getRules() { return rules; } + public String getVersion() { + return version; + } + @Override public Type typeCheck(Scope scope) { return scope.inScope(() -> { @@ -98,11 +107,11 @@ public Type typeCheck(Scope scope) { for (Rule rule : rules) { rule.typeCheck(scope); } - return Type.endpoint(); + return Type.endpointType(); }); } - public void typecheck() { + public void typeCheck() { typeCheck(new Scope<>()); } @@ -119,7 +128,8 @@ public Builder toBuilder() { return builder() .sourceLocation(getSourceLocation()) .parameters(parameters) - .rules(getRules()); + .rules(getRules()) + .version(version); } private Node rulesNode() { @@ -149,16 +159,16 @@ public boolean equals(Object o) { public String toString() { StringBuilder builder = new StringBuilder(); builder.append(String.format("version: %s%n", version)); - builder.append("params: \n").append(indent(parameters.toString(), 2)); + builder.append("params: \n").append(StringUtils.indent(parameters.toString(), 2)); builder.append("rules: \n"); - rules.forEach(rule -> builder.append(indent(rule.toString(), 2))); + rules.forEach(rule -> builder.append(StringUtils.indent(rule.toString(), 2))); return builder.toString(); } - public static class Builder extends SourceLocationTrackingBuilder { + public static class Builder extends RulesComponentBuilder { private final BuilderRef> rules = BuilderRef.forList(); private Parameters parameters; - // default the version to the latest. + // Default the version to the latest. private String version = LATEST_VERSION; /** @@ -212,6 +222,7 @@ public Builder addRule(int index, Rule rule) { * @return the {@link Builder} */ public Builder rules(Collection rules) { + this.rules.clear(); this.rules.get().addAll(rules); return this; } @@ -230,7 +241,7 @@ public Builder parameters(Parameters parameters) { @Override public EndpointRuleSet build() { EndpointRuleSet ruleSet = new EndpointRuleSet(this); - ruleSet.typecheck(); + ruleSet.typeCheck(); return ruleSet; } } diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/util/SourceLocationUtils.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/RulesComponentBuilder.java similarity index 57% rename from smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/util/SourceLocationUtils.java rename to smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/RulesComponentBuilder.java index 45493db38c1..0ac8c533509 100644 --- a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/util/SourceLocationUtils.java +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/RulesComponentBuilder.java @@ -13,14 +13,42 @@ * permissions and limitations under the License. */ -package software.amazon.smithy.rulesengine.language.util; +package software.amazon.smithy.rulesengine.language; +import software.amazon.smithy.model.FromSourceLocation; import software.amazon.smithy.model.SourceLocation; -import software.amazon.smithy.utils.SmithyInternalApi; +import software.amazon.smithy.utils.SmithyBuilder; +import software.amazon.smithy.utils.SmithyUnstableApi; -@SmithyInternalApi -public final class SourceLocationUtils { - private SourceLocationUtils() { +/** + * Builder which also tracks sourceLocation of inputs. If no source location is provided, the source location defaults + * to the Java file/line number of the caller. + * @param Type of the builder + * @param Type of the type that is built + */ +@SmithyUnstableApi +public abstract class RulesComponentBuilder, T> + implements SmithyBuilder, FromSourceLocation { + + private SourceLocation sourceLocation; + + public RulesComponentBuilder(FromSourceLocation sourceLocation) { + if (sourceLocation.getSourceLocation() == SourceLocation.NONE) { + this.sourceLocation = javaLocation(); + } else { + this.sourceLocation = sourceLocation.getSourceLocation(); + } + } + + @Override + public SourceLocation getSourceLocation() { + return sourceLocation; + } + + @SuppressWarnings("unchecked") + public B sourceLocation(FromSourceLocation fromSourceLocation) { + this.sourceLocation = fromSourceLocation.getSourceLocation(); + return (B) this; } /** @@ -44,26 +72,6 @@ public static SourceLocation javaLocation() { return SourceLocation.none(); } - /** - * Prints a source location in "stack trace form": `filename:linenumber`. - * @param sourceLocation Source location to print - * @return formatted source location - */ - public static String stackTraceForm(SourceLocation sourceLocation) { - if (sourceLocation == SourceLocation.NONE) { - return "N/A"; - } - StringBuilder sb = new StringBuilder(); - if (sourceLocation.getFilename() != null) { - sb.append(sourceLocation.getFilename()); - } - if (sourceLocation.getLine() != 0) { - sb.append(":").append(sourceLocation.getLine()); - } - /* column is ignored in stack trace form */ - return sb.toString(); - } - /** * Tests if the given {@code StackTraceElement} is relevant for a comment * used when writing debug information before calls to write. @@ -77,8 +85,8 @@ public static String stackTraceForm(SourceLocation sourceLocation) { static boolean isStackTraceRelevant(StackTraceElement e) { String normalized = e.getClassName().replace("$", "."); return !normalized.startsWith("java.") - && !normalized.startsWith("jdk.") - && !normalized.startsWith(SourceLocationUtils.class.getCanonicalName()) - && !normalized.startsWith("software.amazon.smithy.rulesengine"); + && !normalized.startsWith("jdk.") + && !normalized.startsWith(RulesComponentBuilder.class.getCanonicalName()) + && !normalized.startsWith("software.amazon.smithy.rulesengine"); } } diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/error/InnerParseError.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/error/InnerParseError.java index 626af683782..0d0de8778f0 100644 --- a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/error/InnerParseError.java +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/error/InnerParseError.java @@ -21,7 +21,7 @@ * Represents an error encountered when parsing a rule-set expression. */ @SmithyUnstableApi -public final class InnerParseError extends Exception { +public final class InnerParseError extends RuntimeException { public InnerParseError(String message) { super(message); } diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/error/InvalidRulesException.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/error/InvalidRulesException.java index dfb8c3e7dd4..f4490738355 100644 --- a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/error/InvalidRulesException.java +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/error/InvalidRulesException.java @@ -17,7 +17,6 @@ import software.amazon.smithy.model.FromSourceLocation; import software.amazon.smithy.model.SourceLocation; -import software.amazon.smithy.rulesengine.language.util.SourceLocationUtils; import software.amazon.smithy.utils.SmithyUnstableApi; /** @@ -41,8 +40,24 @@ private static String createMessage(String message, SourceLocation sourceLocatio if (sourceLocation == SourceLocation.NONE) { return message; } else { - String prettyLocation = SourceLocationUtils.stackTraceForm(sourceLocation); + String prettyLocation = stackTraceForm(sourceLocation); return message.contains(prettyLocation) ? message : message + " (" + prettyLocation + ")"; } } + + private static String stackTraceForm(SourceLocation sourceLocation) { + if (sourceLocation == SourceLocation.NONE) { + return "N/A"; + } + + StringBuilder sb = new StringBuilder(); + if (sourceLocation.getFilename() != null) { + sb.append(sourceLocation.getFilename()); + } + if (sourceLocation.getLine() != 0) { + sb.append(":").append(sourceLocation.getLine()); + } + // column is ignored in stack trace form + return sb.toString(); + } } diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/error/RuleError.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/error/RuleError.java index fa3454a8b70..a8b6b33f0ad 100644 --- a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/error/RuleError.java +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/error/RuleError.java @@ -37,7 +37,7 @@ public RuleError(SourceException root) { this.root = root; } - public static T context(String message, Runnable f) throws RuleError { + public static T context(String message, Runnable f) { return RuleError.context(message, SourceLocation.none(), () -> { f.run(); return null; diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/RuleEvaluator.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/RuleEvaluator.java index 40dab3c27a7..88d5eded95e 100644 --- a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/RuleEvaluator.java +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/RuleEvaluator.java @@ -15,17 +15,20 @@ package software.amazon.smithy.rulesengine.language.eval; +import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; import software.amazon.smithy.rulesengine.language.Endpoint; import software.amazon.smithy.rulesengine.language.EndpointRuleSet; +import software.amazon.smithy.rulesengine.language.eval.value.EndpointValue; +import software.amazon.smithy.rulesengine.language.eval.value.Value; import software.amazon.smithy.rulesengine.language.syntax.Identifier; -import software.amazon.smithy.rulesengine.language.syntax.expr.Expression; -import software.amazon.smithy.rulesengine.language.syntax.expr.Literal; -import software.amazon.smithy.rulesengine.language.syntax.expr.Reference; -import software.amazon.smithy.rulesengine.language.syntax.fn.FunctionDefinition; -import software.amazon.smithy.rulesengine.language.syntax.fn.GetAttr; +import software.amazon.smithy.rulesengine.language.syntax.expressions.Expression; +import software.amazon.smithy.rulesengine.language.syntax.expressions.Reference; +import software.amazon.smithy.rulesengine.language.syntax.expressions.literal.Literal; +import software.amazon.smithy.rulesengine.language.syntax.functions.FunctionDefinition; +import software.amazon.smithy.rulesengine.language.syntax.functions.GetAttr; +import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameter; import software.amazon.smithy.rulesengine.language.syntax.rule.Condition; import software.amazon.smithy.rulesengine.language.syntax.rule.Rule; import software.amazon.smithy.rulesengine.language.visit.ExpressionVisitor; @@ -58,24 +61,21 @@ public static Value evaluate(EndpointRuleSet ruleset, Map par * @return The resulting value from the final matched rule. */ public Value evaluateRuleSet(EndpointRuleSet ruleset, Map parameterArguments) { - return scope.inScope( - () -> { - ruleset - .getParameters() - .toList() - .forEach( - param -> { - param.getDefault().ifPresent(value -> scope.insert(param.getName(), value)); - }); - parameterArguments.forEach(scope::insert); - for (Rule rule : ruleset.getRules()) { - Value result = handleRule(rule); - if (!result.isNone()) { - return result; - } - } - throw new RuntimeException("No rules in ruleset matched"); - }); + return scope.inScope(() -> { + for (Parameter parameter : ruleset.getParameters().toList()) { + parameter.getDefault().ifPresent(value -> scope.insert(parameter.getName(), value)); + } + + parameterArguments.forEach(scope::insert); + + for (Rule rule : ruleset.getRules()) { + Value result = handleRule(rule); + if (!result.isEmpty()) { + return result; + } + } + throw new RuntimeException("No rules in ruleset matched"); + }); } @Override @@ -87,27 +87,29 @@ public Value visitLiteral(Literal literal) { public Value visitRef(Reference reference) { return scope .getValue(reference.getName()) - .orElse(Value.none()); + .orElse(Value.emptyValue()); } @Override public Value visitIsSet(Expression fn) { - return Value.bool(!fn.accept(this).isNone()); + return Value.booleanValue(!fn.accept(this).isEmpty()); } @Override public Value visitNot(Expression not) { - return Value.bool(!not.accept(this).expectBool()); + return Value.booleanValue(!not.accept(this).expectBooleanValue().getValue()); } @Override public Value visitBoolEquals(Expression left, Expression right) { - return Value.bool(left.accept(this).expectBool() == right.accept(this).expectBool()); + return Value.booleanValue(left.accept(this).expectBooleanValue() + .equals(right.accept(this).expectBooleanValue())); } @Override public Value visitStringEquals(Expression left, Expression right) { - return Value.bool(left.accept(this).expectString().equals(right.accept(this).expectString())); + return Value.booleanValue(left.accept(this).expectStringValue() + .equals(right.accept(this).expectStringValue())); } public Value visitGetAttr(GetAttr getAttr) { @@ -116,15 +118,20 @@ public Value visitGetAttr(GetAttr getAttr) { @Override public Value visitLibraryFunction(FunctionDefinition definition, List arguments) { - return definition.evaluate(arguments.stream().map(arg -> arg.accept(this)).collect(Collectors.toList())); + List values = new ArrayList<>(); + for (Expression argument : arguments) { + values.add(argument.accept(this)); + } + return definition.evaluate(values); } private Value handleRule(Rule rule) { + RuleEvaluator self = this; return scope.inScope(() -> { for (Condition condition : rule.getConditions()) { Value value = evaluateCondition(condition); - if (value.isNone() || value.equals(Value.bool(false))) { - return Value.none(); + if (value.isEmpty() || value.equals(Value.booleanValue(false))) { + return Value.emptyValue(); } } return rule.accept(new RuleValueVisitor() { @@ -132,7 +139,7 @@ private Value handleRule(Rule rule) { public Value visitTreeRule(List rules) { for (Rule subRule : rules) { Value result = handleRule(subRule); - if (!result.isNone()) { + if (!result.isEmpty()) { return result; } } @@ -142,40 +149,32 @@ public Value visitTreeRule(List rules) { @Override public Value visitErrorRule(Expression error) { - return RuleEvaluator.this.visitErrorRule(error); + return error.accept(self); } @Override public Value visitEndpointRule(Endpoint endpoint) { - return RuleEvaluator.this.visitEndpointRule(endpoint); + EndpointValue.Builder builder = EndpointValue.builder() + .sourceLocation(endpoint) + .url(endpoint.getUrl() + .accept(RuleEvaluator.this) + .expectStringValue() + .getValue()); + endpoint.getProperties() + .forEach((key, value) -> builder.putProperty(key.toString(), + value.accept(RuleEvaluator.this))); + endpoint.getHeaders() + .forEach((name, expressions) -> expressions.forEach(expr -> builder.addHeader(name, + expr.accept(RuleEvaluator.this).expectStringValue().getValue()))); + return builder.build(); } }); }); } - public Value visitErrorRule(Expression error) { - return error.accept(this); - } - - public Value visitEndpointRule(Endpoint endpoint) { - Value.Endpoint.Builder builder = Value.Endpoint.builder() - .sourceLocation(endpoint) - .url(endpoint.getUrl() - .accept(RuleEvaluator.this) - .expectString()); - endpoint.getProperties() - .forEach((key, value) -> builder.addProperty(key.toString(), - value.accept(RuleEvaluator.this))); - endpoint.getHeaders() - .forEach((name, expressions) -> expressions.forEach(expr -> builder.addHeader(name, - expr.accept(RuleEvaluator.this).expectString()))); - return builder.build(); - - } - public Value evaluateCondition(Condition condition) { Value value = condition.getFn().accept(this); - if (!value.isNone()) { + if (!value.isEmpty()) { condition.getResult().ifPresent(res -> scope.insert(res, value)); } return value; diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/Scope.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/Scope.java index 53d4b5f6c1c..b3a582e3c36 100644 --- a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/Scope.java +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/Scope.java @@ -21,12 +21,12 @@ import java.util.Map; import java.util.Optional; import java.util.function.Supplier; -import java.util.stream.Collectors; import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.node.ObjectNode; import software.amazon.smithy.rulesengine.language.error.InnerParseError; +import software.amazon.smithy.rulesengine.language.eval.value.Value; import software.amazon.smithy.rulesengine.language.syntax.Identifier; -import software.amazon.smithy.rulesengine.language.syntax.expr.Reference; +import software.amazon.smithy.rulesengine.language.syntax.expressions.Reference; import software.amazon.smithy.utils.SmithyUnstableApi; /** @@ -67,7 +67,7 @@ public void insert(Identifier name, T value) { } public void setNonNull(Reference name) { - this.scope.getFirst().getNonNullRefs().add(name); + this.scope.getFirst().getNonNullReferences().add(name); } public U inScope(Supplier func) { @@ -89,7 +89,12 @@ public String toString() { } public boolean isNonNull(Reference reference) { - return scope.stream().anyMatch(s -> s.getNonNullRefs().contains(reference)); + for (ScopeLayer layer : scope) { + if (layer.getNonNullReferences().contains(reference)) { + return true; + } + } + return false; } public T expectValue(Identifier name) throws InnerParseError { @@ -104,9 +109,12 @@ public T expectValue(Identifier name) throws InnerParseError { public Optional> getDeclaration(Identifier name) { for (ScopeLayer layer : scope) { if (layer.getTypes().containsKey(name)) { - return Optional.of(layer.getTypes().entrySet().stream() - .filter(e -> e.getKey().equals(name)) - .collect(Collectors.toList()).get(0)); + for (Map.Entry type : layer.getTypes().entrySet()) { + if (type.getKey().equals(name)) { + return Optional.of(type); + } + } + return Optional.empty(); } } return Optional.empty(); diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/ScopeLayer.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/ScopeLayer.java index dadd9c62911..4c9c74b5ff5 100644 --- a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/ScopeLayer.java +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/ScopeLayer.java @@ -21,15 +21,16 @@ import java.util.Objects; import java.util.Set; import software.amazon.smithy.rulesengine.language.syntax.Identifier; -import software.amazon.smithy.rulesengine.language.syntax.expr.Reference; +import software.amazon.smithy.rulesengine.language.syntax.expressions.Reference; import software.amazon.smithy.utils.SmithyUnstableApi; @SmithyUnstableApi +// TODO The internal containers of this class are mutated. final class ScopeLayer { private final Map types; private final Set nonNullReferences; - ScopeLayer(HashMap types, Set nonNullReferences) { + ScopeLayer(Map types, Set nonNullReferences) { this.types = types; this.nonNullReferences = nonNullReferences; } @@ -42,7 +43,7 @@ public Map getTypes() { return types; } - public Set getNonNullRefs() { + public Set getNonNullReferences() { return nonNullReferences; } diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/TestEvaluator.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/TestEvaluator.java index 34078fbb05a..d11880ddbd3 100644 --- a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/TestEvaluator.java +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/TestEvaluator.java @@ -15,18 +15,19 @@ package software.amazon.smithy.rulesengine.language.eval; -import static software.amazon.smithy.rulesengine.language.util.StringUtils.indent; - import java.util.LinkedHashMap; import java.util.Map; import java.util.Optional; import software.amazon.smithy.rulesengine.language.EndpointRuleSet; import software.amazon.smithy.rulesengine.language.error.RuleError; +import software.amazon.smithy.rulesengine.language.eval.value.EndpointValue; +import software.amazon.smithy.rulesengine.language.eval.value.Value; import software.amazon.smithy.rulesengine.language.syntax.Identifier; import software.amazon.smithy.rulesengine.traits.EndpointTestCase; import software.amazon.smithy.rulesengine.traits.EndpointTestExpectation; import software.amazon.smithy.rulesengine.traits.ExpectedEndpoint; import software.amazon.smithy.utils.SmithyUnstableApi; +import software.amazon.smithy.utils.StringUtils; /** * Provides facilities for evaluating an endpoint rule-set and tests. @@ -66,27 +67,27 @@ private static void evaluateExpectation(EndpointTestExpectation want, Value got) if (want.getEndpoint().isPresent()) { ExpectedEndpoint wantEndpoint = want.getEndpoint().get(); - Value.Endpoint.Builder builder = Value.Endpoint.builder() + EndpointValue.Builder builder = EndpointValue.builder() .url(wantEndpoint.getUrl()) .headers(wantEndpoint.getHeaders()); wantEndpoint.getProperties().forEach((s, node) -> { - builder.addProperty(s, Value.fromNode(node)); + builder.putProperty(s, Value.fromNode(node)); }); - Value.Endpoint wantValue = builder.build(); + EndpointValue wantValue = builder.build(); - if (!got.expectEndpoint().equals(wantValue)) { + if (!got.expectEndpointValue().equals(wantValue)) { throw new AssertionError( String.format("Expected endpoint:%n%s but got:%n%s (generated by %s)", - indent(wantEndpoint.toString(), 2), - indent(got.toString(), 2), + StringUtils.indent(wantEndpoint.toString(), 2), + StringUtils.indent(got.toString(), 2), wantEndpoint.getSourceLocation())); } } else { String wantError = want.getError().get(); RuleError.context("While checking endpoint test (expecting an error)", () -> { - if (!got.expectString().equals(wantError)) { + if (!got.expectStringValue().getValue().equals(wantError)) { throw new AssertionError(String.format("Expected error `%s` but got `%s`", wantError, got)); } }); diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/Type.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/Type.java deleted file mode 100644 index 327382c2d86..00000000000 --- a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/Type.java +++ /dev/null @@ -1,436 +0,0 @@ -/* - * 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.eval; - -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import software.amazon.smithy.rulesengine.language.error.InnerParseError; -import software.amazon.smithy.rulesengine.language.syntax.Identifier; -import software.amazon.smithy.utils.SmithyUnstableApi; - -@SmithyUnstableApi -public interface Type { - static String string() { - return new String(); - } - - static Endpoint endpoint() { - return new Endpoint(); - } - - static Type empty() { - return new Type.Empty(); - } - - static Array array(Type inner) { - return new Type.Array(inner); - } - - static Record record(Map inner) { - return new Record(inner); - } - - static Type integer() { - return new Integer(); - } - - static Option optional(Type t) { - return new Option(t); - } - - static Bool bool() { - return new Bool(); - } - - default String expectString() throws InnerParseError { - throw new InnerParseError("Expected string but found " + this); - } - - default Record expectObject(java.lang.String message) throws InnerParseError { - throw new InnerParseError(java.lang.String.format("Expected record but found %s%n == hint: %s", this, message)); - } - - default Bool expectBool() throws InnerParseError { - throw new InnerParseError("Expected boolean but found " + this); - } - - default Integer expectInt() throws InnerParseError { - throw new InnerParseError("Expected int but found " + this); - } - - default Option expectOptional() throws InnerParseError { - throw new InnerParseError("Expected optional but found " + this); - } - - default Array expectArray() throws InnerParseError { - throw new InnerParseError("Expected array but found " + this); - } - - default boolean isA(Type t) { - if (t.equals(new Type.Any())) { - return true; - } - return t.equals(this); - } - - /** - * When used in the context of a condition, the condition can only match if the value was truthful. This means - * that a certain expression can be a different type, for example, {@code Option} will become {@code T}. - * - * @return The type, given that it has been proven truthy - */ - default Type provenTruthy() { - return this; - } - - final class Integer implements Type { - @Override - public int hashCode() { - return 2; - } - - @Override - public boolean equals(Object obj) { - return obj == this || obj != null && obj.getClass() == this.getClass(); - } - - @Override - public java.lang.String toString() { - return "Int"; - } - - @Override - public Integer expectInt() { - return this; - } - } - - final class Any implements Type { - public Any() { - } - - @Override - public boolean isA(Type t) { - return true; - } - - @Override - public int hashCode() { - return 1; - } - - @Override - public boolean equals(Object obj) { - return obj == this || obj != null && obj.getClass() == this.getClass(); - } - - @Override - public java.lang.String toString() { - return "Any[]"; - } - - } - - final class Empty implements Type { - public Empty() { - } - - @Override - public int hashCode() { - return 1; - } - - @Override - public boolean equals(Object obj) { - return obj == this || obj != null && obj.getClass() == this.getClass(); - } - - @Override - public java.lang.String toString() { - return "Empty[]"; - } - - } - - final class Endpoint implements Type { - public Endpoint() { - } - - @Override - public int hashCode() { - return 1; - } - - @Override - public boolean equals(Object obj) { - return obj == this || obj != null && obj.getClass() == this.getClass(); - } - - @Override - public java.lang.String toString() { - return "Endpoint[]"; - } - - } - - final class String implements Type { - public String() { - } - - @Override - public String expectString() { - return this; - } - - @Override - public int hashCode() { - return 1; - } - - @Override - public boolean equals(Object obj) { - return obj == this || obj != null && obj.getClass() == this.getClass(); - } - - @Override - public java.lang.String toString() { - return "String"; - } - - } - - final class Bool implements Type { - public Bool() { - } - - public Bool expectBool() { - return this; - } - - @Override - public int hashCode() { - return 1; - } - - @Override - public boolean equals(Object obj) { - return obj == this || obj != null && obj.getClass() == this.getClass(); - } - - @Override - public java.lang.String toString() { - return "Bool"; - } - - } - - final class Option implements Type { - private final Type inner; - - public Option(Type inner) { - this.inner = inner; - } - - @Override - public String expectString() throws InnerParseError { - throw new InnerParseError(java.lang.String - .format("Expected string but found %s. hint: use `assign` in a condition " - + "or `isSet` to prove that this value is non-null", this)); - } - - @Override - public Bool expectBool() throws InnerParseError { - throw new InnerParseError(java.lang.String - .format("Expected boolean but found %s. hint: use `isSet` to convert " - + "Option to bool", this)); - } - - @Override - public Option expectOptional() throws InnerParseError { - return this; - } - - @Override - public boolean isA(Type t) { - if (!(t instanceof Option)) { - return false; - } - return ((Option) t).inner.isA(inner); - } - - @Override - public Type provenTruthy() { - return inner; - } - - public Type inner() { - return inner; - } - - @Override - public int hashCode() { - return Objects.hash(inner); - } - - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (obj == null || obj.getClass() != this.getClass()) { - return false; - } - Option that = (Option) obj; - return Objects.equals(this.inner, that.inner); - } - - @Override - public java.lang.String toString() { - return java.lang.String.format("Option<%s>", inner); - } - - } - - final class Tuple implements Type { - private final List members; - - public Tuple(List members) { - this.members = members; - } - - public List members() { - return members; - } - - @Override - public int hashCode() { - return Objects.hash(members); - } - - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (obj == null || obj.getClass() != this.getClass()) { - return false; - } - Tuple that = (Tuple) obj; - return Objects.equals(this.members, that.members); - } - - @Override - public java.lang.String toString() { - return this.members.toString(); - } - - } - - final class Array implements Type { - private final Type member; - - public Array(Type member) { - this.member = member; - } - - public Type getMember() { - return member; - } - - @Override - public Array expectArray() throws InnerParseError { - return this; - } - - public Type member() { - return member; - } - - @Override - public int hashCode() { - return Objects.hash(member); - } - - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (obj == null || obj.getClass() != this.getClass()) { - return false; - } - Array that = (Array) obj; - return Objects.equals(this.member, that.member); - } - - @Override - public java.lang.String toString() { - return java.lang.String.format("[%s]", this.member); - } - - } - - final class Record implements Type { - private final Map shape; - - public Record(Map shape) { - this.shape = new LinkedHashMap<>(shape); - } - - @Override - public Record expectObject(java.lang.String message) { - return this; - } - - public Optional get(Identifier name) { - if (shape.containsKey(name)) { - return Optional.of(shape.get(name)); - } else { - return Optional.empty(); - } - } - - public Map shape() { - return shape; - } - - @Override - public int hashCode() { - return Objects.hash(shape); - } - - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (obj == null || obj.getClass() != this.getClass()) { - return false; - } - Record that = (Record) obj; - return Objects.equals(this.shape, that.shape); - } - - @Override - public java.lang.String toString() { - return shape.toString(); - } - - } -} diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/TypeCheck.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/TypeCheck.java index b044c0ecf94..6b4b70793a0 100644 --- a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/TypeCheck.java +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/TypeCheck.java @@ -15,6 +15,7 @@ package software.amazon.smithy.rulesengine.language.eval; +import software.amazon.smithy.rulesengine.language.eval.type.Type; import software.amazon.smithy.utils.SmithyUnstableApi; /* diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/Value.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/Value.java deleted file mode 100644 index 6ac85ec764b..00000000000 --- a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/Value.java +++ /dev/null @@ -1,619 +0,0 @@ -/* - * 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.eval; - -import static software.amazon.smithy.rulesengine.language.util.StringUtils.indent; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.function.BiConsumer; -import java.util.stream.Collectors; -import software.amazon.smithy.model.FromSourceLocation; -import software.amazon.smithy.model.SourceException; -import software.amazon.smithy.model.SourceLocation; -import software.amazon.smithy.model.node.ArrayNode; -import software.amazon.smithy.model.node.BooleanNode; -import software.amazon.smithy.model.node.Node; -import software.amazon.smithy.model.node.NodeVisitor; -import software.amazon.smithy.model.node.NullNode; -import software.amazon.smithy.model.node.NumberNode; -import software.amazon.smithy.model.node.ObjectNode; -import software.amazon.smithy.model.node.StringNode; -import software.amazon.smithy.model.node.ToNode; -import software.amazon.smithy.rulesengine.language.syntax.Identifier; -import software.amazon.smithy.rulesengine.language.util.SourceLocationTrackingBuilder; -import software.amazon.smithy.rulesengine.language.util.SourceLocationUtils; -import software.amazon.smithy.utils.BuilderRef; -import software.amazon.smithy.utils.SmithyBuilder; -import software.amazon.smithy.utils.SmithyUnstableApi; - -/** - * An abstract representing a typed value. - */ -@SmithyUnstableApi -public abstract class Value implements FromSourceLocation, ToNode { - private SourceLocation sourceLocation; - - public Value(SourceLocation sourceLocation) { - this.sourceLocation = sourceLocation; - } - - public static Value fromNode(Node source) { - Value value = source.accept(new NodeVisitor() { - @Override - public Value arrayNode(ArrayNode node) { - return new Array(node.getElements().stream().map(Value::fromNode).collect(Collectors.toList())); - } - - @Override - public Value booleanNode(BooleanNode node) { - return bool(node.getValue()); - } - - @Override - public Value nullNode(NullNode node) { - throw new RuntimeException("null cannot be used as literal"); - } - - @Override - public Value numberNode(NumberNode node) { - if (!node.isNaturalNumber()) { - throw new RuntimeException("only integers >=0 are supported"); - } - return Value.integer(node.getValue().intValue()); - } - - @Override - public Value objectNode(ObjectNode node) { - HashMap out = new LinkedHashMap<>(); - node.getMembers().forEach((name, member) -> out.put(Identifier.of(name), Value.fromNode(member))); - return Value.record(out); - } - - @Override - public Value stringNode(StringNode node) { - return Value.string(node.getValue()); - } - }); - value.sourceLocation = source.getSourceLocation(); - return value; - } - - public static Endpoint endpointFromNode(Node source) { - Endpoint ep = Endpoint.fromNode(source); - ((Value) ep).sourceLocation = source.getSourceLocation(); - return ep; - } - - public static Value none() { - return new None(); - } - - public static String string(java.lang.String value) { - return new String(value); - } - - public static Record record(Map value) { - return new Record(value); - } - - public static Bool bool(boolean value) { - return new Bool(value); - } - - public static Array array(List value) { - return new Array(value); - } - - public static Integer integer(int value) { - return new Integer(value); - } - - public abstract Type type(); - - public java.lang.String expectString() { - throw new RuntimeException("Expected string but was: " + this); - } - - public boolean expectBool() { - throw new RuntimeException("Expected bool but was: " + this); - } - - @Override - public SourceLocation getSourceLocation() { - return Optional.ofNullable(sourceLocation).orElse(SourceLocation.none()); - } - - public Record expectRecord() { - throw new RuntimeException("Expected object but was: " + this); - } - - public boolean isNone() { - return false; - } - - public Endpoint expectEndpoint() { - throw new RuntimeException("Expected endpoint, found " + this); - } - - public Array expectArray() { - throw new RuntimeException("Expected array, found " + this); - } - - public int expectInteger() { - throw new RuntimeException("Expected int, found " + this); - } - - public static final class Integer extends Value { - private final int value; - - private Integer(int value) { - super(SourceLocation.none()); - this.value = value; - } - - @Override - public Type type() { - return Type.integer(); - } - - @Override - public int expectInteger() { - return value; - } - - @Override - public Node toNode() { - return Node.from(value); - } - } - - public static final class String extends Value { - private final java.lang.String value; - - private String(java.lang.String value) { - super(SourceLocation.none()); - this.value = value; - } - - @Override - public Type type() { - return Type.string(); - } - - @Override - public java.lang.String expectString() { - return value(); - } - - public java.lang.String value() { - return value; - } - - @Override - public int hashCode() { - return Objects.hash(value); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - String string = (String) o; - return value.equals(string.value); - } - - @Override - public java.lang.String toString() { - return value; - } - - @Override - public Node toNode() { - return StringNode.from(value); - } - } - - public static final class Bool extends Value { - - private final boolean value; - - private Bool(boolean value) { - super(SourceLocation.none()); - this.value = value; - } - - @Override - public Type type() { - return Type.bool(); - } - - @Override - public boolean expectBool() { - return value(); - } - - private boolean value() { - return this.value; - } - - @Override - public Node toNode() { - return BooleanNode.from(value); - } - - @Override - public int hashCode() { - return Objects.hash(value); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - Bool bool = (Bool) o; - - return value == bool.value; - } - - @Override - public java.lang.String toString() { - return java.lang.String.valueOf(value); - } - } - - public static final class Record extends Value { - private final Map value; - - private Record(Map value) { - super(SourceLocation.none()); - this.value = value; - } - - @Override - public Type type() { - Map type = value.entrySet() - .stream() - .collect(Collectors.toMap(Map.Entry::getKey, value -> value.getValue().type())); - return new Type.Record(type); - } - - @Override - public Record expectRecord() { - return this; - } - - public Value get(java.lang.String key) { - return get(Identifier.of(key)); - } - - public Value get(Identifier key) { - return this.value.get(key); - } - - public void forEach(BiConsumer fn) { - value.forEach(fn); - } - - @Override - public Node toNode() { - ObjectNode.Builder builder = ObjectNode.builder(); - value.forEach((k, v) -> builder.withMember(k.getName(), v)); - return builder.build(); - } - - @Override - public int hashCode() { - return Objects.hash(value); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Record record = (Record) o; - return Objects.equals(value, record.value); - } - - @Override - public java.lang.String toString() { - return value.toString(); - } - - public Map getValue() { - return this.value; - } - } - - public static final class Array extends Value { - private final List inner; - - private Array(List values) { - super(SourceLocation.none()); - this.inner = values; - } - - public List getValues() { - return inner; - } - - @Override - public Type type() { - if (inner.isEmpty()) { - return Type.array(Type.empty()); - } else { - Type first = inner.get(0).type(); - if (inner.stream().allMatch(item -> item.type() == first)) { - return Type.array(first); - } else { - throw new SourceException("An array cannot contain different types", this); - } - } - } - - @Override - public Array expectArray() { - return this; - } - - public Value get(int idx) { - if (this.inner.size() > idx) { - return this.inner.get(idx); - } else { - return new Value.None(); - } - } - - @Override - public Node toNode() { - return inner.stream() - .map(ToNode::toNode) - .collect(ArrayNode.collect()); - } - - @Override - public int hashCode() { - return Objects.hash(inner); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Array array = (Array) o; - return inner.equals(array.inner); - } - - @Override - public java.lang.String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("["); - sb.append(inner.stream().map(Object::toString).collect(Collectors.joining(", "))); - sb.append("]"); - return sb.toString(); - } - } - - public static final class None extends Value { - - public None() { - super(SourceLocation.none()); - } - - @Override - public Type type() { - return Type.empty(); - } - - @Override - public boolean isNone() { - return true; - } - - @Override - public Node toNode() { - return Node.nullNode(); - } - } - - public static final class Endpoint extends Value { - private final java.lang.String url; - private final Map properties; - private final Map> headers; - - private Endpoint(Builder builder) { - super(builder.getSourceLocation()); - this.url = SmithyBuilder.requiredState("url", builder.url); - this.properties = builder.properties.copy(); - this.headers = builder.headers.copy(); - } - - public static Endpoint fromNode(Node node) { - Builder builder = new Builder(node); - ObjectNode on = node.expectObjectNode("endpoints are object nodes"); - on.expectNoAdditionalProperties(Arrays.asList("properties", "url", "headers")); - builder.url(on.expectStringMember("url").getValue()); - on.getObjectMember("properties").ifPresent(props -> { - props.getMembers().forEach((k, v) -> { - builder.addProperty(k.getValue(), Value.fromNode(v)); - }); - - }); - - on.getObjectMember("headers").ifPresent(headers -> headers.getMembers().forEach(((key, value) -> { - java.lang.String name = key.getValue(); - value.expectArrayNode("Header values must be an array").getElements() - .forEach(e -> builder.addHeader(name, e.expectStringNode().getValue())); - }))); - return builder.build(); - } - - public static Builder builder() { - return new Builder(SourceLocationUtils.javaLocation()); - } - - @Override - public Node toNode() { - return ObjectNode.builder() - .withMember("url", url) - .withMember("properties", propertiesNode()) - .withMember("headers", headersNode()) - .build(); - } - - private Node propertiesNode() { - ObjectNode.Builder b = ObjectNode.builder(); - properties.forEach(b::withMember); - return b.build(); - } - - private Node headersNode() { - ObjectNode.Builder builder = ObjectNode.builder(); - - headers.forEach((k, v) -> { - ArrayNode valuesNode = v.stream().map(StringNode::from).collect(ArrayNode.collect()); - builder.withMember(k, valuesNode); - }); - - return builder.build(); - } - - public Map getProperties() { - return properties; - } - - public java.lang.String getUrl() { - return url; - } - - public Map> getHeaders() { - return headers; - } - - @Override - public Type type() { - return Type.endpoint(); - } - - @Override - public Endpoint expectEndpoint() { - return this; - } - - @Override - public int hashCode() { - return Objects.hash(url, properties, headers); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Endpoint endpoint = (Endpoint) o; - return url.equals(endpoint.url) - && properties.equals(endpoint.properties) - && headers.equals(endpoint.headers); - } - - @Override - public java.lang.String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("url: ").append(url).append("\n"); - sb.append("properties:\n"); - sb.append(indent(properties.toString(), 2)); - // todo(rcoh) - if (!headers.isEmpty()) { - headers.forEach((key, value) -> { - sb.append(indent(java.lang.String.format("%s:%s", key, value), 2)); - }); - } - return sb.toString(); - } - - public static final class Builder extends SourceLocationTrackingBuilder { - private final BuilderRef> properties = BuilderRef.forOrderedMap(); - private final BuilderRef>> headers = - BuilderRef.forOrderedMap(); - private java.lang.String url; - - public Builder(FromSourceLocation sourceLocation) { - super(sourceLocation); - } - - public Builder url(java.lang.String url) { - this.url = url; - return this; - } - - public Builder headers(Map> headers) { - this.headers.clear(); - this.headers.get().putAll(headers); - return this; - } - - public Builder addHeader(java.lang.String name, java.lang.String value) { - List values = this.headers.get().computeIfAbsent(name, (k) -> new ArrayList<>()); - values.add(value); - return this; - } - - public Builder properties(Map properties) { - this.properties.clear(); - this.properties.get().putAll(properties); - return this; - } - - public Builder addProperty(java.lang.String value, Value fromNode) { - this.properties.get().put(value, fromNode); - return this; - } - - @Override - public Endpoint build() { - return new Endpoint(this); - } - - } - - } -} diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/AbstractType.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/AbstractType.java new file mode 100644 index 00000000000..af50fd23a99 --- /dev/null +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/AbstractType.java @@ -0,0 +1,33 @@ +/* + * 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.eval.type; + +abstract class AbstractType implements Type { + @Override + public int hashCode() { + return 1; + } + + @Override + public boolean equals(Object obj) { + return obj == this || obj != null && obj.getClass() == this.getClass(); + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } +} diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/IntoSelf.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/AnyType.java similarity index 64% rename from smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/IntoSelf.java rename to smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/AnyType.java index b537dcb100a..996f07ec8e9 100644 --- a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/IntoSelf.java +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/AnyType.java @@ -13,18 +13,18 @@ * permissions and limitations under the License. */ -package software.amazon.smithy.rulesengine; +package software.amazon.smithy.rulesengine.language.eval.type; -import software.amazon.smithy.utils.SmithyUnstableApi; +public final class AnyType extends AbstractType { + AnyType() {} + + @Override + public boolean isA(Type type) { + return true; + } -/** - * An interface that describe a type that can convert itself into itself. - * @param the type. - */ -@SmithyUnstableApi -public interface IntoSelf> extends Into { @Override - default T into() { - return (T) this; + public String toString() { + return "AnyType[]"; } } diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/ArrayType.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/ArrayType.java new file mode 100644 index 00000000000..173a8488806 --- /dev/null +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/ArrayType.java @@ -0,0 +1,58 @@ +/* + * 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.eval.type; + +import java.util.Objects; + +public final class ArrayType extends AbstractType { + private final Type member; + + ArrayType(Type member) { + this.member = member; + } + + public Type getMember() { + return member; + } + + @Override + public ArrayType expectArrayType() { + return this; + } + + @Override + public int hashCode() { + return Objects.hash(member); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + ArrayType that = (ArrayType) obj; + return Objects.equals(this.member, that.member); + } + + @Override + public String toString() { + return String.format("[%s]", this.member); + } + +} diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/Into.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/BooleanType.java similarity index 68% rename from smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/Into.java rename to smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/BooleanType.java index 9f60f913877..d28d7ac27a1 100644 --- a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/Into.java +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/BooleanType.java @@ -13,15 +13,12 @@ * permissions and limitations under the License. */ -package software.amazon.smithy.rulesengine; +package software.amazon.smithy.rulesengine.language.eval.type; -import software.amazon.smithy.utils.SmithyUnstableApi; +public final class BooleanType extends AbstractType { + BooleanType() {} -/** - * An interface that describe a type that can be transformed to type T. - * @param the type. - */ -@SmithyUnstableApi -public interface Into { - T into(); + public BooleanType expectBooleanType() { + return this; + } } diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/EmptyType.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/EmptyType.java new file mode 100644 index 00000000000..3ce34a5740b --- /dev/null +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/EmptyType.java @@ -0,0 +1,25 @@ +/* + * 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.eval.type; + +public final class EmptyType extends AbstractType { + EmptyType() {} + + @Override + public String toString() { + return "EmptyType[]"; + } +} diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/EndpointType.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/EndpointType.java new file mode 100644 index 00000000000..f3fd52c4d8f --- /dev/null +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/EndpointType.java @@ -0,0 +1,25 @@ +/* + * 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.eval.type; + +public final class EndpointType extends AbstractType { + EndpointType() {} + + @Override + public String toString() { + return "EndpointType[]"; + } +} diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/IntegerType.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/IntegerType.java new file mode 100644 index 00000000000..3fd7fe61843 --- /dev/null +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/IntegerType.java @@ -0,0 +1,25 @@ +/* + * 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.eval.type; + +public final class IntegerType extends AbstractType { + IntegerType() {} + + @Override + public IntegerType expectIntegerType() { + return this; + } +} diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/OptionalType.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/OptionalType.java new file mode 100644 index 00000000000..1f0988a0ecd --- /dev/null +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/OptionalType.java @@ -0,0 +1,84 @@ +/* + * 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.eval.type; + +import java.util.Objects; +import software.amazon.smithy.rulesengine.language.error.InnerParseError; + +public final class OptionalType extends AbstractType { + private final Type inner; + + public OptionalType(Type inner) { + this.inner = inner; + } + + @Override + public StringType expectStringType() throws InnerParseError { + throw new InnerParseError(String.format("Expected string but found %s. hint: use `assign` in a condition " + + "or `isSet` to prove that this value is non-null", this)); + } + + @Override + public BooleanType expectBooleanType() throws InnerParseError { + throw new InnerParseError(String.format("Expected boolean but found %s. hint: use `isSet` to convert " + + "OptionalType to bool", this)); + } + + @Override + public OptionalType expectOptionalType() { + return this; + } + + @Override + public boolean isA(Type type) { + if (!(type instanceof OptionalType)) { + return false; + } + return ((OptionalType) type).inner.isA(inner); + } + + @Override + public Type provenTruthy() { + return inner; + } + + public Type inner() { + return inner; + } + + @Override + public int hashCode() { + return Objects.hash(inner); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + OptionalType that = (OptionalType) obj; + return Objects.equals(this.inner, that.inner); + } + + @Override + public String toString() { + return String.format("OptionalType<%s>", inner); + } + +} diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/RecordType.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/RecordType.java new file mode 100644 index 00000000000..66c4dae4775 --- /dev/null +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/RecordType.java @@ -0,0 +1,69 @@ +/* + * 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.eval.type; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import software.amazon.smithy.rulesengine.language.syntax.Identifier; + +public final class RecordType extends AbstractType { + private final Map shape; + + public RecordType(Map shape) { + this.shape = new LinkedHashMap<>(shape); + } + + @Override + public RecordType expectRecordType(String message) { + return this; + } + + public Optional get(Identifier name) { + if (shape.containsKey(name)) { + return Optional.of(shape.get(name)); + } else { + return Optional.empty(); + } + } + + public Map getShape() { + return shape; + } + + @Override + public int hashCode() { + return Objects.hash(shape); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + RecordType that = (RecordType) obj; + return Objects.equals(this.shape, that.shape); + } + + @Override + public String toString() { + return shape.toString(); + } +} diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/StringType.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/StringType.java new file mode 100644 index 00000000000..b47fc516e5f --- /dev/null +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/StringType.java @@ -0,0 +1,25 @@ +/* + * 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.eval.type; + +public final class StringType extends AbstractType { + StringType() {} + + @Override + public StringType expectStringType() { + return this; + } +} diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/TupleType.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/TupleType.java new file mode 100644 index 00000000000..734bb93cfa8 --- /dev/null +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/TupleType.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.eval.type; + +import java.util.List; +import java.util.Objects; + +public final class TupleType extends AbstractType { + private final List memberTypes; + + public TupleType(List memberTypes) { + this.memberTypes = memberTypes; + } + + public List getMemberTypes() { + return memberTypes; + } + + @Override + public int hashCode() { + return Objects.hash(memberTypes); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + TupleType that = (TupleType) obj; + return Objects.equals(this.memberTypes, that.memberTypes); + } + + @Override + public String toString() { + return this.memberTypes.toString(); + } + +} diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/Type.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/Type.java new file mode 100644 index 00000000000..cf28338d2c5 --- /dev/null +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/type/Type.java @@ -0,0 +1,119 @@ +/* + * 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.eval.type; + +import java.util.List; +import java.util.Map; +import software.amazon.smithy.rulesengine.language.error.InnerParseError; +import software.amazon.smithy.rulesengine.language.syntax.Identifier; +import software.amazon.smithy.utils.SmithyUnstableApi; + +@SmithyUnstableApi +public interface Type { + default boolean isA(Type type) { + return type.equals(this); + } + + /** + * When used in the context of a condition, the condition can only match if the value was truthful. This means + * that a certain expression can be a different type, for example, {@code OptionalType} will become {@code T}. + * + * @return The type, given that it has been proven truthy + */ + default Type provenTruthy() { + return this; + } + + static AnyType anyType() { + return new AnyType(); + } + + static ArrayType arrayType(Type inner) { + return new ArrayType(inner); + } + + static BooleanType booleanType() { + return new BooleanType(); + } + + static EmptyType emptyType() { + return new EmptyType(); + } + + static EndpointType endpointType() { + return new EndpointType(); + } + + static IntegerType integerType() { + return new IntegerType(); + } + + static OptionalType optionalType(Type type) { + return new OptionalType(type); + } + + static RecordType recordType(Map inner) { + return new RecordType(inner); + } + + static StringType stringType() { + return new StringType(); + } + + static TupleType tupleType(List members) { + return new TupleType(members); + } + + default AnyType expectAnyType() throws InnerParseError { + throw new InnerParseError("Expected any but found " + this); + } + + default ArrayType expectArrayType() throws InnerParseError { + throw new InnerParseError("Expected array but found " + this); + } + + default BooleanType expectBooleanType() throws InnerParseError { + throw new InnerParseError("Expected boolean but found " + this); + } + + default EmptyType expectEmptyType() throws InnerParseError { + throw new InnerParseError("Expected empty but found " + this); + } + + default EndpointType expectEndpointType() throws InnerParseError { + throw new InnerParseError("Expected endpoint but found " + this); + } + + default IntegerType expectIntegerType() throws InnerParseError { + throw new InnerParseError("Expected integer but found " + this); + } + + default OptionalType expectOptionalType() throws InnerParseError { + throw new InnerParseError("Expected optional but found " + this); + } + + default RecordType expectRecordType(String message) throws InnerParseError { + throw new InnerParseError(String.format("Expected record but found %s%n == hint: %s", this, message)); + } + + default StringType expectStringType() throws InnerParseError { + throw new InnerParseError("Expected string but found " + this); + } + + default TupleType expectTupleType() throws InnerParseError { + throw new InnerParseError("Expected tuple but found " + this); + } +} diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/value/ArrayValue.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/value/ArrayValue.java new file mode 100644 index 00000000000..e22728c29bd --- /dev/null +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/value/ArrayValue.java @@ -0,0 +1,105 @@ +/* + * 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.eval.value; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import software.amazon.smithy.model.SourceException; +import software.amazon.smithy.model.SourceLocation; +import software.amazon.smithy.model.node.ArrayNode; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.rulesengine.language.eval.type.Type; + +public final class ArrayValue extends Value { + private final List values; + + ArrayValue(List values) { + super(SourceLocation.none()); + this.values = values; + } + + public List getValues() { + return values; + } + + @Override + public Type getType() { + if (values.isEmpty()) { + return Type.arrayType(Type.emptyType()); + } else { + Type first = values.get(0).getType(); + for (Value value : values) { + if (value.getType() != first) { + throw new SourceException("An array cannot contain different types", this); + } + } + return Type.arrayType(first); + } + } + + @Override + public ArrayValue expectArrayValue() { + return this; + } + + public Value get(int idx) { + if (this.values.size() > idx) { + return this.values.get(idx); + } else { + return Value.emptyValue(); + } + } + + @Override + public Node toNode() { + ArrayNode.Builder builder = ArrayNode.builder(); + for (Value value : values) { + builder.withValue(value.toNode()); + } + return builder.build(); + } + + @Override + public int hashCode() { + return Objects.hash(values); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ArrayValue array = (ArrayValue) o; + return values.equals(array.values); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("["); + List valueStrings = new ArrayList<>(); + for (Value value : values) { + valueStrings.add(value.toString()); + } + sb.append(String.join(", ", valueStrings)); + sb.append("]"); + return sb.toString(); + } +} diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/value/BooleanValue.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/value/BooleanValue.java new file mode 100644 index 00000000000..71a49cd05bf --- /dev/null +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/value/BooleanValue.java @@ -0,0 +1,74 @@ +/* + * 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.eval.value; + +import java.util.Objects; +import software.amazon.smithy.model.SourceLocation; +import software.amazon.smithy.model.node.BooleanNode; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.rulesengine.language.eval.type.Type; + +public final class BooleanValue extends Value { + private final boolean value; + + BooleanValue(boolean value) { + super(SourceLocation.none()); + this.value = value; + } + + @Override + public Type getType() { + return Type.booleanType(); + } + + @Override + public BooleanValue expectBooleanValue() { + return this; + } + + public boolean getValue() { + return value; + } + + @Override + public Node toNode() { + return BooleanNode.from(value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + BooleanValue bool = (BooleanValue) o; + + return value == bool.value; + } + + @Override + public String toString() { + return String.valueOf(value); + } +} diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/value/EmptyValue.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/value/EmptyValue.java new file mode 100644 index 00000000000..6f7080a4db7 --- /dev/null +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/value/EmptyValue.java @@ -0,0 +1,41 @@ +/* + * 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.eval.value; + +import software.amazon.smithy.model.SourceLocation; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.rulesengine.language.eval.type.Type; + +public final class EmptyValue extends Value { + public EmptyValue() { + super(SourceLocation.none()); + } + + @Override + public Type getType() { + return Type.emptyType(); + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public Node toNode() { + return Node.nullNode(); + } +} diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/value/EndpointValue.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/value/EndpointValue.java new file mode 100644 index 00000000000..50f6daef09b --- /dev/null +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/value/EndpointValue.java @@ -0,0 +1,192 @@ +/* + * 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.eval.value; + +import static software.amazon.smithy.rulesengine.language.RulesComponentBuilder.javaLocation; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import software.amazon.smithy.model.FromSourceLocation; +import software.amazon.smithy.model.node.ArrayNode; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.ObjectNode; +import software.amazon.smithy.rulesengine.language.RulesComponentBuilder; +import software.amazon.smithy.rulesengine.language.eval.type.Type; +import software.amazon.smithy.utils.BuilderRef; +import software.amazon.smithy.utils.SmithyBuilder; +import software.amazon.smithy.utils.StringUtils; + +public final class EndpointValue extends Value { + private final String url; + private final Map properties; + private final Map> headers; + + private EndpointValue(Builder builder) { + super(builder.getSourceLocation()); + this.url = SmithyBuilder.requiredState("url", builder.url); + this.properties = builder.properties.copy(); + this.headers = builder.headers.copy(); + } + + public static EndpointValue fromNode(Node node) { + Builder builder = new Builder(node); + ObjectNode on = node.expectObjectNode("endpoints are object nodes"); + on.expectNoAdditionalProperties(Arrays.asList("properties", "url", "headers")); + builder.url(on.expectStringMember("url").getValue()); + on.getObjectMember("properties").ifPresent(props -> { + props.getMembers().forEach((k, v) -> { + builder.putProperty(k.getValue(), Value.fromNode(v)); + }); + + }); + + on.getObjectMember("headers").ifPresent(headers -> headers.getMembers().forEach(((key, value) -> { + String name = key.getValue(); + value.expectArrayNode("Header values must be an array").getElements() + .forEach(e -> builder.addHeader(name, e.expectStringNode().getValue())); + }))); + return builder.build(); + } + + public static Builder builder() { + return new Builder(javaLocation()); + } + + @Override + public Node toNode() { + return ObjectNode.builder() + .withMember("url", url) + .withMember("properties", propertiesNode()) + .withMember("headers", headersNode()) + .build(); + } + + private Node propertiesNode() { + ObjectNode.Builder b = ObjectNode.builder(); + properties.forEach(b::withMember); + return b.build(); + } + + private Node headersNode() { + ObjectNode.Builder builder = ObjectNode.builder(); + + for (Map.Entry> entry : headers.entrySet()) { + builder.withMember(entry.getKey(), ArrayNode.fromStrings(entry.getValue())); + } + + return builder.build(); + } + + public Map getProperties() { + return properties; + } + + public String getUrl() { + return url; + } + + public Map> getHeaders() { + return headers; + } + + @Override + public Type getType() { + return Type.endpointType(); + } + + @Override + public EndpointValue expectEndpointValue() { + return this; + } + + @Override + public int hashCode() { + return Objects.hash(url, properties, headers); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + EndpointValue endpoint = (EndpointValue) o; + return url.equals(endpoint.url) + && properties.equals(endpoint.properties) + && headers.equals(endpoint.headers); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("url: ").append(url).append("\n"); + sb.append("properties:\n"); + sb.append(StringUtils.indent(properties.toString(), 2)); + for (Map.Entry> entry : headers.entrySet()) { + sb.append(StringUtils.indent(String.format("%s:%s", entry.getKey(), entry.getValue()), 2)); + } + return sb.toString(); + } + + public static final class Builder extends RulesComponentBuilder { + private final BuilderRef> properties = BuilderRef.forOrderedMap(); + private final BuilderRef>> headers = + BuilderRef.forOrderedMap(); + private String url; + + public Builder(FromSourceLocation sourceLocation) { + super(sourceLocation); + } + + public Builder url(String url) { + this.url = url; + return this; + } + + public Builder headers(Map> headers) { + this.headers.clear(); + this.headers.get().putAll(headers); + return this; + } + + public Builder addHeader(String name, String value) { + List values = this.headers.get().computeIfAbsent(name, (k) -> new ArrayList<>()); + values.add(value); + return this; + } + + public Builder properties(Map properties) { + this.properties.clear(); + this.properties.get().putAll(properties); + return this; + } + + public Builder putProperty(String value, Value fromNode) { + this.properties.get().put(value, fromNode); + return this; + } + + @Override + public EndpointValue build() { + return new EndpointValue(this); + } + } +} diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/value/IntegerValue.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/value/IntegerValue.java new file mode 100644 index 00000000000..3055c90beec --- /dev/null +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/value/IntegerValue.java @@ -0,0 +1,48 @@ +/* + * 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.eval.value; + +import software.amazon.smithy.model.SourceLocation; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.rulesengine.language.eval.type.Type; + +public final class IntegerValue extends Value { + private final int value; + + IntegerValue(int value) { + super(SourceLocation.none()); + this.value = value; + } + + @Override + public Type getType() { + return Type.integerType(); + } + + @Override + public IntegerValue expectIntegerValue() { + return this; + } + + public int getValue() { + return value; + } + + @Override + public Node toNode() { + return Node.from(value); + } +} diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/value/RecordValue.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/value/RecordValue.java new file mode 100644 index 00000000000..15eb47f9f1f --- /dev/null +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/value/RecordValue.java @@ -0,0 +1,95 @@ +/* + * 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.eval.value; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.function.BiConsumer; +import software.amazon.smithy.model.SourceLocation; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.ObjectNode; +import software.amazon.smithy.rulesengine.language.eval.type.RecordType; +import software.amazon.smithy.rulesengine.language.eval.type.Type; +import software.amazon.smithy.rulesengine.language.syntax.Identifier; + +public final class RecordValue extends Value { + private final Map value; + + RecordValue(Map value) { + super(SourceLocation.none()); + this.value = value; + } + + @Override + public Type getType() { + Map type = new HashMap<>(); + for (Map.Entry valueEntry : value.entrySet()) { + type.put(valueEntry.getKey(), valueEntry.getValue().getType()); + } + return new RecordType(type); + } + + @Override + public RecordValue expectRecordValue() { + return this; + } + + public Value get(String key) { + return get(Identifier.of(key)); + } + + public Value get(Identifier key) { + return this.value.get(key); + } + + public void forEach(BiConsumer fn) { + value.forEach(fn); + } + + @Override + public Node toNode() { + ObjectNode.Builder builder = ObjectNode.builder(); + value.forEach((k, v) -> builder.withMember(k.getName(), v)); + return builder.build(); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + RecordValue record = (RecordValue) o; + return Objects.equals(value, record.value); + } + + @Override + public String toString() { + return value.toString(); + } + + public Map getValue() { + return this.value; + } +} diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/value/StringValue.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/value/StringValue.java new file mode 100644 index 00000000000..b9b1c4ab7d5 --- /dev/null +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/value/StringValue.java @@ -0,0 +1,72 @@ +/* + * 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.eval.value; + +import java.util.Objects; +import software.amazon.smithy.model.SourceLocation; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.StringNode; +import software.amazon.smithy.rulesengine.language.eval.type.Type; + +public final class StringValue extends Value { + private final String value; + + StringValue(String value) { + super(SourceLocation.none()); + this.value = value; + } + + @Override + public Type getType() { + return Type.stringType(); + } + + @Override + public StringValue expectStringValue() { + return this; + } + + public String getValue() { + return value; + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + StringValue other = (StringValue) o; + return value.equals(other.value); + } + + @Override + public String toString() { + return value; + } + + @Override + public Node toNode() { + return StringNode.from(value); + } +} diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/value/Value.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/value/Value.java new file mode 100644 index 00000000000..cdcccc2958e --- /dev/null +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/eval/value/Value.java @@ -0,0 +1,156 @@ +/* + * 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.eval.value; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import software.amazon.smithy.model.FromSourceLocation; +import software.amazon.smithy.model.SourceLocation; +import software.amazon.smithy.model.node.ArrayNode; +import software.amazon.smithy.model.node.BooleanNode; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.NodeVisitor; +import software.amazon.smithy.model.node.NullNode; +import software.amazon.smithy.model.node.NumberNode; +import software.amazon.smithy.model.node.ObjectNode; +import software.amazon.smithy.model.node.StringNode; +import software.amazon.smithy.model.node.ToNode; +import software.amazon.smithy.rulesengine.language.eval.type.Type; +import software.amazon.smithy.rulesengine.language.syntax.Identifier; +import software.amazon.smithy.utils.SmithyUnstableApi; + +/** + * An abstract representing a typed value. + */ +@SmithyUnstableApi +public abstract class Value implements FromSourceLocation, ToNode { + private SourceLocation sourceLocation; + + public Value(SourceLocation sourceLocation) { + this.sourceLocation = sourceLocation; + } + + public static Value fromNode(Node source) { + Value value = source.accept(new NodeVisitor() { + @Override + public Value arrayNode(ArrayNode node) { + return new ArrayValue(node.getElementsAs(Value::fromNode)); + } + + @Override + public Value booleanNode(BooleanNode node) { + return booleanValue(node.getValue()); + } + + @Override + public Value nullNode(NullNode node) { + return emptyValue(); + } + + @Override + public Value numberNode(NumberNode node) { + if (!node.isNaturalNumber()) { + throw new RuntimeException("only integers >=0 are supported"); + } + int nodeValue = node.getValue().intValue(); + if (nodeValue < 0) { + throw new RuntimeException("only integers >=0 are supported"); + } + return Value.integerValue(nodeValue); + } + + @Override + public Value objectNode(ObjectNode node) { + Map out = new LinkedHashMap<>(); + node.getMembers().forEach((name, member) -> out.put(Identifier.of(name), Value.fromNode(member))); + return Value.recordValue(out); + } + + @Override + public Value stringNode(StringNode node) { + return Value.stringValue(node.getValue()); + } + }); + value.sourceLocation = source.getSourceLocation(); + return value; + } + + public abstract Type getType(); + + public boolean isEmpty() { + return false; + } + + @Override + public SourceLocation getSourceLocation() { + return Optional.ofNullable(sourceLocation).orElse(SourceLocation.none()); + } + + public static ArrayValue arrayValue(List value) { + return new ArrayValue(value); + } + + public static BooleanValue booleanValue(boolean value) { + return new BooleanValue(value); + } + + public static EmptyValue emptyValue() { + return new EmptyValue(); + } + + public static EndpointValue endpointValue(Node source) { + return EndpointValue.fromNode(source); + } + + public static IntegerValue integerValue(int value) { + return new IntegerValue(value); + } + + public static RecordValue recordValue(Map value) { + return new RecordValue(value); + } + + public static StringValue stringValue(String value) { + return new StringValue(value); + } + + public ArrayValue expectArrayValue() { + throw new RuntimeException("Expected array, found " + this); + } + + public BooleanValue expectBooleanValue() { + throw new RuntimeException("Expected bool but was: " + this); + } + + public EndpointValue expectEndpointValue() { + throw new RuntimeException("Expected endpoint, found " + this); + } + + public IntegerValue expectIntegerValue() { + throw new RuntimeException("Expected int, found " + this); + } + + public RecordValue expectRecordValue() { + throw new RuntimeException("Expected object but was: " + this); + } + + public StringValue expectStringValue() { + throw new RuntimeException("Expected string but was: " + this); + } + +} diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/impl/AwsArn.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/impl/AwsArn.java index 3d1cbb347d9..2173fede275 100644 --- a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/impl/AwsArn.java +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/impl/AwsArn.java @@ -19,7 +19,6 @@ import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.stream.Collectors; import software.amazon.smithy.utils.BuilderRef; import software.amazon.smithy.utils.SmithyBuilder; import software.amazon.smithy.utils.SmithyUnstableApi; @@ -70,8 +69,7 @@ public static Optional parse(String arn) { .service(base[2]) .region(base[3]) .accountId(base[4]) - .resource(Arrays.stream(base[5].split("[:/]", -1)) - .collect(Collectors.toList())) + .resource(Arrays.asList(base[5].split("[:/]", -1))) .build()); } @@ -79,23 +77,23 @@ public static Builder builder() { return new Builder(); } - public String partition() { + public String getPartition() { return partition; } - public String service() { + public String getService() { return service; } - public String region() { + public String getRegion() { return region; } - public String accountId() { + public String getAccountId() { return accountId; } - public List resource() { + public List getResource() { return resource; } @@ -120,7 +118,7 @@ public boolean equals(Object o) { @Override public String toString() { StringBuilder resource = new StringBuilder(); - this.resource().forEach(resource::append); + this.getResource().forEach(resource::append); return "Arn[" + "partition=" diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/model/Partition.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/model/Partition.java index 55eaa8e7640..de130cc9914 100644 --- a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/model/Partition.java +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/model/Partition.java @@ -23,7 +23,7 @@ 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.rulesengine.language.util.SourceLocationTrackingBuilder; +import software.amazon.smithy.rulesengine.language.RulesComponentBuilder; import software.amazon.smithy.utils.BuilderRef; import software.amazon.smithy.utils.SmithyBuilder; import software.amazon.smithy.utils.SmithyUnstableApi; @@ -77,15 +77,15 @@ public static Partition fromNode(Node node) { return b.build(); } - public String id() { + public String getId() { return id; } - public String regionRegex() { + public String getRegionRegex() { return regionRegex; } - public Map regions() { + public Map getRegions() { return regions; } @@ -142,7 +142,7 @@ private Node regionsNode() { return on.build(); } - public static class Builder extends SourceLocationTrackingBuilder { + public static class Builder extends RulesComponentBuilder { private String id; private String regionRegex; private final BuilderRef> regions = BuilderRef.forOrderedMap(); diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/model/PartitionOutputs.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/model/PartitionOutputs.java index 9500dd3e535..b97b2ea2382 100644 --- a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/model/PartitionOutputs.java +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/model/PartitionOutputs.java @@ -22,7 +22,7 @@ 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.rulesengine.language.util.SourceLocationTrackingBuilder; +import software.amazon.smithy.rulesengine.language.RulesComponentBuilder; import software.amazon.smithy.utils.SmithyBuilder; import software.amazon.smithy.utils.SmithyUnstableApi; import software.amazon.smithy.utils.ToSmithyBuilder; @@ -73,11 +73,11 @@ public static PartitionOutputs fromNode(Node node) { return b.build(); } - public String dnsSuffix() { + public String getDnsSuffix() { return dnsSuffix; } - public String dualStackDnsSuffix() { + public String getDualStackDnsSuffix() { return dualStackDnsSuffix; } @@ -132,7 +132,7 @@ public Node toNode() { .build(); } - public static class Builder extends SourceLocationTrackingBuilder { + public static class Builder extends RulesComponentBuilder { private String dnsSuffix; private String dualStackDnsSuffix; private boolean supportsFips; diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/model/Partitions.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/model/Partitions.java index a7b339204ae..3836c9faab4 100644 --- a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/model/Partitions.java +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/model/Partitions.java @@ -24,9 +24,8 @@ 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.rulesengine.language.util.SourceLocationTrackingBuilder; +import software.amazon.smithy.rulesengine.language.RulesComponentBuilder; import software.amazon.smithy.utils.BuilderRef; -import software.amazon.smithy.utils.SmithyBuilder; import software.amazon.smithy.utils.SmithyUnstableApi; import software.amazon.smithy.utils.ToSmithyBuilder; @@ -92,11 +91,11 @@ public String toString() { + '}'; } - public String version() { + public String getVersion() { return version; } - public List partitions() { + public List getPartitions() { return partitions; } @@ -106,7 +105,7 @@ public SourceLocation getSourceLocation() { } @Override - public SmithyBuilder toBuilder() { + public Builder toBuilder() { return new Builder(getSourceLocation()) .version(version) .partitions(partitions); @@ -126,7 +125,7 @@ private Node partitionsNode() { return node.build(); } - public static class Builder extends SourceLocationTrackingBuilder { + public static class Builder extends RulesComponentBuilder { private String version; private final BuilderRef> partitions = BuilderRef.forList(); diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/model/RegionOverride.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/model/RegionOverride.java index 701d90af08d..3876028c103 100644 --- a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/model/RegionOverride.java +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/model/RegionOverride.java @@ -19,7 +19,7 @@ import software.amazon.smithy.model.SourceLocation; import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.node.ToNode; -import software.amazon.smithy.rulesengine.language.util.SourceLocationTrackingBuilder; +import software.amazon.smithy.rulesengine.language.RulesComponentBuilder; import software.amazon.smithy.utils.SmithyBuilder; import software.amazon.smithy.utils.SmithyUnstableApi; import software.amazon.smithy.utils.ToSmithyBuilder; @@ -69,7 +69,7 @@ public Node toNode() { return Node.objectNode(); } - public static class Builder extends SourceLocationTrackingBuilder { + public static class Builder extends RulesComponentBuilder { public Builder(FromSourceLocation sourceLocation) { super(sourceLocation); diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/stdlib/AwsIsVirtualHostableS3Bucket.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/stdlib/AwsIsVirtualHostableS3Bucket.java index 8150893b00a..5075149a682 100644 --- a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/stdlib/AwsIsVirtualHostableS3Bucket.java +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/stdlib/AwsIsVirtualHostableS3Bucket.java @@ -17,19 +17,19 @@ 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.expr.Expression; -import software.amazon.smithy.rulesengine.language.syntax.fn.Function; -import software.amazon.smithy.rulesengine.language.syntax.fn.FunctionDefinition; -import software.amazon.smithy.rulesengine.language.syntax.fn.LibraryFunction; +import software.amazon.smithy.rulesengine.language.eval.type.Type; +import software.amazon.smithy.rulesengine.language.eval.value.Value; +import software.amazon.smithy.rulesengine.language.syntax.expressions.Expression; +import software.amazon.smithy.rulesengine.language.syntax.functions.Function; +import software.amazon.smithy.rulesengine.language.syntax.functions.FunctionDefinition; +import software.amazon.smithy.rulesengine.language.syntax.functions.LibraryFunction; import software.amazon.smithy.utils.SmithyUnstableApi; /** * An AWS rule-set function for determining whether a given string can be promoted to an S3 virtual bucket host label. */ @SmithyUnstableApi -public class AwsIsVirtualHostableS3Bucket extends FunctionDefinition { +public class AwsIsVirtualHostableS3Bucket implements FunctionDefinition { public static final String ID = "aws.isVirtualHostableS3Bucket"; @Override @@ -39,26 +39,26 @@ public String getId() { @Override public List getArguments() { - return Arrays.asList(Type.string(), Type.bool()); + return Arrays.asList(Type.stringType(), Type.booleanType()); } @Override public Type getReturnType() { - return Type.bool(); + return Type.booleanType(); } @Override public Value evaluate(List arguments) { - String hostLabel = arguments.get(0).expectString(); - boolean allowDots = arguments.get(1).expectBool(); + String hostLabel = arguments.get(0).expectStringValue().getValue(); + boolean allowDots = arguments.get(1).expectBooleanValue().getValue(); if (allowDots) { - return Value.bool( + return Value.booleanValue( hostLabel.matches("[a-z\\d][a-z\\d\\-.]{1,61}[a-z\\d]") && !hostLabel.matches("(\\d+\\.){3}\\d+") // don't allow ip address && !hostLabel.matches(".*[.-]{2}.*") // don't allow names like bucket-.name or bucket.-name ); } else { - return Value.bool(hostLabel.matches("[a-z\\d][a-z\\d\\-]{1,61}[a-z\\d]")); + return Value.booleanValue(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/stdlib/AwsPartition.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/stdlib/AwsPartition.java index ade1bc27892..fe9d974eb25 100644 --- a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/stdlib/AwsPartition.java +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/stdlib/AwsPartition.java @@ -24,18 +24,19 @@ import java.util.Map; import java.util.ServiceLoader; import java.util.regex.Pattern; -import software.amazon.smithy.rulesengine.language.eval.Type; -import software.amazon.smithy.rulesengine.language.eval.Value; +import software.amazon.smithy.rulesengine.language.eval.type.RecordType; +import software.amazon.smithy.rulesengine.language.eval.type.Type; +import software.amazon.smithy.rulesengine.language.eval.value.Value; +import software.amazon.smithy.rulesengine.language.model.Partition; import software.amazon.smithy.rulesengine.language.model.PartitionOutputs; import software.amazon.smithy.rulesengine.language.model.Partitions; import software.amazon.smithy.rulesengine.language.stdlib.partition.PartitionDataProvider; import software.amazon.smithy.rulesengine.language.syntax.Identifier; -import software.amazon.smithy.rulesengine.language.syntax.expr.Expression; -import software.amazon.smithy.rulesengine.language.syntax.fn.Function; -import software.amazon.smithy.rulesengine.language.syntax.fn.FunctionDefinition; -import software.amazon.smithy.rulesengine.language.syntax.fn.FunctionNode; -import software.amazon.smithy.rulesengine.language.syntax.fn.LibraryFunction; -import software.amazon.smithy.rulesengine.language.util.LazyValue; +import software.amazon.smithy.rulesengine.language.syntax.expressions.Expression; +import software.amazon.smithy.rulesengine.language.syntax.functions.Function; +import software.amazon.smithy.rulesengine.language.syntax.functions.FunctionDefinition; +import software.amazon.smithy.rulesengine.language.syntax.functions.FunctionNode; +import software.amazon.smithy.rulesengine.language.syntax.functions.LibraryFunction; import software.amazon.smithy.utils.MapUtils; import software.amazon.smithy.utils.SmithyUnstableApi; @@ -43,7 +44,7 @@ * An AWS rule-set function for mapping a region string to a partition. */ @SmithyUnstableApi -public final class AwsPartition extends FunctionDefinition { +public final class AwsPartition implements FunctionDefinition { public static final String ID = "aws.partition"; public static final Identifier NAME = Identifier.of("name"); @@ -53,9 +54,7 @@ public final class AwsPartition extends FunctionDefinition { public static final Identifier SUPPORTS_DUAL_STACK = Identifier.of("supportsDualStack"); public static final Identifier INFERRED = Identifier.of("inferred"); - private final LazyValue partitionData = LazyValue.builder() - .initializer(this::loadPartitionData) - .build(); + private static final PartitionData PARTITION_DATA = loadPartitionData(); @Override public String getId() { @@ -64,35 +63,32 @@ public String getId() { @Override public List getArguments() { - return Collections.singletonList(Type.string()); + return Collections.singletonList(Type.stringType()); } @Override public Type getReturnType() { - LinkedHashMap type = new LinkedHashMap<>(); - type.put(NAME, Type.string()); - type.put(DNS_SUFFIX, Type.string()); - type.put(DUAL_STACK_DNS_SUFFIX, Type.string()); - type.put(SUPPORTS_DUAL_STACK, Type.bool()); - type.put(SUPPORTS_FIPS, Type.bool()); - return Type.optional(new Type.Record(type)); + Map type = new LinkedHashMap<>(); + type.put(NAME, Type.stringType()); + type.put(DNS_SUFFIX, Type.stringType()); + type.put(DUAL_STACK_DNS_SUFFIX, Type.stringType()); + type.put(SUPPORTS_DUAL_STACK, Type.booleanType()); + type.put(SUPPORTS_FIPS, Type.booleanType()); + return Type.optionalType(new RecordType(type)); } @Override public Value evaluate(List arguments) { - String regionName = arguments.get(0).expectString(); - - final PartitionData data = partitionData.value(); - - software.amazon.smithy.rulesengine.language.model.Partition matchedPartition; + String regionName = arguments.get(0).expectStringValue().getValue(); + Partition matchedPartition; boolean inferred = false; // Known region - matchedPartition = data.regionMap.get(regionName); + matchedPartition = PARTITION_DATA.regionMap.get(regionName); if (matchedPartition == null) { - // try matching on region name pattern - for (software.amazon.smithy.rulesengine.language.model.Partition p : data.partitions) { - Pattern regex = Pattern.compile(p.regionRegex()); + // Try matching on region name pattern + for (Partition p : PARTITION_DATA.partitions) { + Pattern regex = Pattern.compile(p.getRegionRegex()); if (regex.matcher(regionName).matches()) { matchedPartition = p; inferred = true; @@ -102,17 +98,27 @@ public Value evaluate(List arguments) { } if (matchedPartition == null) { - matchedPartition = data.partitions.stream().filter(p -> p.id().equals("aws")).findFirst().get(); + for (Partition partition : PARTITION_DATA.partitions) { + if (partition.getId().equals("aws")) { + matchedPartition = partition; + break; + } + } + } + + if (matchedPartition == null) { + // TODO + throw new RuntimeException("Unable to match a partition for region " + regionName); } PartitionOutputs matchedPartitionOutputs = matchedPartition.getOutputs(); - return Value.record(MapUtils.of( - NAME, Value.string(matchedPartition.id()), - DNS_SUFFIX, Value.string(matchedPartitionOutputs.dnsSuffix()), - DUAL_STACK_DNS_SUFFIX, Value.string(matchedPartitionOutputs.dualStackDnsSuffix()), - SUPPORTS_FIPS, Value.bool(matchedPartitionOutputs.supportsFips()), - SUPPORTS_DUAL_STACK, Value.bool(matchedPartitionOutputs.supportsDualStack()), - INFERRED, Value.bool(inferred))); + return Value.recordValue(MapUtils.of( + NAME, Value.stringValue(matchedPartition.getId()), + DNS_SUFFIX, Value.stringValue(matchedPartitionOutputs.getDnsSuffix()), + DUAL_STACK_DNS_SUFFIX, Value.stringValue(matchedPartitionOutputs.getDualStackDnsSuffix()), + SUPPORTS_FIPS, Value.booleanValue(matchedPartitionOutputs.supportsFips()), + SUPPORTS_DUAL_STACK, Value.booleanValue(matchedPartitionOutputs.supportsDualStack()), + INFERRED, Value.booleanValue(inferred))); } /** @@ -125,7 +131,7 @@ public static Function ofExpression(Expression expression) { return new LibraryFunction(new AwsPartition(), FunctionNode.ofExpressions(ID, expression)); } - private PartitionData loadPartitionData() { + private static PartitionData loadPartitionData() { Iterator iter = ServiceLoader.load(PartitionDataProvider.class).iterator(); if (!iter.hasNext()) { throw new RuntimeException("Unable to locate partition data"); @@ -137,9 +143,9 @@ private PartitionData loadPartitionData() { PartitionData partitionData = new PartitionData(); - partitions.partitions().forEach(part -> { + partitions.getPartitions().forEach(part -> { partitionData.partitions.add(part); - part.regions().forEach((name, override) -> { + part.getRegions().forEach((name, override) -> { partitionData.regionMap.put(name, part); }); }); @@ -148,8 +154,7 @@ private PartitionData loadPartitionData() { } private static class PartitionData { - private final List partitions = new ArrayList<>(); - private final Map regionMap = - new HashMap<>(); + private final List partitions = new ArrayList<>(); + private final Map regionMap = new HashMap<>(); } } diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/stdlib/BooleanEquals.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/stdlib/BooleanEquals.java index 7cc3d15cb24..d3ccf41ef9f 100644 --- a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/stdlib/BooleanEquals.java +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/stdlib/BooleanEquals.java @@ -19,13 +19,13 @@ import java.util.List; import software.amazon.smithy.rulesengine.language.error.InnerParseError; import software.amazon.smithy.rulesengine.language.eval.Scope; -import software.amazon.smithy.rulesengine.language.eval.Type; -import software.amazon.smithy.rulesengine.language.eval.Value; -import software.amazon.smithy.rulesengine.language.syntax.expr.Expression; -import software.amazon.smithy.rulesengine.language.syntax.fn.Function; -import software.amazon.smithy.rulesengine.language.syntax.fn.FunctionDefinition; -import software.amazon.smithy.rulesengine.language.syntax.fn.FunctionNode; -import software.amazon.smithy.rulesengine.language.syntax.fn.LibraryFunction; +import software.amazon.smithy.rulesengine.language.eval.type.Type; +import software.amazon.smithy.rulesengine.language.eval.value.Value; +import software.amazon.smithy.rulesengine.language.syntax.expressions.Expression; +import software.amazon.smithy.rulesengine.language.syntax.functions.Function; +import software.amazon.smithy.rulesengine.language.syntax.functions.FunctionDefinition; +import software.amazon.smithy.rulesengine.language.syntax.functions.FunctionNode; +import software.amazon.smithy.rulesengine.language.syntax.functions.LibraryFunction; import software.amazon.smithy.rulesengine.language.visit.ExpressionVisitor; import software.amazon.smithy.utils.SmithyUnstableApi; @@ -63,7 +63,7 @@ protected Type typeCheckLocal(Scope scope) throws InnerParseError { return DEFINITION.getReturnType(); } - static class Definition extends FunctionDefinition { + public static class Definition implements FunctionDefinition { public static final String ID = BooleanEquals.ID; @Override @@ -73,17 +73,18 @@ public String getId() { @Override public List getArguments() { - return Arrays.asList(Type.bool(), Type.bool()); + return Arrays.asList(Type.booleanType(), Type.booleanType()); } @Override public Type getReturnType() { - return Type.bool(); + return Type.booleanType(); } @Override public Value evaluate(List arguments) { - return Value.bool(arguments.get(0).expectBool() == arguments.get(1).expectBool()); + return Value.booleanValue(arguments.get(0).expectBooleanValue().getValue() + == arguments.get(1).expectBooleanValue().getValue()); } } } diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/stdlib/IsValidHostLabel.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/stdlib/IsValidHostLabel.java index 21b3b291667..4e90232af3b 100644 --- a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/stdlib/IsValidHostLabel.java +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/stdlib/IsValidHostLabel.java @@ -17,19 +17,19 @@ 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.expr.Expression; -import software.amazon.smithy.rulesengine.language.syntax.fn.Function; -import software.amazon.smithy.rulesengine.language.syntax.fn.FunctionDefinition; -import software.amazon.smithy.rulesengine.language.syntax.fn.LibraryFunction; +import software.amazon.smithy.rulesengine.language.eval.type.Type; +import software.amazon.smithy.rulesengine.language.eval.value.Value; +import software.amazon.smithy.rulesengine.language.syntax.expressions.Expression; +import software.amazon.smithy.rulesengine.language.syntax.functions.Function; +import software.amazon.smithy.rulesengine.language.syntax.functions.FunctionDefinition; +import software.amazon.smithy.rulesengine.language.syntax.functions.LibraryFunction; import software.amazon.smithy.utils.SmithyUnstableApi; /** * A rule-engine function for checking whether a string is a valid DNS host label. */ @SmithyUnstableApi -public final class IsValidHostLabel extends FunctionDefinition { +public final class IsValidHostLabel implements FunctionDefinition { public static final String ID = "isValidHostLabel"; @Override @@ -39,22 +39,22 @@ public String getId() { @Override public List getArguments() { - return Arrays.asList(Type.string(), Type.bool()); + return Arrays.asList(Type.stringType(), Type.booleanType()); } @Override public Type getReturnType() { - return Type.bool(); + return Type.booleanType(); } @Override public Value evaluate(List arguments) { - String hostLabel = arguments.get(0).expectString(); - boolean allowDots = arguments.get(1).expectBool(); + String hostLabel = arguments.get(0).expectStringValue().getValue(); + boolean allowDots = arguments.get(1).expectBooleanValue().getValue(); if (allowDots) { - return Value.bool(hostLabel.matches("[a-zA-Z\\d][a-zA-Z\\d\\-.]{0,62}")); + return Value.booleanValue(hostLabel.matches("[a-zA-Z\\d][a-zA-Z\\d\\-.]{0,62}")); } else { - return Value.bool(hostLabel.matches("[a-zA-Z\\d][a-zA-Z\\d\\-]{0,62}")); + return Value.booleanValue(hostLabel.matches("[a-zA-Z\\d][a-zA-Z\\d\\-]{0,62}")); } } diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/stdlib/ParseArn.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/stdlib/ParseArn.java index 209cdd21e37..ef25b193942 100644 --- a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/stdlib/ParseArn.java +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/stdlib/ParseArn.java @@ -15,18 +15,19 @@ package software.amazon.smithy.rulesengine.language.stdlib; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; -import software.amazon.smithy.rulesengine.language.eval.Type; -import software.amazon.smithy.rulesengine.language.eval.Value; +import software.amazon.smithy.rulesengine.language.eval.type.RecordType; +import software.amazon.smithy.rulesengine.language.eval.type.Type; +import software.amazon.smithy.rulesengine.language.eval.value.Value; import software.amazon.smithy.rulesengine.language.impl.AwsArn; import software.amazon.smithy.rulesengine.language.syntax.Identifier; -import software.amazon.smithy.rulesengine.language.syntax.expr.Expression; -import software.amazon.smithy.rulesengine.language.syntax.fn.Function; -import software.amazon.smithy.rulesengine.language.syntax.fn.FunctionDefinition; -import software.amazon.smithy.rulesengine.language.syntax.fn.LibraryFunction; +import software.amazon.smithy.rulesengine.language.syntax.expressions.Expression; +import software.amazon.smithy.rulesengine.language.syntax.functions.Function; +import software.amazon.smithy.rulesengine.language.syntax.functions.FunctionDefinition; +import software.amazon.smithy.rulesengine.language.syntax.functions.LibraryFunction; import software.amazon.smithy.utils.MapUtils; import software.amazon.smithy.utils.SmithyUnstableApi; @@ -34,7 +35,7 @@ * An aws rule-set function for parsing an AWS ARN into it's componenet parts. */ @SmithyUnstableApi -public final class ParseArn extends FunctionDefinition { +public final class ParseArn implements FunctionDefinition { public static final String ID = "aws.parseArn"; public static final Identifier PARTITION = Identifier.of("partition"); public static final Identifier SERVICE = Identifier.of("service"); @@ -50,35 +51,39 @@ public String getId() { @Override public List getArguments() { - return Collections.singletonList(Type.string()); + return Collections.singletonList(Type.stringType()); } @Override public Type getReturnType() { - return Type.optional(new Type.Record(MapUtils.of( - PARTITION, Type.string(), - SERVICE, Type.string(), - REGION, Type.string(), - ACCOUNT_ID, Type.string(), - RESOURCE_ID, Type.array(Type.string()) + return Type.optionalType(new RecordType(MapUtils.of( + PARTITION, Type.stringType(), + SERVICE, Type.stringType(), + REGION, Type.stringType(), + ACCOUNT_ID, Type.stringType(), + RESOURCE_ID, Type.arrayType(Type.stringType()) ))); } @Override public Value evaluate(List arguments) { - String value = arguments.get(0).expectString(); + String value = arguments.get(0).expectStringValue().getValue(); Optional arnOpt = AwsArn.parse(value); - return arnOpt.map(awsArn -> - (Value) Value.record(MapUtils.of( - PARTITION, Value.string(awsArn.partition()), - SERVICE, Value.string(awsArn.service()), - REGION, Value.string(awsArn.region()), - ACCOUNT_ID, Value.string(awsArn.accountId()), - RESOURCE_ID, Value.array(awsArn.resource().stream() - .map(v -> (Value) Value.string(v)) - .collect(Collectors.toList())) - )) - ).orElse(new Value.None()); + if (!arnOpt.isPresent()) { + return Value.emptyValue(); + } + + AwsArn awsArn = arnOpt.get(); + List resourceId = new ArrayList<>(); + for (String resourceIdPart : awsArn.getResource()) { + resourceId.add(Value.stringValue(resourceIdPart)); + } + return Value.recordValue(MapUtils.of( + PARTITION, Value.stringValue(awsArn.getPartition()), + SERVICE, Value.stringValue(awsArn.getService()), + REGION, Value.stringValue(awsArn.getRegion()), + ACCOUNT_ID, Value.stringValue(awsArn.getAccountId()), + RESOURCE_ID, Value.arrayValue(resourceId))); } public static Function ofExpression(Expression expression) { diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/stdlib/ParseUrl.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/stdlib/ParseUrl.java index b4f39dce4a5..e41729fb824 100644 --- a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/stdlib/ParseUrl.java +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/stdlib/ParseUrl.java @@ -17,16 +17,15 @@ import java.net.MalformedURLException; import java.net.URL; -import java.util.Arrays; import java.util.Collections; 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.eval.type.Type; +import software.amazon.smithy.rulesengine.language.eval.value.Value; import software.amazon.smithy.rulesengine.language.syntax.Identifier; -import software.amazon.smithy.rulesengine.language.syntax.expr.Expression; -import software.amazon.smithy.rulesengine.language.syntax.fn.Function; -import software.amazon.smithy.rulesengine.language.syntax.fn.FunctionDefinition; -import software.amazon.smithy.rulesengine.language.syntax.fn.LibraryFunction; +import software.amazon.smithy.rulesengine.language.syntax.expressions.Expression; +import software.amazon.smithy.rulesengine.language.syntax.functions.Function; +import software.amazon.smithy.rulesengine.language.syntax.functions.FunctionDefinition; +import software.amazon.smithy.rulesengine.language.syntax.functions.LibraryFunction; import software.amazon.smithy.utils.MapUtils; import software.amazon.smithy.utils.SmithyUnstableApi; import software.amazon.smithy.utils.StringUtils; @@ -35,7 +34,7 @@ * A rule-set function to parse a URI from a string. */ @SmithyUnstableApi -public class ParseUrl extends FunctionDefinition { +public class ParseUrl implements FunctionDefinition { public static final String ID = "parseURL"; public static final Identifier SCHEME = Identifier.of("scheme"); public static final Identifier AUTHORITY = Identifier.of("authority"); @@ -51,7 +50,7 @@ public String getId() { @Override public List getArguments() { - return Collections.singletonList(Type.string()); + return Collections.singletonList(Type.stringType()); } public static Function ofExpression(Expression expression) { @@ -60,28 +59,28 @@ public static Function ofExpression(Expression expression) { @Override public Type getReturnType() { - return Type.optional(Type.record( + return Type.optionalType(Type.recordType( MapUtils.of( - SCHEME, Type.string(), - AUTHORITY, Type.string(), - PATH, Type.string(), - NORMALIZED_PATH, Type.string(), - IS_IP, Type.bool() + SCHEME, Type.stringType(), + AUTHORITY, Type.stringType(), + PATH, Type.stringType(), + NORMALIZED_PATH, Type.stringType(), + IS_IP, Type.booleanType() ) )); } @Override public Value evaluate(List arguments) { - String url = arguments.get(0).expectString(); + String url = arguments.get(0).expectStringValue().getValue(); try { URL parsed = new URL(url); - String path = parsed.getPath(); if (parsed.getQuery() != null) { System.out.println("empty query not supported"); - return Value.none(); + return Value.emptyValue(); } + boolean isIpAddr = false; String host = parsed.getHost(); if (host.startsWith("[") && host.endsWith("]")) { @@ -89,17 +88,20 @@ public Value evaluate(List arguments) { } String[] dottedParts = host.split("\\."); if (dottedParts.length == 4) { - if (Arrays.stream(dottedParts).allMatch(part -> { + isIpAddr = true; + for (String dottedPart : dottedParts) { try { - int value = Integer.parseInt(part); - return value >= 0 && value <= 255; + int value = Integer.parseInt(dottedPart); + if (value < 0 || value > 255) { + isIpAddr = false; + } } catch (NumberFormatException ex) { - return false; + isIpAddr = false; } - })) { - isIpAddr = true; } } + + String path = parsed.getPath(); String normalizedPath; if (StringUtils.isBlank(path)) { normalizedPath = "/"; @@ -114,16 +116,18 @@ public Value evaluate(List arguments) { } normalizedPath = builder.toString(); } - return Value.record(MapUtils.of( - SCHEME, Value.string(parsed.getProtocol()), - AUTHORITY, Value.string(parsed.getAuthority()), - PATH, Value.string(path), - NORMALIZED_PATH, Value.string(normalizedPath.toString()), - IS_IP, Value.bool(isIpAddr) + + return Value.recordValue(MapUtils.of( + SCHEME, Value.stringValue(parsed.getProtocol()), + AUTHORITY, Value.stringValue(parsed.getAuthority()), + PATH, Value.stringValue(path), + NORMALIZED_PATH, Value.stringValue(normalizedPath), + IS_IP, Value.booleanValue(isIpAddr) )); } catch (MalformedURLException e) { + // TODO Search for more of these System.out.printf("invalid URL: %s%n", e); - return Value.none(); + return Value.emptyValue(); } } } diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/stdlib/StringEquals.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/stdlib/StringEquals.java index 95bf6f7de13..57f004406a7 100644 --- a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/stdlib/StringEquals.java +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/stdlib/StringEquals.java @@ -19,13 +19,13 @@ import java.util.List; import software.amazon.smithy.rulesengine.language.error.InnerParseError; import software.amazon.smithy.rulesengine.language.eval.Scope; -import software.amazon.smithy.rulesengine.language.eval.Type; -import software.amazon.smithy.rulesengine.language.eval.Value; -import software.amazon.smithy.rulesengine.language.syntax.expr.Expression; -import software.amazon.smithy.rulesengine.language.syntax.fn.Function; -import software.amazon.smithy.rulesengine.language.syntax.fn.FunctionDefinition; -import software.amazon.smithy.rulesengine.language.syntax.fn.FunctionNode; -import software.amazon.smithy.rulesengine.language.syntax.fn.LibraryFunction; +import software.amazon.smithy.rulesengine.language.eval.type.Type; +import software.amazon.smithy.rulesengine.language.eval.value.Value; +import software.amazon.smithy.rulesengine.language.syntax.expressions.Expression; +import software.amazon.smithy.rulesengine.language.syntax.functions.Function; +import software.amazon.smithy.rulesengine.language.syntax.functions.FunctionDefinition; +import software.amazon.smithy.rulesengine.language.syntax.functions.FunctionNode; +import software.amazon.smithy.rulesengine.language.syntax.functions.LibraryFunction; import software.amazon.smithy.rulesengine.language.visit.ExpressionVisitor; import software.amazon.smithy.utils.SmithyUnstableApi; @@ -63,7 +63,7 @@ protected Type typeCheckLocal(Scope scope) throws InnerParseError { return DEFINITION.getReturnType(); } - private static class Definition extends FunctionDefinition { + public static class Definition implements FunctionDefinition { public static final String ID = StringEquals.ID; @Override @@ -73,17 +73,18 @@ public String getId() { @Override public List getArguments() { - return Arrays.asList(Type.string(), Type.string()); + return Arrays.asList(Type.stringType(), Type.stringType()); } @Override public Type getReturnType() { - return Type.bool(); + return Type.booleanType(); } @Override public Value evaluate(List arguments) { - return Value.bool(arguments.get(0).expectString().equals(arguments.get(1).expectString())); + return Value.booleanValue(arguments.get(0).expectStringValue().getValue() + .equals(arguments.get(1).expectStringValue().getValue())); } } } diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/stdlib/Substring.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/stdlib/Substring.java index 275a1a0b2b5..b097bb4f472 100644 --- a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/stdlib/Substring.java +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/stdlib/Substring.java @@ -17,19 +17,19 @@ 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.expr.Expression; -import software.amazon.smithy.rulesengine.language.syntax.fn.Function; -import software.amazon.smithy.rulesengine.language.syntax.fn.FunctionDefinition; -import software.amazon.smithy.rulesengine.language.syntax.fn.LibraryFunction; +import software.amazon.smithy.rulesengine.language.eval.type.Type; +import software.amazon.smithy.rulesengine.language.eval.value.Value; +import software.amazon.smithy.rulesengine.language.syntax.expressions.Expression; +import software.amazon.smithy.rulesengine.language.syntax.functions.Function; +import software.amazon.smithy.rulesengine.language.syntax.functions.FunctionDefinition; +import software.amazon.smithy.rulesengine.language.syntax.functions.LibraryFunction; import software.amazon.smithy.utils.SmithyUnstableApi; /** * A rule-set function for getting the substring of a string value. */ @SmithyUnstableApi -public final class Substring extends FunctionDefinition { +public final class Substring implements FunctionDefinition { public static final String ID = "substring"; @Override @@ -39,38 +39,38 @@ public String getId() { @Override public List getArguments() { - return Arrays.asList(Type.string(), Type.integer(), Type.integer(), Type.bool()); + return Arrays.asList(Type.stringType(), Type.integerType(), Type.integerType(), Type.booleanType()); } @Override public Type getReturnType() { - return Type.optional(Type.string()); + return Type.optionalType(Type.stringType()); } @Override public Value evaluate(List arguments) { - String str = arguments.get(0).expectString(); - int startIndex = arguments.get(1).expectInteger(); - int stopIndex = arguments.get(2).expectInteger(); - boolean reverse = arguments.get(3).expectBool(); + String str = arguments.get(0).expectStringValue().getValue(); + int startIndex = arguments.get(1).expectIntegerValue().getValue(); + int stopIndex = arguments.get(2).expectIntegerValue().getValue(); + boolean reverse = arguments.get(3).expectBooleanValue().getValue(); for (int i = 0; i < str.length(); i++) { char ch = str.charAt(i); if (!(ch <= 127)) { - return Value.none(); + return Value.emptyValue(); } } if (startIndex >= stopIndex || str.length() < stopIndex) { - return new Value.None(); + return Value.emptyValue(); } if (!reverse) { - return Value.string(str.substring(startIndex, stopIndex)); + return Value.stringValue(str.substring(startIndex, stopIndex)); } else { int revStart = str.length() - stopIndex; int revStop = str.length() - startIndex; - return Value.string(str.substring(revStart, revStop)); + return Value.stringValue(str.substring(revStart, revStop)); } } diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/stdlib/UriEncode.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/stdlib/UriEncode.java index 1df44868e42..0e6a656cc30 100644 --- a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/stdlib/UriEncode.java +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/stdlib/UriEncode.java @@ -19,24 +19,28 @@ import java.net.URLEncoder; import java.util.Collections; 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.expr.Expression; -import software.amazon.smithy.rulesengine.language.syntax.fn.Function; -import software.amazon.smithy.rulesengine.language.syntax.fn.FunctionDefinition; -import software.amazon.smithy.rulesengine.language.syntax.fn.LibraryFunction; +import java.util.Map; +import software.amazon.smithy.rulesengine.language.eval.type.Type; +import software.amazon.smithy.rulesengine.language.eval.value.Value; +import software.amazon.smithy.rulesengine.language.syntax.expressions.Expression; +import software.amazon.smithy.rulesengine.language.syntax.functions.Function; +import software.amazon.smithy.rulesengine.language.syntax.functions.FunctionDefinition; +import software.amazon.smithy.rulesengine.language.syntax.functions.LibraryFunction; +import software.amazon.smithy.utils.MapUtils; import software.amazon.smithy.utils.SmithyUnstableApi; /** * A rule-set function for URI encoding a string. */ @SmithyUnstableApi -public final class UriEncode extends FunctionDefinition { - +public final class UriEncode implements FunctionDefinition { public static final String ID = "uriEncode"; - private static final String[] ENCODED_CHARACTERS = new String[]{"+", "*", "%7E"}; - private static final String[] ENCODED_CHARACTERS_REPLACEMENTS = new String[]{"%20", "%2A", "~"}; + private static final Map ENCODING_REPLACEMENTS = MapUtils.of( + "+", "%20", + "*", "%2A", + "%7E", "~" + ); @Override public String getId() { @@ -45,23 +49,23 @@ public String getId() { @Override public List getArguments() { - return Collections.singletonList(Type.string()); + return Collections.singletonList(Type.stringType()); } @Override public Type getReturnType() { - return Type.string(); + return Type.stringType(); } @Override public Value evaluate(List arguments) { - String url = arguments.get(0).expectString(); + String url = arguments.get(0).expectStringValue().getValue(); try { String encoded = URLEncoder.encode(url, "UTF-8"); - for (int i = 0; i < ENCODED_CHARACTERS.length; i++) { - encoded = encoded.replace(ENCODED_CHARACTERS[i], ENCODED_CHARACTERS_REPLACEMENTS[i]); + for (Map.Entry entry : ENCODING_REPLACEMENTS.entrySet()) { + encoded = encoded.replace(entry.getKey(), entry.getValue()); } - return Value.string(encoded); + return Value.stringValue(encoded); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/syntax/Identifier.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/syntax/Identifier.java index bc8769309db..14d5661fbc3 100644 --- a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/syntax/Identifier.java +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/syntax/Identifier.java @@ -15,25 +15,28 @@ package software.amazon.smithy.rulesengine.language.syntax; +import static software.amazon.smithy.rulesengine.language.RulesComponentBuilder.javaLocation; + 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.StringNode; import software.amazon.smithy.model.node.ToNode; -import software.amazon.smithy.rulesengine.language.util.MandatorySourceLocation; -import software.amazon.smithy.rulesengine.language.util.SourceLocationUtils; import software.amazon.smithy.utils.SmithyUnstableApi; @SmithyUnstableApi -public final class Identifier extends MandatorySourceLocation implements ToNode { +public final class Identifier implements FromSourceLocation, ToNode { private final StringNode name; + private final SourceLocation sourceLocation; - Identifier(StringNode name) { - super(name); + private Identifier(StringNode name) { this.name = name; + sourceLocation = name.getSourceLocation(); } public static Identifier of(String name) { - return new Identifier(new StringNode(name, SourceLocationUtils.javaLocation())); + return new Identifier(new StringNode(name, javaLocation())); } public static Identifier of(StringNode name) { @@ -44,6 +47,11 @@ public StringNode getName() { return name; } + @Override + public SourceLocation getSourceLocation() { + return sourceLocation; + } + @Override public int hashCode() { return Objects.hash(name); diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/syntax/expr/Literal.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/syntax/expr/Literal.java deleted file mode 100644 index 9c44605056e..00000000000 --- a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/syntax/expr/Literal.java +++ /dev/null @@ -1,678 +0,0 @@ -/* - * 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.syntax.expr; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.stream.Collectors; -import software.amazon.smithy.model.FromSourceLocation; -import software.amazon.smithy.model.SourceException; -import software.amazon.smithy.model.SourceLocation; -import software.amazon.smithy.model.node.ArrayNode; -import software.amazon.smithy.model.node.BooleanNode; -import software.amazon.smithy.model.node.Node; -import software.amazon.smithy.model.node.NodeVisitor; -import software.amazon.smithy.model.node.NullNode; -import software.amazon.smithy.model.node.NumberNode; -import software.amazon.smithy.model.node.ObjectNode; -import software.amazon.smithy.model.node.StringNode; -import software.amazon.smithy.rulesengine.language.error.RuleError; -import software.amazon.smithy.rulesengine.language.eval.RuleEvaluator; -import software.amazon.smithy.rulesengine.language.eval.Scope; -import software.amazon.smithy.rulesengine.language.eval.Type; -import software.amazon.smithy.rulesengine.language.eval.Value; -import software.amazon.smithy.rulesengine.language.syntax.Identifier; -import software.amazon.smithy.rulesengine.language.util.SourceLocationUtils; -import software.amazon.smithy.rulesengine.language.visit.ExpressionVisitor; -import software.amazon.smithy.rulesengine.language.visit.TemplateVisitor; -import software.amazon.smithy.utils.SmithyUnstableApi; - -/** - * Literals allow rules and properties to define arbitrarily nested JSON structures (e.g.for properties) - *

- * They support template strings, but _do not_ support template objects since that creates ambiguity. {@link Template}s - * are a basic example of literals–literal strings. Literals can also be booleans, objects, integers or tuples. - */ -@SmithyUnstableApi -public final class Literal extends Expression { - - private final ILiteral source; - - private Literal(ILiteral source, FromSourceLocation sourceLocation) { - super(sourceLocation.getSourceLocation()); - this.source = source; - } - - /** - * Constructs a tuple literal of values. - * - * @param values the values. - * @return the tuple literal. - */ - public static Literal tuple(List values) { - return new Literal(new Tuple(values), SourceLocationUtils.javaLocation()); - } - - /** - * Constructs a record literal of values. - * - * @param record a map of values to be converted to a record. - * @return the record literal. - */ - public static Literal record(Map record) { - return new Literal(new Record(record), SourceLocationUtils.javaLocation()); - } - - /** - * Constructs a string literal from a {@link Template} value. - * - * @param value the template value. - * @return the string literal. - */ - public static Literal string(Template value) { - return new Literal(new String(value), SourceLocationUtils.javaLocation()); - } - - /** - * Constructs an integer literal from an integer value. - * - * @param value the integer value. - * @return the integer literal. - */ - public static Literal integer(int value) { - return new Literal(new Integer(Node.from(value)), SourceLocationUtils.javaLocation()); - } - - /** - * Constructs a bool literal from a boolean value. - * - * @param value the boolean value. - * @return the bool literal. - */ - public static Literal bool(boolean value) { - return new Literal(new Bool(Node.from(value)), SourceLocationUtils.javaLocation()); - } - - /** - * Constructs a literal from a {@link Node} based on the Node's type. - * - * @param node a node to construct as a literal. - * @return the literal representation of the node. - */ - public static Literal fromNode(Node node) { - ILiteral iLiteral = node.accept(new NodeVisitor() { - @Override - public ILiteral arrayNode(ArrayNode arrayNode) { - return new Tuple(arrayNode.getElements().stream() - .map(el -> new Literal(el.accept(this), el)) - .collect(Collectors.toList())); - } - - @Override - public ILiteral booleanNode(BooleanNode booleanNode) { - return new Bool(booleanNode); - } - - @Override - public ILiteral nullNode(NullNode nullNode) { - throw new RuntimeException("null node not supported"); - } - - @Override - public ILiteral numberNode(NumberNode numberNode) { - return new Integer(numberNode); - } - - @Override - public ILiteral objectNode(ObjectNode objectNode) { - Map obj = new LinkedHashMap<>(); - objectNode.getMembers().forEach((k, v) -> { - obj.put(Identifier.of(k), new Literal(v.accept(this), v)); - }); - return new Record(obj); - } - - @Override - public ILiteral stringNode(StringNode stringNode) { - return new String(new Template(stringNode)); - } - }); - return new Literal(iLiteral, node.getSourceLocation()); - } - - /** - * Attempts to convert the literal to a {@link java.lang.String}. Otherwise throws an exception. - * - * @return the literal as a string. - */ - public java.lang.String expectLiteralString() { - if (source instanceof String) { - final String s = (String) source; - - return s.value.expectLiteral(); - } else { - throw new RuleError(new SourceException("Expected a literal string, got " + source, this)); - } - } - - /** - * Attempts to convert the literal to a {@link Boolean} if possible. Otherwise, returns an empty optional. - * - * @return an optional boolean. - */ - public Optional asBool() { - return source.asBool(); - } - - /** - * Attempts to convert the literal to a {@link Template} if possible. Otherwise, returns an empty optional. - * - * @return an optional boolean. - */ - public Optional