Skip to content

Commit 31b60f5

Browse files
committed
Add BDD validation, same as ruleset trait
1 parent 1302376 commit 31b60f5

19 files changed

Lines changed: 551 additions & 361 deletions

File tree

smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/validators/RuleSetAwsBuiltInValidator.java

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,15 @@
66

77
import java.util.ArrayList;
88
import java.util.List;
9-
import java.util.Optional;
109
import java.util.Set;
1110
import software.amazon.smithy.model.FromSourceLocation;
1211
import software.amazon.smithy.model.Model;
1312
import software.amazon.smithy.model.shapes.ServiceShape;
1413
import software.amazon.smithy.model.validation.AbstractValidator;
1514
import software.amazon.smithy.model.validation.ValidationEvent;
1615
import software.amazon.smithy.rulesengine.aws.language.functions.AwsBuiltIns;
17-
import software.amazon.smithy.rulesengine.language.EndpointRuleSet;
1816
import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameter;
17+
import software.amazon.smithy.rulesengine.traits.BddTrait;
1918
import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait;
2019
import software.amazon.smithy.utils.SetUtils;
2120

@@ -33,36 +32,30 @@ public class RuleSetAwsBuiltInValidator extends AbstractValidator {
3332
@Override
3433
public List<ValidationEvent> validate(Model model) {
3534
List<ValidationEvent> events = new ArrayList<>();
36-
for (ServiceShape serviceShape : model.getServiceShapesWithTrait(EndpointRuleSetTrait.class)) {
37-
events.addAll(validateRuleSetAwsBuiltIns(serviceShape,
38-
serviceShape.expectTrait(EndpointRuleSetTrait.class)
39-
.getEndpointRuleSet()));
35+
36+
for (ServiceShape s : model.getServiceShapesWithTrait(EndpointRuleSetTrait.class)) {
37+
EndpointRuleSetTrait trait = s.expectTrait(EndpointRuleSetTrait.class);
38+
validateRuleSetAwsBuiltIns(events, s, trait.getEndpointRuleSet().getParameters());
39+
}
40+
41+
for (ServiceShape s : model.getServiceShapesWithTrait(BddTrait.class)) {
42+
validateRuleSetAwsBuiltIns(events, s, s.expectTrait(BddTrait.class).getBdd().getParameters());
4043
}
44+
4145
return events;
4246
}
4347

44-
private List<ValidationEvent> validateRuleSetAwsBuiltIns(ServiceShape serviceShape, EndpointRuleSet ruleSet) {
45-
List<ValidationEvent> events = new ArrayList<>();
46-
for (Parameter parameter : ruleSet.getParameters()) {
48+
private void validateRuleSetAwsBuiltIns(List<ValidationEvent> events, ServiceShape s, Iterable<Parameter> params) {
49+
for (Parameter parameter : params) {
4750
if (parameter.isBuiltIn()) {
48-
validateBuiltIn(serviceShape, parameter.getBuiltIn().get(), parameter).ifPresent(events::add);
51+
validateBuiltIn(events, s, parameter.getBuiltIn().get(), parameter);
4952
}
5053
}
51-
return events;
5254
}
5355

54-
private Optional<ValidationEvent> validateBuiltIn(
55-
ServiceShape serviceShape,
56-
String builtInName,
57-
FromSourceLocation source
58-
) {
59-
if (ADDITIONAL_CONSIDERATION_BUILT_INS.contains(builtInName)) {
60-
return Optional.of(danger(
61-
serviceShape,
62-
source,
63-
String.format(ADDITIONAL_CONSIDERATION_MESSAGE, builtInName),
64-
builtInName));
56+
private void validateBuiltIn(List<ValidationEvent> events, ServiceShape s, String name, FromSourceLocation source) {
57+
if (ADDITIONAL_CONSIDERATION_BUILT_INS.contains(name)) {
58+
events.add(danger(s, source, String.format(ADDITIONAL_CONSIDERATION_MESSAGE, name), name));
6559
}
66-
return Optional.empty();
6760
}
6861
}

smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/evaluation/RuleEvaluator.java

Lines changed: 71 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,13 @@
2121
import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameter;
2222
import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameters;
2323
import software.amazon.smithy.rulesengine.language.syntax.rule.Condition;
24+
import software.amazon.smithy.rulesengine.language.syntax.rule.EndpointRule;
25+
import software.amazon.smithy.rulesengine.language.syntax.rule.ErrorRule;
2426
import software.amazon.smithy.rulesengine.language.syntax.rule.Rule;
2527
import software.amazon.smithy.rulesengine.language.syntax.rule.RuleValueVisitor;
28+
import software.amazon.smithy.rulesengine.logic.RuleBasedConditionEvaluator;
29+
import software.amazon.smithy.rulesengine.logic.bdd.Bdd;
30+
import software.amazon.smithy.rulesengine.logic.bdd.BddEvaluator;
2631
import software.amazon.smithy.utils.SmithyUnstableApi;
2732

2833
/**
@@ -45,6 +50,44 @@ public static Value evaluate(EndpointRuleSet ruleset, Map<Identifier, Value> par
4550
return new RuleEvaluator().evaluateRuleSet(ruleset, parameterArguments);
4651
}
4752

53+
/**
54+
* Initializes a new {@link RuleEvaluator} instances, and evaluates the provided BDD and parameter arguments.
55+
*
56+
* @param bdd The endpoint bdd.
57+
* @param parameterArguments The rule-set parameter identifiers and values to evaluate the BDD against.
58+
* @return The resulting value from the final matched rule.
59+
*/
60+
public static Value evaluate(Bdd bdd, Map<Identifier, Value> parameterArguments) {
61+
return new RuleEvaluator().evaluateBdd(bdd, parameterArguments);
62+
}
63+
64+
private Value evaluateBdd(Bdd bdd, Map<Identifier, Value> parameterArguments) {
65+
return scope.inScope(() -> {
66+
for (Parameter parameter : bdd.getParameters()) {
67+
parameter.getDefault().ifPresent(value -> scope.insert(parameter.getName(), value));
68+
}
69+
70+
parameterArguments.forEach(scope::insert);
71+
BddEvaluator evaluator = BddEvaluator.from(bdd);
72+
Condition[] conds = bdd.getConditions().toArray(new Condition[0]);
73+
RuleBasedConditionEvaluator conditionEvaluator = new RuleBasedConditionEvaluator(this, conds);
74+
int result = evaluator.evaluate(conditionEvaluator);
75+
76+
if (result <= 0) {
77+
throw new RuntimeException("No BDD result matched");
78+
}
79+
80+
Rule rule = bdd.getResults().get(result);
81+
if (rule instanceof EndpointRule) {
82+
return resolveEndpoint(this, ((EndpointRule) rule).getEndpoint());
83+
} else if (rule instanceof ErrorRule) {
84+
return resolveError(this, ((ErrorRule) rule).getError());
85+
} else {
86+
throw new RuntimeException("Invalid BDD rule result: " + rule);
87+
}
88+
});
89+
}
90+
4891
/**
4992
* Evaluate the provided ruleset and parameter arguments.
5093
*
@@ -175,32 +218,40 @@ public Value visitTreeRule(List<Rule> rules) {
175218

176219
@Override
177220
public Value visitErrorRule(Expression error) {
178-
return error.accept(self);
221+
return resolveError(self, error);
179222
}
180223

181224
@Override
182225
public Value visitEndpointRule(Endpoint endpoint) {
183-
EndpointValue.Builder builder = EndpointValue.builder()
184-
.sourceLocation(endpoint)
185-
.url(endpoint.getUrl()
186-
.accept(RuleEvaluator.this)
187-
.expectStringValue()
188-
.getValue());
189-
190-
for (Map.Entry<Identifier, Literal> entry : endpoint.getProperties().entrySet()) {
191-
builder.putProperty(entry.getKey().toString(), entry.getValue().accept(RuleEvaluator.this));
192-
}
193-
194-
for (Map.Entry<String, List<Expression>> entry : endpoint.getHeaders().entrySet()) {
195-
List<String> values = new ArrayList<>();
196-
for (Expression expression : entry.getValue()) {
197-
values.add(expression.accept(RuleEvaluator.this).expectStringValue().getValue());
198-
}
199-
builder.putHeader(entry.getKey(), values);
200-
}
201-
return builder.build();
226+
return resolveEndpoint(self, endpoint);
202227
}
203228
});
204229
});
205230
}
231+
232+
private static Value resolveEndpoint(RuleEvaluator self, Endpoint endpoint) {
233+
EndpointValue.Builder builder = EndpointValue.builder()
234+
.sourceLocation(endpoint)
235+
.url(endpoint.getUrl()
236+
.accept(self)
237+
.expectStringValue()
238+
.getValue());
239+
240+
for (Map.Entry<Identifier, Literal> entry : endpoint.getProperties().entrySet()) {
241+
builder.putProperty(entry.getKey().toString(), entry.getValue().accept(self));
242+
}
243+
244+
for (Map.Entry<String, List<Expression>> entry : endpoint.getHeaders().entrySet()) {
245+
List<String> values = new ArrayList<>();
246+
for (Expression expression : entry.getValue()) {
247+
values.add(expression.accept(self).expectStringValue().getValue());
248+
}
249+
builder.putHeader(entry.getKey(), values);
250+
}
251+
return builder.build();
252+
}
253+
254+
private static Value resolveError(RuleEvaluator self, Expression error) {
255+
return error.accept(self);
256+
}
206257
}

smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/evaluation/TestEvaluator.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import software.amazon.smithy.rulesengine.language.evaluation.value.EndpointValue;
1414
import software.amazon.smithy.rulesengine.language.evaluation.value.Value;
1515
import software.amazon.smithy.rulesengine.language.syntax.Identifier;
16+
import software.amazon.smithy.rulesengine.logic.bdd.Bdd;
1617
import software.amazon.smithy.rulesengine.traits.EndpointTestCase;
1718
import software.amazon.smithy.rulesengine.traits.EndpointTestExpectation;
1819
import software.amazon.smithy.rulesengine.traits.ExpectedEndpoint;
@@ -34,12 +35,30 @@ private TestEvaluator() {}
3435
* @param testCase The test case.
3536
*/
3637
public static void evaluate(EndpointRuleSet ruleset, EndpointTestCase testCase) {
38+
Value result = RuleEvaluator.evaluate(ruleset, createParams(testCase));
39+
processResult(result, testCase);
40+
}
41+
42+
/**
43+
* Evaluate the given rule-set and test case. Throws an exception in the event the test case does not pass.
44+
*
45+
* @param bdd The BDD to be tested.
46+
* @param testCase The test case.
47+
*/
48+
public static void evaluate(Bdd bdd, EndpointTestCase testCase) {
49+
Value result = RuleEvaluator.evaluate(bdd, createParams(testCase));
50+
processResult(result, testCase);
51+
}
52+
53+
private static Map<Identifier, Value> createParams(EndpointTestCase testCase) {
3754
Map<Identifier, Value> parameters = new LinkedHashMap<>();
3855
for (Map.Entry<StringNode, Node> entry : testCase.getParams().getMembers().entrySet()) {
3956
parameters.put(Identifier.of(entry.getKey()), Value.fromNode(entry.getValue()));
4057
}
41-
Value result = RuleEvaluator.evaluate(ruleset, parameters);
58+
return parameters;
59+
}
4260

61+
private static void processResult(Value result, EndpointTestCase testCase) {
4362
StringBuilder messageBuilder = new StringBuilder("while executing test case");
4463
if (testCase.getDocumentation().isPresent()) {
4564
messageBuilder.append(" ").append(testCase.getDocumentation().get());

smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/syntax/parameters/Parameter.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import software.amazon.smithy.utils.ListUtils;
2525
import software.amazon.smithy.utils.SmithyBuilder;
2626
import software.amazon.smithy.utils.SmithyUnstableApi;
27+
import software.amazon.smithy.utils.StringUtils;
2728
import software.amazon.smithy.utils.ToSmithyBuilder;
2829

2930
/**
@@ -272,7 +273,7 @@ public Node toNode() {
272273
if (documentation != null) {
273274
node.withMember(DOCUMENTATION, documentation);
274275
}
275-
node.withMember(TYPE, type.toString());
276+
node.withMember(TYPE, StringUtils.uncapitalize(type.toString()));
276277
return node.build();
277278
}
278279

0 commit comments

Comments
 (0)