Skip to content

Commit

Permalink
Improve in-code rule-set composition
Browse files Browse the repository at this point in the history
This commit adds a `SyntaxElement` abstract class that contains several
methods intended for use in constructing rule-sets in code. This class
implements two new interfaces (`ToCondition` and `ToExpression`) to support
converting between types of syntax elements that compose into rule-sets.
The `Condition`, `Parameter`, and `Expression` classes extend this,
allowing all types of ruleset syntax to be composed this way.
  • Loading branch information
kstich committed Sep 4, 2023
1 parent caeee0b commit e71e029
Show file tree
Hide file tree
Showing 26 changed files with 501 additions and 95 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import software.amazon.smithy.rulesengine.language.evaluation.type.Type;
import software.amazon.smithy.rulesengine.language.evaluation.value.Value;
import software.amazon.smithy.rulesengine.language.syntax.Identifier;
import software.amazon.smithy.rulesengine.language.syntax.expressions.Expression;
import software.amazon.smithy.rulesengine.language.syntax.ToExpression;
import software.amazon.smithy.rulesengine.language.syntax.expressions.ExpressionVisitor;
import software.amazon.smithy.rulesengine.language.syntax.expressions.functions.FunctionDefinition;
import software.amazon.smithy.rulesengine.language.syntax.expressions.functions.FunctionNode;
Expand Down Expand Up @@ -74,7 +74,7 @@ public static Definition getDefinition() {
* @param arg1 the region to retrieve partition information from.
* @return The resulting {@link AwsPartition} function.
*/
public static AwsPartition ofExpressions(Expression arg1) {
public static AwsPartition ofExpressions(ToExpression arg1) {
return DEFINITION.createFunction(FunctionNode.ofExpressions(ID, arg1));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import java.util.regex.Pattern;
import software.amazon.smithy.rulesengine.language.evaluation.type.Type;
import software.amazon.smithy.rulesengine.language.evaluation.value.Value;
import software.amazon.smithy.rulesengine.language.syntax.ToExpression;
import software.amazon.smithy.rulesengine.language.syntax.expressions.Expression;
import software.amazon.smithy.rulesengine.language.syntax.expressions.ExpressionVisitor;
import software.amazon.smithy.rulesengine.language.syntax.expressions.functions.FunctionDefinition;
Expand Down Expand Up @@ -45,10 +46,22 @@ public static Definition getDefinition() {
* @param arg2 whether to allow subdomains.
* @return The resulting {@link IsVirtualHostableS3Bucket} function.
*/
public static IsVirtualHostableS3Bucket ofExpressions(Expression arg1, Expression arg2) {
public static IsVirtualHostableS3Bucket ofExpressions(ToExpression arg1, ToExpression arg2) {
return DEFINITION.createFunction(FunctionNode.ofExpressions(ID, arg1, arg2));
}


/**
* Creates a {@link IsVirtualHostableS3Bucket} function from the given expressions.
*
* @param arg1 the value to check.
* @param arg2 whether to allow subdomains.
* @return The resulting {@link IsVirtualHostableS3Bucket} function.
*/
public static IsVirtualHostableS3Bucket ofExpressions(ToExpression arg1, boolean arg2) {
return ofExpressions(arg1, Expression.of(arg2));
}

@Override
public <T> T accept(ExpressionVisitor<T> visitor) {
return visitor.visitLibraryFunction(DEFINITION, getArguments());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import software.amazon.smithy.rulesengine.language.evaluation.type.Type;
import software.amazon.smithy.rulesengine.language.evaluation.value.Value;
import software.amazon.smithy.rulesengine.language.syntax.Identifier;
import software.amazon.smithy.rulesengine.language.syntax.expressions.Expression;
import software.amazon.smithy.rulesengine.language.syntax.ToExpression;
import software.amazon.smithy.rulesengine.language.syntax.expressions.ExpressionVisitor;
import software.amazon.smithy.rulesengine.language.syntax.expressions.functions.FunctionDefinition;
import software.amazon.smithy.rulesengine.language.syntax.expressions.functions.FunctionNode;
Expand Down Expand Up @@ -56,7 +56,7 @@ public static Definition getDefinition() {
* @param arg1 the ARN to parse.
* @return The resulting {@link ParseArn} function.
*/
public static ParseArn ofExpressions(Expression arg1) {
public static ParseArn ofExpressions(ToExpression arg1) {
return DEFINITION.createFunction(FunctionNode.ofExpressions(ID, arg1));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@ public void awsPartitionOfExpression() {
@Test
public void isVirtualHostableS3BucketOfExpression() {
IsVirtualHostableS3Bucket function = IsVirtualHostableS3Bucket.ofExpressions(
Expression.of("foobar"), Expression.of(true));
Expression.of("foobar"), true);
assertThat(function, instanceOf(IsVirtualHostableS3Bucket.class));

IsVirtualHostableS3Bucket function2 = IsVirtualHostableS3Bucket.ofExpressions(
Expression.of("foobar"), Expression.of(true));
assertThat(function2, instanceOf(IsVirtualHostableS3Bucket.class));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

package software.amazon.smithy.rulesengine.language.syntax;

import software.amazon.smithy.rulesengine.language.syntax.expressions.Expression;
import software.amazon.smithy.rulesengine.language.syntax.expressions.functions.BooleanEquals;
import software.amazon.smithy.rulesengine.language.syntax.expressions.functions.GetAttr;
import software.amazon.smithy.rulesengine.language.syntax.expressions.functions.IsSet;
import software.amazon.smithy.rulesengine.language.syntax.expressions.functions.IsValidHostLabel;
import software.amazon.smithy.rulesengine.language.syntax.expressions.functions.Not;
import software.amazon.smithy.rulesengine.language.syntax.expressions.functions.ParseUrl;
import software.amazon.smithy.rulesengine.language.syntax.expressions.functions.StringEquals;
import software.amazon.smithy.rulesengine.language.syntax.expressions.functions.Substring;
import software.amazon.smithy.rulesengine.language.syntax.rule.Condition;
import software.amazon.smithy.utils.SmithyInternalApi;

/**
* A class that is coercible into {@link Condition}s and {@link Expression}s for
* use in composing rule-sets in code.
*/
@SmithyInternalApi
public abstract class SyntaxElement implements ToCondition, ToExpression {
/**
* Returns a BooleanEquals expression comparing this expression to the provided boolean value.
*
* @param value the value to compare against.
* @return the {@link BooleanEquals} function.
*/
public final BooleanEquals booleanEqual(boolean value) {
return BooleanEquals.ofExpressions(toExpression(), Expression.of(value));
}

/**
* Returns a StringEquals function of this expression and the given string value.
*
* @param value the string value to compare this expression to.
* @return the {@link StringEquals} function.
*/
public final StringEquals stringEqual(String value) {
return StringEquals.ofExpressions(toExpression(), Expression.of(value));
}

/**
* Returns a GetAttr function containing the given path string.
*
* @param path the path.
* @return the {@link GetAttr} function.
*/
public final GetAttr getAttr(String path) {
return GetAttr.ofExpressions(toExpression(), path);
}

/**
* Returns a GetAttr function containing the given identifier.
*
* @param path the path.
* @return the {@link GetAttr} function.
*/
public final GetAttr getAttr(Identifier path) {
return GetAttr.ofExpressions(toExpression(), path.toString());
}

/**
* Returns an IsSet expression for this instance.
*
* @return the {@link IsSet} function.
*/
public final IsSet isSet() {
return IsSet.ofExpressions(toExpression());
}

/**
* Returns an isValidHostLabel expression of this expression.
*
* @param allowDots whether the UTF-8 {@code .} is considered valid within a host label.
* @return the {@link IsValidHostLabel} function.
*/
public final IsValidHostLabel isValidHostLabel(boolean allowDots) {
return IsValidHostLabel.ofExpressions(toExpression(), allowDots);
}

/**
* Returns a Not expression of this instance.
*
* @return the {@link Not} function.
*/
public final Not not() {
return Not.ofExpressions(toExpression());
}

/**
* Returns a parseUrl expression of this expression.
*
* @return the {@link ParseUrl} function.
*/
public final ParseUrl parseUrl() {
return ParseUrl.ofExpressions(toExpression());
}

/**
* Returns a Substring expression of this expression.
*
* @param startIndex the starting index of the string.
* @param stopIndex the ending index of the string.
* @param reverse whether the indexing is should start from end of the string to start.
* @return the {@link Substring} function.
*/
public final Substring substring(int startIndex, int stopIndex, boolean reverse) {
return Substring.ofExpressions(toExpression(), startIndex, stopIndex, reverse);
}

/**
* Converts this expression to a string template.
* By default, this implementation returns a {@link RuntimeException}.
*
* @return the String template.
*/
public String template() {
throw new RuntimeException(String.format("cannot convert %s to a string template", this));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

package software.amazon.smithy.rulesengine.language.syntax;

import software.amazon.smithy.rulesengine.language.syntax.rule.Condition;
import software.amazon.smithy.utils.SmithyInternalApi;

/**
* Supplies functionality to be coercible into {@link Condition}s for
* use in composing rule-sets in code.
*/
@SmithyInternalApi
public interface ToCondition {
/**
* Convert this into a condition builder for compositional use.
*
* @return the condition builder.
*/
Condition.Builder toConditionBuilder();

/**
* Convert this into a condition.
*
* @return the condition.
*/
default Condition toCondition() {
return toConditionBuilder().build();
}

/**
* Converts this function into a condition which stores the output in the named result.
*
* @param result the name of the result parameter.
* @return the function as a condition.
*/
default Condition toCondition(String result) {
return toConditionBuilder().result(result).build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

package software.amazon.smithy.rulesengine.language.syntax;

import software.amazon.smithy.rulesengine.language.syntax.expressions.Expression;
import software.amazon.smithy.utils.SmithyInternalApi;

/**
* Supplies functionality to be coercible into {@link Expression}s for
* use in composing rule-sets in code.
*/
@SmithyInternalApi
public interface ToExpression {
/**
* Convert this into an expression.
*
* @return the expression.
*/
Expression toExpression();
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@
import software.amazon.smithy.rulesengine.language.evaluation.TypeCheck;
import software.amazon.smithy.rulesengine.language.evaluation.type.Type;
import software.amazon.smithy.rulesengine.language.syntax.Identifier;
import software.amazon.smithy.rulesengine.language.syntax.SyntaxElement;
import software.amazon.smithy.rulesengine.language.syntax.expressions.functions.FunctionNode;
import software.amazon.smithy.rulesengine.language.syntax.expressions.functions.GetAttr;
import software.amazon.smithy.rulesengine.language.syntax.expressions.functions.Not;
import software.amazon.smithy.rulesengine.language.syntax.expressions.literal.Literal;
import software.amazon.smithy.rulesengine.language.syntax.rule.Condition;
import software.amazon.smithy.utils.SmithyUnstableApi;

/**
Expand All @@ -33,7 +34,7 @@
* Expressions are the fundamental building block of the rule language.
*/
@SmithyUnstableApi
public abstract class Expression implements FromSourceLocation, ToNode, TypeCheck {
public abstract class Expression extends SyntaxElement implements FromSourceLocation, ToNode, TypeCheck {
private final SourceLocation sourceLocation;
private Type cachedType;

Expand Down Expand Up @@ -65,15 +66,6 @@ public static Literal of(String value) {
return getLiteral(StringNode.from(value));
}

/**
* Negates the current expression.
*
* @return the negated expression.
*/
public Expression not() {
return Not.ofExpressions(this);
}

/**
* Constructs an expression from the provided {@link Node}.
*
Expand Down Expand Up @@ -160,14 +152,14 @@ public Type type() {
return cachedType;
}

/**
* Converts this expression to a string template. By default,
* this implementation returns a {@link RuntimeException}.
*
* @return the string template.
*/
public String getTemplate() {
throw new RuntimeException(String.format("Cannot convert `%s` to a string template.", this));
@Override
public Condition.Builder toConditionBuilder() {
return Condition.builder().fn(this);
}

@Override
public Expression toExpression() {
return this;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public Identifier getName() {
}

@Override
public String getTemplate() {
public String template() {
return String.format("{%s}", name);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.util.List;
import software.amazon.smithy.rulesengine.language.evaluation.type.Type;
import software.amazon.smithy.rulesengine.language.evaluation.value.Value;
import software.amazon.smithy.rulesengine.language.syntax.ToExpression;
import software.amazon.smithy.rulesengine.language.syntax.expressions.Expression;
import software.amazon.smithy.rulesengine.language.syntax.expressions.ExpressionVisitor;
import software.amazon.smithy.utils.SmithyUnstableApi;
Expand Down Expand Up @@ -41,10 +42,21 @@ public static Definition getDefinition() {
* @param arg2 the second argument to compare.
* @return The resulting {@link BooleanEquals} function.
*/
public static BooleanEquals ofExpressions(Expression arg1, Expression arg2) {
public static BooleanEquals ofExpressions(ToExpression arg1, ToExpression arg2) {
return DEFINITION.createFunction(FunctionNode.ofExpressions(ID, arg1, arg2));
}

/**
* Creates a {@link BooleanEquals} function from the given expressions.
*
* @param arg1 the first argument to compare.
* @param arg2 the second argument to compare.
* @return The resulting {@link BooleanEquals} function.
*/
public static BooleanEquals ofExpressions(ToExpression arg1, boolean arg2) {
return ofExpressions(arg1, Expression.of(arg2));
}

@Override
public <R> R accept(ExpressionVisitor<R> visitor) {
return visitor.visitBoolEquals(functionNode.getArguments().get(0), functionNode.getArguments().get(1));
Expand Down
Loading

0 comments on commit e71e029

Please sign in to comment.