Skip to content

Commit 3065cab

Browse files
committed
Add version to bdd trait and syntax elements
We can now track what version an endpointBdd trait uses and attach minimum version requirements to all syntax elements of the rules engine, include functions. Now the coalesce function is available since version 1.1. Next, I'll add validation to ensure the version requirements of every syntax element of an endpointRuleSet or endpointBdd meet the version of the trait.
1 parent e07631c commit 3065cab

12 files changed

Lines changed: 168 additions & 21 deletions

File tree

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@
2222
*/
2323
@SmithyInternalApi
2424
public abstract class SyntaxElement implements ToCondition, ToExpression {
25+
/**
26+
* Get the rules engine version that this syntax element is available since.
27+
*
28+
* @return the version this is available since.
29+
*/
30+
public String availableSince() {
31+
return "1.0";
32+
}
33+
2534
/**
2635
* Returns a BooleanEquals expression comparing this expression to the provided boolean value.
2736
*

smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/syntax/expressions/functions/Coalesce.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
*
3838
* <p>Supports chaining:
3939
* {@code coalesce(opt1, coalesce(opt2, coalesce(opt3, default)))}
40+
*
41+
* <p>Available since: rules engine 1.1.
4042
*/
4143
@SmithyUnstableApi
4244
public final class Coalesce extends LibraryFunction {
@@ -67,6 +69,11 @@ public static Coalesce ofExpressions(ToExpression arg1, ToExpression arg2) {
6769
return DEFINITION.createFunction(FunctionNode.ofExpressions(ID, arg1, arg2));
6870
}
6971

72+
@Override
73+
public String availableSince() {
74+
return "1.1";
75+
}
76+
7077
@Override
7178
public <R> R accept(ExpressionVisitor<R> visitor) {
7279
List<Expression> args = getArguments();

smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/logic/bdd/EndpointBddTrait.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import java.io.DataOutputStream;
99
import java.io.IOException;
1010
import java.io.UncheckedIOException;
11+
import java.math.BigDecimal;
1112
import java.nio.ByteBuffer;
1213
import java.nio.ByteOrder;
1314
import java.util.ArrayList;
@@ -37,25 +38,34 @@
3738
public final class EndpointBddTrait extends AbstractTrait implements ToSmithyBuilder<EndpointBddTrait> {
3839
public static final ShapeId ID = ShapeId.from("smithy.rules#endpointBdd");
3940

41+
private static final BigDecimal MIN_VERSION = new BigDecimal("1.1");
4042
private static final Set<String> ALLOWED_PROPERTIES = SetUtils.of(
43+
"version",
4144
"parameters",
4245
"conditions",
4346
"results",
4447
"root",
4548
"nodes",
4649
"nodeCount");
4750

51+
private final String version;
4852
private final Parameters parameters;
4953
private final List<Condition> conditions;
5054
private final List<Rule> results;
5155
private final Bdd bdd;
5256

5357
private EndpointBddTrait(Builder builder) {
5458
super(ID, builder.getSourceLocation());
59+
this.version = SmithyBuilder.requiredState("version", builder.version);
5560
this.parameters = SmithyBuilder.requiredState("parameters", builder.parameters);
5661
this.conditions = SmithyBuilder.requiredState("conditions", builder.conditions);
5762
this.results = SmithyBuilder.requiredState("results", builder.results);
5863
this.bdd = SmithyBuilder.requiredState("bdd", builder.bdd);
64+
65+
BigDecimal v = new BigDecimal(version);
66+
if (v.compareTo(MIN_VERSION) < 0) {
67+
throw new IllegalArgumentException("Rules engine version for endpointBdd trait must be >= " + MIN_VERSION);
68+
}
5969
}
6070

6171
/**
@@ -72,7 +82,14 @@ public static EndpointBddTrait from(Cfg cfg) {
7282
throw new IllegalStateException("Mismatch between BDD var count and orderedConditions size");
7383
}
7484

85+
// Automatically convert 1.0 versions of the decision tree to 1.1 for the minimum version of the BDD trait.
86+
String version = cfg.getVersion();
87+
if (version.equals("1.0")) {
88+
version = "1.1";
89+
}
90+
7591
return builder()
92+
.version(version)
7693
.parameters(cfg.getParameters())
7794
.conditions(compiler.getOrderedConditions())
7895
.results(compiler.getIndexedResults())
@@ -116,6 +133,15 @@ public Bdd getBdd() {
116133
return bdd;
117134
}
118135

136+
/**
137+
* Get the endpoint ruleset version.
138+
*
139+
* @return the rules engine version
140+
*/
141+
public String getVersion() {
142+
return version;
143+
}
144+
119145
/**
120146
* Transform this BDD using the given function and return the updated BddTrait.
121147
*
@@ -129,6 +155,7 @@ public EndpointBddTrait transform(Function<EndpointBddTrait, EndpointBddTrait> t
129155
@Override
130156
protected Node createNode() {
131157
ObjectNode.Builder builder = ObjectNode.builder();
158+
builder.withMember("version", version);
132159
builder.withMember("parameters", parameters.toNode());
133160

134161
ArrayNode.Builder conditionBuilder = ArrayNode.builder();
@@ -167,6 +194,7 @@ protected Node createNode() {
167194
public static EndpointBddTrait fromNode(Node node) {
168195
ObjectNode obj = node.expectObjectNode();
169196
obj.warnIfAdditionalProperties(ALLOWED_PROPERTIES);
197+
String version = obj.expectStringMember("version").getValue();
170198
Parameters params = Parameters.fromNode(obj.expectObjectMember("parameters"));
171199
List<Condition> conditions = obj.expectArrayMember("conditions").getElementsAs(Condition::fromNode);
172200

@@ -182,6 +210,7 @@ public static EndpointBddTrait fromNode(Node node) {
182210
Bdd bdd = decodeBdd(nodesBase64, nodeCount, rootRef, conditions.size(), results.size());
183211

184212
EndpointBddTrait trait = builder()
213+
.version(version)
185214
.sourceLocation(node)
186215
.parameters(params)
187216
.conditions(conditions)
@@ -241,6 +270,7 @@ public static Builder builder() {
241270
@Override
242271
public Builder toBuilder() {
243272
return builder()
273+
.version(version)
244274
.sourceLocation(getSourceLocation())
245275
.parameters(parameters)
246276
.conditions(conditions)
@@ -252,13 +282,25 @@ public Builder toBuilder() {
252282
* Builder for BddTrait.
253283
*/
254284
public static final class Builder extends AbstractTraitBuilder<EndpointBddTrait, Builder> {
285+
private String version = "1.1";
255286
private Parameters parameters;
256287
private List<Condition> conditions;
257288
private List<Rule> results;
258289
private Bdd bdd;
259290

260291
private Builder() {}
261292

293+
/**
294+
* Sets the rules engine version.
295+
*
296+
* @param version Version to set (e.g., 1.1).
297+
* @return this builder
298+
*/
299+
public Builder version(String version) {
300+
this.version = version;
301+
return this;
302+
}
303+
262304
/**
263305
* Sets the parameters.
264306
*

smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/logic/cfg/Cfg.java

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import java.util.List;
1616
import java.util.Map;
1717
import java.util.NoSuchElementException;
18+
import java.util.Objects;
1819
import java.util.Set;
1920
import software.amazon.smithy.rulesengine.language.EndpointRuleSet;
2021
import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameters;
@@ -45,13 +46,18 @@ public final class Cfg implements Iterable<CfgNode> {
4546
// Lazily computed condition data
4647
private Condition[] conditions;
4748
private Map<Condition, Integer> conditionToIndex;
49+
private final String version;
4850

4951
Cfg(EndpointRuleSet ruleSet, CfgNode root) {
50-
this(ruleSet == null ? Parameters.builder().build() : ruleSet.getParameters(), root);
52+
this(
53+
ruleSet == null ? Parameters.builder().build() : ruleSet.getParameters(),
54+
root,
55+
ruleSet == null ? "1.1" : ruleSet.getVersion());
5156
}
5257

53-
Cfg(Parameters parameters, CfgNode root) {
58+
Cfg(Parameters parameters, CfgNode root, String version) {
5459
this.root = SmithyBuilder.requiredState("root", root);
60+
this.version = version;
5561
this.parameters = parameters;
5662
}
5763

@@ -69,6 +75,15 @@ public static Cfg from(EndpointRuleSet ruleSet) {
6975
return builder.build(root);
7076
}
7177

78+
/**
79+
* Get the endpoint ruleset version of the CFG.
80+
*
81+
* @return endpoint ruleset version.
82+
*/
83+
public String getVersion() {
84+
return version;
85+
}
86+
7287
/**
7388
* Gets all unique conditions in the CFG, in the order they were discovered.
7489
*
@@ -141,13 +156,14 @@ public boolean equals(Object object) {
141156
} else if (object == null || getClass() != object.getClass()) {
142157
return false;
143158
} else {
144-
return root.equals(((Cfg) object).root);
159+
Cfg o = (Cfg) object;
160+
return root.equals(o.root) && version.equals(o.version);
145161
}
146162
}
147163

148164
@Override
149165
public int hashCode() {
150-
return root.hashCode();
166+
return Objects.hash(root, version);
151167
}
152168

153169
/**

smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/logic/cfg/CfgBuilder.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,12 @@ public final class CfgBuilder {
6666
private int convergenceNodesCreated = 0;
6767
private int phiVariableCounter = 0;
6868

69+
private String version = "1.1";
70+
6971
public CfgBuilder(EndpointRuleSet ruleSet) {
7072
// Apply SSA transform to ensure globally unique variable names
7173
this.ruleSet = SsaTransform.transform(ruleSet);
74+
this.version = ruleSet.getVersion();
7275

7376
// Analyze results and create convergence nodes
7477
analyzeAndCreateConvergenceNodes();
@@ -84,6 +87,17 @@ public Cfg build(CfgNode root) {
8487
return new Cfg(ruleSet, Objects.requireNonNull(root));
8588
}
8689

90+
/**
91+
* Set the version of the endpoint rules engine (e.g., 1.1).
92+
*
93+
* @param version Version to set.
94+
* @return the builder;
95+
*/
96+
public CfgBuilder version(String version) {
97+
this.version = Objects.requireNonNull(version);
98+
return this;
99+
}
100+
87101
/**
88102
* Creates a condition node, reusing existing nodes when possible.
89103
*

smithy-rules-engine/src/main/resources/META-INF/smithy/smithy.rules.smithy

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ document endpointRuleSet
1111
@unstable
1212
@trait(selector: "service")
1313
structure endpointBdd {
14+
/// The rules engine version. Must be set to 1.1 or higher.
15+
@required
16+
version: String
17+
1418
/// A map of zero or more endpoint parameter names to their parameter configuration.
1519
@required
1620
parameters: Parameters
@@ -35,15 +39,12 @@ structure endpointBdd {
3539

3640
/// Base64-encoded array of BDD nodes representing the decision graph structure.
3741
///
42+
/// All integers are encoded in big-endian.
43+
///
3844
/// The first node (index 0) is always the terminal node `[-1, 1, -1]` and is included in the nodeCount.
3945
/// User-defined nodes start at index 1.
4046
///
41-
/// Zig-zag encoding transforms signed integers to unsigned:
42-
/// - 0 -> 0, -1 → 1, 1 → 2, -2 → 3, 2 → 4, etc.
43-
/// - Formula: `(n << 1) ^ (n >> 31)`
44-
/// - This ensures small negative numbers use few bytes
45-
///
46-
/// Each node consists of three varint-encoded integers written sequentially:
47+
/// Each node is written one after the other and consists of three integers written sequentially:
4748
/// 1. variable index
4849
/// 2. high reference (when condition is true)
4950
/// 3. low reference (when condition is false)
@@ -69,17 +70,6 @@ structure endpointBdd {
6970
/// boolean function and its complement; instead of creating separate nodes for `condition AND other` and
7071
/// `NOT(condition AND other)`, we can reuse the same nodes with complement edges. Complement edges cannot be
7172
/// used on result terminals.
72-
///
73-
/// Example (before encoding):
74-
/// ```
75-
/// nodes = [
76-
/// [ -1, 1, -1], // 0: terminal node
77-
/// [ 0, 3, 2], // 1: if condition[0] then node 3, else node 2
78-
/// [ 1, 2000001, -1], // 2: if condition[1] then result[1], else FALSE
79-
/// ]
80-
/// ```
81-
///
82-
/// After zig-zag + varint + base64: `"AQEBAAYEBAGBwOgPAQ=="`
8373
@required
8474
nodes: String
8575
}

smithy-rules-engine/src/test/resources/software/amazon/smithy/rulesengine/traits/errorfiles/bdd/bdd-invalid-base64.smithy

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ namespace smithy.example
55
use smithy.rules#endpointBdd
66

77
@endpointBdd({
8+
version: "1.1"
89
parameters: {
910
Region: {
1011
type: "string"

smithy-rules-engine/src/test/resources/software/amazon/smithy/rulesengine/traits/errorfiles/bdd/bdd-invalid-node-data.smithy

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ namespace smithy.example
55
use smithy.rules#endpointBdd
66

77
@endpointBdd({
8+
version: "1.1"
89
parameters: {
910
Region: {
1011
type: "string"

smithy-rules-engine/src/test/resources/software/amazon/smithy/rulesengine/traits/errorfiles/bdd/bdd-invalid-root-reference.smithy

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ namespace smithy.example
55
use smithy.rules#endpointBdd
66

77
@endpointBdd({
8+
version: "1.1"
89
parameters: {}
910
conditions: []
1011
results: []

smithy-rules-engine/src/test/resources/software/amazon/smithy/rulesengine/traits/errorfiles/bdd/bdd-valid.smithy

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use smithy.rules#endpointBdd
1010
UseFips: {type: "boolean", documentation: "docs"}
1111
)
1212
@endpointBdd({
13+
version: "1.1"
1314
"parameters": {
1415
"Region": {
1516
"required": true,

0 commit comments

Comments
 (0)