Skip to content

Commit d0b4848

Browse files
l46kokcopybara-github
authored andcommitted
Add a shorthand for declaring policy variables
PiperOrigin-RevId: 869243638
1 parent d4e4984 commit d0b4848

4 files changed

Lines changed: 130 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: 59 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,45 @@ 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
359+
.setName(ctx.newValueString(keyNode))
360+
.setExpression(ctx.newValueString(nodeTuple.getValueNode()));
361+
iterations++;
362+
363+
if (iterations > 1) {
364+
ctx.reportError(keyId, "Only one variable may be defined inline");
365+
}
366+
}
367+
368+
if (!assertRequiredFields(ctx, id, builder.getMissingRequiredFieldNames())) {
369+
return ERROR_VARIABLE;
370+
}
371+
372+
return builder.build();
373+
}
374+
375+
private Variable parseVariableObject(
376+
PolicyParserContext<Node> ctx,
377+
CelPolicy.Builder policyBuilder,
378+
long id,
379+
MappingNode variableMap,
380+
Variable.Builder builder) {
342381
for (NodeTuple nodeTuple : variableMap.getValue()) {
343382
Node keyNode = nodeTuple.getKeyNode();
344383
long keyId = ctx.collectMetadata(keyNode);
@@ -370,8 +409,13 @@ public CelPolicy.Variable parseVariable(
370409
return builder.build();
371410
}
372411

373-
private ParserImpl(TagVisitor<Node> tagVisitor, String source, String description) {
412+
private ParserImpl(
413+
TagVisitor<Node> tagVisitor,
414+
boolean enableSimpleVariables,
415+
String source,
416+
String description) {
374417
this.tagVisitor = tagVisitor;
418+
this.enableSimpleVariables = enableSimpleVariables;
375419
this.policySource =
376420
CelPolicySource.newBuilder(CelCodePointArray.fromString(source))
377421
.setDescription(description)
@@ -413,9 +457,11 @@ public ValueString newValueString(Node node) {
413457
static final class Builder implements CelPolicyParserBuilder<Node> {
414458

415459
private TagVisitor<Node> tagVisitor;
460+
private boolean enableSimpleVariables;
416461

417462
private Builder() {
418463
this.tagVisitor = new TagVisitor<Node>() {};
464+
this.enableSimpleVariables = false;
419465
}
420466

421467
@Override
@@ -424,17 +470,24 @@ public CelPolicyParserBuilder<Node> addTagVisitor(TagVisitor<Node> tagVisitor) {
424470
return this;
425471
}
426472

473+
@Override
474+
public CelPolicyParserBuilder<Node> enableSimpleVariables(boolean enable) {
475+
this.enableSimpleVariables = enable;
476+
return this;
477+
}
478+
427479
@Override
428480
public CelPolicyParser build() {
429-
return new CelPolicyYamlParser(tagVisitor);
481+
return new CelPolicyYamlParser(tagVisitor, enableSimpleVariables);
430482
}
431483
}
432484

433-
static Builder newBuilder() {
434-
return new Builder();
485+
static CelPolicyParserBuilder<Node> newBuilder() {
486+
return new Builder().enableSimpleVariables(false);
435487
}
436488

437-
private CelPolicyYamlParser(TagVisitor<Node> tagVisitor) {
489+
private CelPolicyYamlParser(TagVisitor<Node> tagVisitor, boolean enableSimpleVariables) {
438490
this.tagVisitor = checkNotNull(tagVisitor);
491+
this.enableSimpleVariables = enableSimpleVariables;
439492
}
440493
}

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

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

305+
@Test
306+
public void evaluateYamlPolicy_withSimpleVariable() throws Exception {
307+
Cel cel = newCel();
308+
String policySource =
309+
"name: shorthand_variables_policy\n"
310+
+ "rule:\n"
311+
+ " variables:\n"
312+
+ " - first: 'true'\n"
313+
+ " - second: 'false'\n"
314+
+ " match:\n"
315+
+ " - output: 'variables.first && variables.second'";
316+
CelPolicyParser parser =
317+
CelPolicyParserFactory.newYamlParserBuilder().enableSimpleVariables(true).build();
318+
CelPolicy policy = parser.parse(policySource);
319+
320+
CelAbstractSyntaxTree compiledPolicyAst =
321+
CelPolicyCompilerFactory.newPolicyCompiler(cel).build().compile(policy);
322+
323+
boolean evalResult = (boolean) cel.createProgram(compiledPolicyAst).eval();
324+
325+
assertThat(evalResult).isFalse();
326+
}
327+
305328
private static final class EvaluablePolicyTestData {
306329
private final TestYamlPolicy yamlPolicy;
307330
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)