1818import static com .google .common .collect .ImmutableList .toImmutableList ;
1919import static java .util .stream .Collectors .toCollection ;
2020
21- import com .google .auto .value .AutoValue ;
2221import com .google .common .collect .ImmutableList ;
2322import com .google .common .collect .Lists ;
2423import dev .cel .bundle .Cel ;
2524import dev .cel .common .CelAbstractSyntaxTree ;
2625import dev .cel .common .CelMutableAst ;
2726import dev .cel .common .CelValidationException ;
2827import dev .cel .common .Operator ;
28+ import dev .cel .common .ast .CelExpr ;
2929import dev .cel .common .ast .CelExpr .ExprKind .Kind ;
3030import dev .cel .common .formats .ValueString ;
3131import dev .cel .common .navigation .CelNavigableMutableAst ;
@@ -48,22 +48,11 @@ final class RuleComposer implements CelAstOptimizer {
4848
4949 @ Override
5050 public OptimizationResult optimize (CelAbstractSyntaxTree ast , Cel cel ) {
51- RuleOptimizationResult result = optimizeRule (cel , compiledRule );
52- return OptimizationResult .create (result .ast () .toParsedAst ());
51+ Step result = optimizeRule (cel , compiledRule );
52+ return OptimizationResult .create (result .expr .toParsedAst ());
5353 }
5454
55- @ AutoValue
56- abstract static class RuleOptimizationResult {
57- abstract CelMutableAst ast ();
58-
59- abstract boolean isOptionalResult ();
60-
61- static RuleOptimizationResult create (CelMutableAst ast , boolean isOptionalResult ) {
62- return new AutoValue_RuleComposer_RuleOptimizationResult (ast , isOptionalResult );
63- }
64- }
65-
66- private RuleOptimizationResult optimizeRule (Cel cel , CelCompiledRule compiledRule ) {
55+ private Step optimizeRule (Cel cel , CelCompiledRule compiledRule ) {
6756 cel =
6857 cel .toCelBuilder ()
6958 .addVarDeclarations (
@@ -72,81 +61,52 @@ private RuleOptimizationResult optimizeRule(Cel cel, CelCompiledRule compiledRul
7261 .collect (toImmutableList ()))
7362 .build ();
7463
75- CelMutableAst matchAst = astMutator .newGlobalCall (Function .OPTIONAL_NONE .getFunction ());
76- boolean isOptionalResult = true ;
64+ Step output = null ;
65+ if (compiledRule .hasOptionalOutput ()) {
66+ output =
67+ new Step (
68+ true ,
69+ false ,
70+ newTrueAst (cel ),
71+ astMutator .newGlobalCall (Function .OPTIONAL_NONE .getFunction ()));
72+ }
73+
7774 // Keep track of the last output ID that might cause type-check failure while attempting to
7875 // compose the subgraphs.
7976 long lastOutputId = 0 ;
77+
8078 for (CelCompiledMatch match : Lists .reverse (compiledRule .matches ())) {
8179 CelAbstractSyntaxTree conditionAst = match .condition ();
82- // If the condition is trivially true, none of the matches in the rule causes the result
83- // to become optional, and the rule is not the last match, then this will introduce
84- // unreachable outputs or rules.
8580 boolean isTriviallyTrue = match .isConditionTriviallyTrue ();
81+ CelMutableAst condAst = CelMutableAst .fromCelAst (conditionAst );
8682
8783 switch (match .result ().kind ()) {
88- // For the match's output, determine whether the output should be wrapped
89- // into an optional value, a conditional, or both.
9084 case OUTPUT :
9185 OutputValue matchOutput = match .result ().output ();
9286 CelMutableAst outAst = CelMutableAst .fromCelAst (matchOutput .ast ());
93- if (isTriviallyTrue ) {
94- matchAst = outAst ;
95- isOptionalResult = false ;
96- lastOutputId = matchOutput .sourceId ();
97- continue ;
98- }
99- if (isOptionalResult ) {
100- outAst = astMutator .newGlobalCall (Function .OPTIONAL_OF .getFunction (), outAst );
101- }
87+ Step step = new Step (false , !isTriviallyTrue , condAst , outAst );
88+ output = combine (cel , astMutator , step , output );
10289
103- matchAst =
104- astMutator .newGlobalCall (
105- Operator .CONDITIONAL .getFunction (),
106- CelMutableAst .fromCelAst (conditionAst ),
107- outAst ,
108- matchAst );
10990 assertComposedAstIsValid (
11091 cel ,
111- matchAst ,
92+ output . expr ,
11293 "conflicting output types found." ,
11394 matchOutput .sourceId (),
11495 lastOutputId );
96+
11597 lastOutputId = matchOutput .sourceId ();
11698 continue ;
11799 case RULE :
118- // If the match has a nested rule, then compute the rule and whether it has
119- // an optional return value.
120100 CelCompiledRule matchNestedRule = match .result ().rule ();
121- RuleOptimizationResult nestedRule = optimizeRule (cel , matchNestedRule );
101+ Step nestedRule = optimizeRule (cel , matchNestedRule );
122102 boolean nestedHasOptional = matchNestedRule .hasOptionalOutput ();
123- CelMutableAst nestedRuleAst = nestedRule .ast ();
124- if (isOptionalResult && !nestedHasOptional ) {
125- nestedRuleAst =
126- astMutator .newGlobalCall (Function .OPTIONAL_OF .getFunction (), nestedRuleAst );
127- }
128- if (!isOptionalResult && nestedHasOptional ) {
129- matchAst = astMutator .newGlobalCall (Function .OPTIONAL_OF .getFunction (), matchAst );
130- isOptionalResult = true ;
131- }
132- // If either the nested rule or current condition output are optional then
133- // use optional.or() to specify the combination of the first and second results
134- // Note, the argument order is reversed due to the traversal of matches in
135- // reverse order.
136- if (isOptionalResult && isTriviallyTrue ) {
137- matchAst = astMutator .newMemberCall (nestedRuleAst , Function .OR .getFunction (), matchAst );
138- } else {
139- matchAst =
140- astMutator .newGlobalCall (
141- Operator .CONDITIONAL .getFunction (),
142- CelMutableAst .fromCelAst (conditionAst ),
143- nestedRuleAst ,
144- matchAst );
145- }
103+
104+ Step ruleStep = new Step (nestedHasOptional , !isTriviallyTrue , condAst , nestedRule .expr );
105+ output = combine (cel , astMutator , ruleStep , output );
146106
147107 assertComposedAstIsValid (
148108 cel ,
149- matchAst ,
109+ output . expr ,
150110 String .format (
151111 "failed composing the subrule '%s' due to conflicting output types." ,
152112 matchNestedRule .ruleId ().map (ValueString ::value ).orElse ("" )),
@@ -155,11 +115,106 @@ private RuleOptimizationResult optimizeRule(Cel cel, CelCompiledRule compiledRul
155115 }
156116 }
157117
158- CelMutableAst result = inlineCompiledVariables (matchAst , compiledRule .variables ());
118+ CelMutableAst resultExpr = output .expr ;
119+ resultExpr = inlineCompiledVariables (resultExpr , compiledRule .variables ());
120+ resultExpr = astMutator .renumberIdsConsecutively (resultExpr );
159121
160- result = astMutator .renumberIdsConsecutively (result );
122+ return new Step (output .isOptional , false , newTrueAst (cel ), resultExpr );
123+ }
161124
162- return RuleOptimizationResult .create (result , isOptionalResult );
125+ static RuleComposer newInstance (
126+ CelCompiledRule compiledRule , String variablePrefix , int iterationLimit ) {
127+ return new RuleComposer (compiledRule , variablePrefix , iterationLimit );
128+ }
129+
130+ // For the match's output, determine whether the output should be wrapped
131+ // into an optional value, a conditional, or both.
132+ //
133+ // Assembles two output expressions into a single output step.
134+ private Step combine (Cel cel , AstMutator astMutator , Step s , Step step ) {
135+ if (step == null ) {
136+ return s ;
137+ }
138+ CelMutableAst trueCondition = newTrueAst (cel );
139+
140+ if (s .isOptional ) {
141+ if (step .isOptional ) {
142+ if (s .isConditional ) {
143+ return new Step (
144+ true ,
145+ false ,
146+ trueCondition ,
147+ astMutator .newGlobalCall (
148+ Operator .CONDITIONAL .getFunction (), s .cond , s .expr , step .expr ));
149+ } else {
150+ if (!isOptionalNone (step .expr )) {
151+ // If either the nested rule or current condition output are optional then
152+ // use optional.or() to specify the combination of the first and second results
153+ // Note, the argument order is reversed due to the traversal of matches in
154+ // reverse order.
155+ return new Step (
156+ true , false , trueCondition , astMutator .newMemberCall (s .expr , "or" , step .expr ));
157+ }
158+ return s ;
159+ }
160+ } else { // step is non-optional
161+ if (s .isConditional ) {
162+ return new Step (
163+ true ,
164+ false ,
165+ trueCondition ,
166+ astMutator .newGlobalCall (
167+ Operator .CONDITIONAL .getFunction (),
168+ s .cond ,
169+ s .expr ,
170+ astMutator .newGlobalCall (Function .OPTIONAL_OF .getFunction (), step .expr )));
171+ } else {
172+ return new Step (
173+ false , false , trueCondition , astMutator .newMemberCall (s .expr , "orValue" , step .expr ));
174+ }
175+ }
176+ } else { // s is non-optional
177+ if (step .isOptional ) {
178+ if (s .isConditional ) {
179+ return new Step (
180+ true ,
181+ false ,
182+ trueCondition ,
183+ astMutator .newGlobalCall (
184+ Operator .CONDITIONAL .getFunction (),
185+ s .cond ,
186+ astMutator .newGlobalCall (Function .OPTIONAL_OF .getFunction (), s .expr ),
187+ step .expr ));
188+ } else {
189+ // If the condition is trivially true, none of the matches in the rule causes the result
190+ // to become optional, and the rule is not the last match, then this will introduce
191+ // unreachable outputs or rules (pruning away 'step').
192+ return s ;
193+ }
194+ } else { // step is non-optional
195+ return new Step (
196+ false ,
197+ false ,
198+ trueCondition ,
199+ astMutator .newGlobalCall (
200+ Operator .CONDITIONAL .getFunction (), s .cond , s .expr , step .expr ));
201+ }
202+ }
203+ }
204+
205+ private static boolean isOptionalNone (CelMutableAst ast ) {
206+ CelExpr expr = ast .toParsedAst ().getExpr ();
207+ return expr .getKind ().equals (Kind .CALL )
208+ && expr .call ().function ().equals ("optional.none" )
209+ && expr .call ().args ().isEmpty ();
210+ }
211+
212+ private static CelMutableAst newTrueAst (Cel cel ) {
213+ try {
214+ return CelMutableAst .fromCelAst (cel .compile ("true" ).getAst ());
215+ } catch (CelValidationException e ) {
216+ throw new IllegalStateException ("Failed to compile 'true'" , e );
217+ }
163218 }
164219
165220 private CelMutableAst inlineCompiledVariables (
@@ -186,11 +241,6 @@ private CelMutableAst inlineCompiledVariables(
186241 return mutatedAst ;
187242 }
188243
189- static RuleComposer newInstance (
190- CelCompiledRule compiledRule , String variablePrefix , int iterationLimit ) {
191- return new RuleComposer (compiledRule , variablePrefix , iterationLimit );
192- }
193-
194244 private void assertComposedAstIsValid (
195245 Cel cel , CelMutableAst composedAst , String failureMessage , Long ... ids ) {
196246 assertComposedAstIsValid (cel , composedAst , failureMessage , Arrays .asList (ids ));
@@ -206,10 +256,35 @@ private void assertComposedAstIsValid(
206256 }
207257 }
208258
209- private RuleComposer (CelCompiledRule compiledRule , String variablePrefix , int iterationLimit ) {
210- this .compiledRule = checkNotNull (compiledRule );
211- this .variablePrefix = variablePrefix ;
212- this .astMutator = AstMutator .newInstance (iterationLimit );
259+ // Step represents an intermediate stage of rule and match expression composition.
260+ //
261+ // The CelCompiledRule and CelCompiledMatch types are meant to represent standalone tuples of
262+ // condition and output expressions, and have no notion of how the order of combination would
263+ // impact composition since composition rules may vary based on the policy execution semantic,
264+ // e.g. first-match versus logical-or, logical-and, or accumulation.
265+ private static class Step {
266+ /**
267+ * Indicates whether the output step has an optional result. Individual conditional attributes
268+ * are not optional; however, rules and subrules can have optional output.
269+ */
270+ private final boolean isOptional ;
271+
272+ /** True if the condition expression is not trivially true. */
273+ private final boolean isConditional ;
274+
275+ /** The condition associated with the output. */
276+ private final CelMutableAst cond ;
277+
278+ /** The output expression for the step. */
279+ private final CelMutableAst expr ;
280+
281+ private Step (
282+ boolean isOptional , boolean isConditional , CelMutableAst cond , CelMutableAst expr ) {
283+ this .isOptional = isOptional ;
284+ this .isConditional = isConditional ;
285+ this .cond = cond ;
286+ this .expr = expr ;
287+ }
213288 }
214289
215290 static final class RuleCompositionException extends RuntimeException {
@@ -225,4 +300,10 @@ private RuleCompositionException(
225300 this .compileException = e ;
226301 }
227302 }
303+
304+ private RuleComposer (CelCompiledRule compiledRule , String variablePrefix , int iterationLimit ) {
305+ this .compiledRule = checkNotNull (compiledRule );
306+ this .variablePrefix = variablePrefix ;
307+ this .astMutator = AstMutator .newInstance (iterationLimit );
308+ }
228309}
0 commit comments