Skip to content

Commit 5e652ac

Browse files
l46kokcopybara-github
authored andcommitted
Add a shorthand for declaring policy variables
PiperOrigin-RevId: 868430821
1 parent b3ee6a6 commit 5e652ac

4 files changed

Lines changed: 131 additions & 6 deletions

File tree

policy/src/main/java/dev/cel/policy/CelPolicyParserBuilder.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,30 @@ public interface CelPolicyParserBuilder<T> {
2929
@CanIgnoreReturnValue
3030
CelPolicyParserBuilder<T> addTagVisitor(TagVisitor<T> tagVisitor);
3131

32+
/**
33+
* Configures the parser to allow for key-value pairs to declare a variable name and expression.
34+
*
35+
* <p>For example:
36+
*
37+
* <pre>{@code
38+
* variables:
39+
* - foo: bar
40+
* - baz: qux
41+
* }</pre>
42+
*
43+
* <p>This is in contrast to the default behavior, which requires the following syntax:
44+
*
45+
* <pre>{@code
46+
* variables:
47+
* - name: foo
48+
* expression: bar
49+
* - name: baz
50+
* expression: qux
51+
* }</pre>
52+
*/
53+
@CanIgnoreReturnValue
54+
CelPolicyParserBuilder<T> enableSimpleVariables(boolean enable);
55+
3256
/** Builds a new instance of {@link CelPolicyParser}. */
3357
@CheckReturnValue
3458
CelPolicyParser build();

policy/src/main/java/dev/cel/policy/CelPolicyYamlParser.java

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ final class CelPolicyYamlParser implements CelPolicyParser {
4949
Variable.newBuilder().setExpression(ERROR_VALUE).setName(ERROR_VALUE).build();
5050

5151
private final TagVisitor<Node> tagVisitor;
52+
private final boolean enableSimpleVariables;
5253

5354
@Override
5455
public CelPolicy parse(String policySource) throws CelPolicyValidationException {
@@ -58,13 +59,15 @@ public CelPolicy parse(String policySource) throws CelPolicyValidationException
5859
@Override
5960
public CelPolicy parse(String policySource, String description)
6061
throws CelPolicyValidationException {
61-
ParserImpl parser = new ParserImpl(tagVisitor, policySource, description);
62+
ParserImpl parser =
63+
new ParserImpl(tagVisitor, enableSimpleVariables, policySource, description);
6264
return parser.parseYaml();
6365
}
6466

6567
private static class ParserImpl implements PolicyParserContext<Node> {
6668

6769
private final TagVisitor<Node> tagVisitor;
70+
private final boolean enableSimpleVariables;
6871
private final CelPolicySource policySource;
6972
private final ParserContext<Node> ctx;
7073

@@ -336,9 +339,44 @@ public CelPolicy.Variable parseVariable(
336339
if (!assertYamlType(ctx, id, node, YamlNodeType.MAP)) {
337340
return ERROR_VARIABLE;
338341
}
342+
339343
MappingNode variableMap = (MappingNode) node;
340344
Variable.Builder builder = Variable.newBuilder();
341345

346+
if (enableSimpleVariables) {
347+
return parseVariableInline(ctx, id, variableMap, builder);
348+
}
349+
return parseVariableObject(ctx, policyBuilder, id, variableMap, builder);
350+
}
351+
352+
private Variable parseVariableInline(
353+
PolicyParserContext<Node> ctx, long id, MappingNode variableMap, Variable.Builder builder) {
354+
int iterations = 0;
355+
for (NodeTuple nodeTuple : variableMap.getValue()) {
356+
Node keyNode = nodeTuple.getKeyNode();
357+
long keyId = ctx.collectMetadata(keyNode);
358+
builder.setName(ctx.newValueString(keyNode));
359+
builder.setExpression(ctx.newValueString(nodeTuple.getValueNode()));
360+
iterations++;
361+
362+
if (iterations > 1) {
363+
ctx.reportError(keyId, "Only one variable may be defined inline");
364+
}
365+
}
366+
367+
if (!assertRequiredFields(ctx, id, builder.getMissingRequiredFieldNames())) {
368+
return ERROR_VARIABLE;
369+
}
370+
371+
return builder.build();
372+
}
373+
374+
private Variable parseVariableObject(
375+
PolicyParserContext<Node> ctx,
376+
CelPolicy.Builder policyBuilder,
377+
long id,
378+
MappingNode variableMap,
379+
Variable.Builder builder) {
342380
for (NodeTuple nodeTuple : variableMap.getValue()) {
343381
Node keyNode = nodeTuple.getKeyNode();
344382
long keyId = ctx.collectMetadata(keyNode);
@@ -370,8 +408,13 @@ public CelPolicy.Variable parseVariable(
370408
return builder.build();
371409
}
372410

373-
private ParserImpl(TagVisitor<Node> tagVisitor, String source, String description) {
411+
private ParserImpl(
412+
TagVisitor<Node> tagVisitor,
413+
boolean enableSimpleVariables,
414+
String source,
415+
String description) {
374416
this.tagVisitor = tagVisitor;
417+
this.enableSimpleVariables = enableSimpleVariables;
375418
this.policySource =
376419
CelPolicySource.newBuilder(CelCodePointArray.fromString(source))
377420
.setDescription(description)
@@ -413,9 +456,11 @@ public ValueString newValueString(Node node) {
413456
static final class Builder implements CelPolicyParserBuilder<Node> {
414457

415458
private TagVisitor<Node> tagVisitor;
459+
private boolean enableSimpleVariables;
416460

417461
private Builder() {
418462
this.tagVisitor = new TagVisitor<Node>() {};
463+
this.enableSimpleVariables = false;
419464
}
420465

421466
@Override
@@ -424,17 +469,24 @@ public CelPolicyParserBuilder<Node> addTagVisitor(TagVisitor<Node> tagVisitor) {
424469
return this;
425470
}
426471

472+
@Override
473+
public CelPolicyParserBuilder<Node> enableSimpleVariables(boolean enable) {
474+
this.enableSimpleVariables = enable;
475+
return this;
476+
}
477+
427478
@Override
428479
public CelPolicyParser build() {
429-
return new CelPolicyYamlParser(tagVisitor);
480+
return new CelPolicyYamlParser(tagVisitor, enableSimpleVariables);
430481
}
431482
}
432483

433-
static Builder newBuilder() {
434-
return new Builder();
484+
static CelPolicyParserBuilder<Node> newBuilder() {
485+
return new Builder().enableSimpleVariables(false);
435486
}
436487

437-
private CelPolicyYamlParser(TagVisitor<Node> tagVisitor) {
488+
private CelPolicyYamlParser(TagVisitor<Node> tagVisitor, boolean enableSimpleVariables) {
438489
this.tagVisitor = checkNotNull(tagVisitor);
490+
this.enableSimpleVariables = enableSimpleVariables;
439491
}
440492
}

policy/src/test/java/dev/cel/policy/CelPolicyCompilerImplTest.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,31 @@ public void evaluateYamlPolicy_lateBoundFunction() throws Exception {
302302
assertThat(evalResult).isEqualTo("foo" + exampleValue);
303303
}
304304

305+
@Test
306+
@SuppressWarnings("unchecked") // Test only
307+
public void evaluateYamlPolicy_withSimpleVariable() throws Exception {
308+
Cel cel = newCel();
309+
String policySource =
310+
"name: shorthand_variables_policy\n"
311+
+ "rule:\n"
312+
+ " variables:\n"
313+
+ " - first: 'true'\n"
314+
+ " - second: 'false'\n"
315+
+ " match:\n"
316+
+ " - condition: 'variables.first && variables.second'\n"
317+
+ " output: 'false'\n";
318+
CelPolicyParser parser =
319+
CelPolicyParserFactory.newYamlParserBuilder().enableSimpleVariables(true).build();
320+
CelPolicy policy = parser.parse(policySource);
321+
322+
CelAbstractSyntaxTree compiledPolicyAst =
323+
CelPolicyCompilerFactory.newPolicyCompiler(cel).build().compile(policy);
324+
325+
Optional<Object> evalResult = (Optional<Object>) cel.createProgram(compiledPolicyAst).eval();
326+
327+
assertThat(evalResult).hasValue(false);
328+
}
329+
305330
private static final class EvaluablePolicyTestData {
306331
private final TestYamlPolicy yamlPolicy;
307332
private final PolicyTestCase testCase;

policy/src/test/java/dev/cel/policy/CelPolicyYamlParserTest.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,30 @@ public void parseYamlPolicy_withImports() throws Exception {
148148
.inOrder();
149149
}
150150

151+
@Test
152+
public void parseYamlPolicy_withSimpleVariable_multipleInlinedVariables() {
153+
String policySource =
154+
"name: shorthand_variables_policy\n"
155+
+ "rule:\n"
156+
+ " variables:\n"
157+
+ " - first: 'true'\n"
158+
+ " second: 'false'\n"
159+
+ " match:\n"
160+
+ " - condition: 'variables.my_var'\n"
161+
+ " output: 'true'\n";
162+
CelPolicyParser parser =
163+
CelPolicyParserFactory.newYamlParserBuilder().enableSimpleVariables(true).build();
164+
165+
CelPolicyValidationException e =
166+
assertThrows(CelPolicyValidationException.class, () -> parser.parse(policySource));
167+
assertThat(e)
168+
.hasMessageThat()
169+
.contains(
170+
"ERROR: <input>:5:7: Only one variable may be defined inline\n"
171+
+ " | second: 'false'\n"
172+
+ " | ......^");
173+
}
174+
151175
@Test
152176
public void parseYamlPolicy_errors(@TestParameter PolicyParseErrorTestCase testCase) {
153177
CelPolicyValidationException e =

0 commit comments

Comments
 (0)