Skip to content

Commit da61bbd

Browse files
l46kokcopybara-github
authored andcommitted
Parse explanations in match blocks
PiperOrigin-RevId: 662598633
1 parent 595eccd commit da61bbd

File tree

4 files changed

+64
-1
lines changed

4 files changed

+64
-1
lines changed

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,9 @@ public abstract static class Match {
159159

160160
public abstract Result result();
161161

162+
/** Explanation returns the explanation expression, or empty expression if output is not set. */
163+
public abstract Optional<ValueString> explanation();
164+
162165
/** Encapsulates the result of this match when condition is met. (either an output or a rule) */
163166
@AutoOneOf(Match.Result.Kind.class)
164167
public abstract static class Result {
@@ -191,8 +194,12 @@ public abstract static class Builder implements RequiredFieldsChecker {
191194

192195
public abstract Builder setResult(Result result);
193196

197+
public abstract Builder setExplanation(ValueString explanation);
198+
194199
abstract Optional<Result> result();
195200

201+
abstract Optional<ValueString> explanation();
202+
196203
@Override
197204
public ImmutableList<RequiredField> requiredFields() {
198205
return ImmutableList.of(RequiredField.of("output or a rule", this::result));

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,12 +202,30 @@ public CelPolicy.Match parseMatch(
202202
result -> ctx.reportError(tagId, "Only the rule or the output may be set"));
203203
matchBuilder.setResult(Match.Result.ofOutput(ctx.newValueString(value)));
204204
break;
205+
case "explanation":
206+
matchBuilder
207+
.result()
208+
.filter(result -> result.kind().equals(Match.Result.Kind.RULE))
209+
.ifPresent(
210+
result ->
211+
ctx.reportError(
212+
tagId,
213+
"Explanation can only be set on output match cases, not nested rules"));
214+
matchBuilder.setExplanation(ctx.newValueString(value));
215+
break;
205216
case "rule":
206217
matchBuilder
207218
.result()
208219
.filter(result -> result.kind().equals(Match.Result.Kind.OUTPUT))
209220
.ifPresent(
210221
result -> ctx.reportError(tagId, "Only the rule or the output may be set"));
222+
matchBuilder
223+
.explanation()
224+
.ifPresent(
225+
result ->
226+
ctx.reportError(
227+
result.id(),
228+
"Explanation can only be set on output match cases, not nested rules"));
211229
matchBuilder.setResult(Match.Result.ofRule(parseRule(ctx, policyBuilder, value)));
212230
break;
213231
default:

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

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import static com.google.common.truth.Truth.assertThat;
1818
import static org.junit.Assert.assertThrows;
1919

20+
import com.google.common.collect.Iterables;
2021
import com.google.testing.junit.testparameterinjector.TestParameter;
2122
import com.google.testing.junit.testparameterinjector.TestParameterInjector;
2223
import dev.cel.policy.PolicyTestHelper.K8sTagHandler;
@@ -42,6 +43,21 @@ public void parseYamlPolicy_success(@TestParameter TestYamlPolicy yamlPolicy) th
4243
assertThat(policy.policySource().getDescription()).isEqualTo(description);
4344
}
4445

46+
@Test
47+
public void parseYamlPolicy_withExplanation() throws Exception {
48+
String policySource =
49+
"rule:\n"
50+
+ " match:\n"
51+
+ " - output: 'true'\n"
52+
+ " explanation: \"'custom explanation'\"";
53+
54+
CelPolicy policy = POLICY_PARSER.parse(policySource);
55+
56+
assertThat(policy.rule().matches()).hasSize(1);
57+
assertThat(Iterables.getOnlyElement(policy.rule().matches()).explanation())
58+
.hasValue(ValueString.of(11, "'custom explanation'"));
59+
}
60+
4561
@Test
4662
public void parseYamlPolicy_errors(@TestParameter PolicyParseErrorTestCase testCase) {
4763
CelPolicyValidationException e =
@@ -197,6 +213,28 @@ private enum PolicyParseErrorTestCase {
197213
"ERROR: <input>:7:7: Only the rule or the output may be set\n"
198214
+ " | output: \"world\"\n"
199215
+ " | ......^"),
216+
MATCH_NESTED_RULE_SET_THEN_EXPLANATION(
217+
"rule:\n"
218+
+ " match:\n"
219+
+ " - condition: \"true\"\n"
220+
+ " rule:\n"
221+
+ " match:\n"
222+
+ " - output: \"hello\"\n"
223+
+ " explanation: \"foo\"",
224+
"ERROR: <input>:7:7: Explanation can only be set on output match cases, not nested rules\n"
225+
+ " | explanation: \"foo\"\n"
226+
+ " | ......^"),
227+
MATCH_EXPLANATION_SET_THEN_NESTED_RULE(
228+
"rule:\n"
229+
+ " match:\n"
230+
+ " - condition: \"true\"\n"
231+
+ " explanation: \"foo\"\n"
232+
+ " rule:\n"
233+
+ " match:\n"
234+
+ " - output: \"hello\"\n",
235+
"ERROR: <input>:4:21: Explanation can only be set on output match cases, not nested rules\n"
236+
+ " | explanation: \"foo\"\n"
237+
+ " | ....................^"),
200238
INVALID_ROOT_NODE_TYPE(
201239
"- rule:\n" + " id: a",
202240
"ERROR: <input>:1:1: Got yaml node type tag:yaml.org,2002:seq, wanted type(s)"

policy/src/test/resources/nested_rule/policy.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,4 @@ rule:
3535
- condition: resource.origin in variables.permitted_regions
3636
output: "{'banned': false}"
3737
- output: "{'banned': true}"
38-
38+
explanation: "'resource is in the banned region ' + resource.origin"

0 commit comments

Comments
 (0)