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,58 @@ 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 ;
77- // Keep track of the last output ID that might cause type-check failure while attempting to
78- // compose the subgraphs.
64+ Step output = null ;
65+ // If the rule has an optional output, the last result in the ternary should return
66+ // `optional.none`. This output is implicit and created here to reflect the desired
67+ // last possible output of this type of rule.
68+ if (compiledRule .hasOptionalOutput ()) {
69+ output =
70+ Step .newUnconditionalOptionalStep (
71+ newTrueAst (cel ), astMutator .newGlobalCall (Function .OPTIONAL_NONE .getFunction ()));
72+ }
73+
7974 long lastOutputId = 0 ;
8075 for (CelCompiledMatch match : Lists .reverse (compiledRule .matches ())) {
8176 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.
8577 boolean isTriviallyTrue = match .isConditionTriviallyTrue ();
78+ CelMutableAst condAst = CelMutableAst .fromCelAst (conditionAst );
8679
8780 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.
9081 case OUTPUT :
82+ // If the output is non-nil, then it is considered a non-optional output since
83+ // it is explicitly stated. If the rule itself is optional, then the base case value
84+ // of output being optional.none() will convert the non-optional value to an optional
85+ // one.
9186 OutputValue matchOutput = match .result ().output ();
9287 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- }
102-
103- matchAst =
104- astMutator .newGlobalCall (
105- Operator .CONDITIONAL .getFunction (),
106- CelMutableAst .fromCelAst (conditionAst ),
107- outAst ,
108- matchAst );
88+ Step step = Step .newNonOptionalStep (!isTriviallyTrue , condAst , outAst );
89+ output = combine (cel , astMutator , step , output );
90+
10991 assertComposedAstIsValid (
11092 cel ,
111- matchAst ,
93+ output . expr ,
11294 "conflicting output types found." ,
11395 matchOutput .sourceId (),
11496 lastOutputId );
97+
11598 lastOutputId = matchOutput .sourceId ();
11699 continue ;
117100 case RULE :
118101 // If the match has a nested rule, then compute the rule and whether it has
119102 // an optional return value.
120103 CelCompiledRule matchNestedRule = match .result ().rule ();
121- RuleOptimizationResult nestedRule = optimizeRule (cel , matchNestedRule );
104+ Step nestedRule = optimizeRule (cel , matchNestedRule );
122105 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- }
106+
107+ Step ruleStep =
108+ nestedHasOptional
109+ ? Step .newOptionalStep (!isTriviallyTrue , condAst , nestedRule .expr )
110+ : Step .newNonOptionalStep (!isTriviallyTrue , condAst , nestedRule .expr );
111+ output = combine (cel , astMutator , ruleStep , output );
146112
147113 assertComposedAstIsValid (
148114 cel ,
149- matchAst ,
115+ output . expr ,
150116 String .format (
151117 "failed composing the subrule '%s' due to conflicting output types." ,
152118 matchNestedRule .ruleId ().map (ValueString ::value ).orElse ("" )),
@@ -155,11 +121,116 @@ private RuleOptimizationResult optimizeRule(Cel cel, CelCompiledRule compiledRul
155121 }
156122 }
157123
158- CelMutableAst result = inlineCompiledVariables (matchAst , compiledRule .variables ());
124+ CelMutableAst resultExpr = output .expr ;
125+ resultExpr = inlineCompiledVariables (resultExpr , compiledRule .variables ());
126+ resultExpr = astMutator .renumberIdsConsecutively (resultExpr );
127+
128+ return output .isOptional
129+ ? Step .newUnconditionalOptionalStep (newTrueAst (cel ), resultExpr )
130+ : Step .newUnconditionalNonOptionalStep (newTrueAst (cel ), resultExpr );
131+ }
132+
133+ static RuleComposer newInstance (
134+ CelCompiledRule compiledRule , String variablePrefix , int iterationLimit ) {
135+ return new RuleComposer (compiledRule , variablePrefix , iterationLimit );
136+ }
137+
138+ // Assembles two output expressions into a single output step.
139+ private Step combine (Cel cel , AstMutator astMutator , Step s , Step step ) {
140+ if (step == null ) {
141+ return s ;
142+ }
143+ CelMutableAst trueCondition = newTrueAst (cel );
144+
145+ if (s .isOptional ) {
146+ return combineWhenCurrentIsOptional (s , step , astMutator , trueCondition );
147+ } else {
148+ return combineWhenCurrentIsNonOptional (s , step , astMutator , trueCondition );
149+ }
150+ }
159151
160- result = astMutator .renumberIdsConsecutively (result );
152+ private Step combineWhenCurrentIsOptional (
153+ Step s , Step step , AstMutator astMutator , CelMutableAst trueCondition ) {
154+ // optional.combine(optional) // optional
155+ // (optional && conditional).combine(non-optional) // optional
156+ // (optional && unconditional).combine(non-optional) // non-optional
157+ if (step .isOptional ) {
158+ if (s .isConditional ) {
159+ return Step .newUnconditionalOptionalStep (
160+ trueCondition ,
161+ astMutator .newGlobalCall (
162+ Operator .CONDITIONAL .getFunction (), s .cond , s .expr , step .expr ));
163+ } else {
164+ if (!isOptionalNone (step .expr )) {
165+ // If either the nested rule or current condition output are optional then
166+ // use optional.or() to specify the combination of the first and second results
167+ // Note, the argument order is reversed due to the traversal of matches in
168+ // reverse order.
169+ return Step .newUnconditionalOptionalStep (
170+ trueCondition , astMutator .newMemberCall (s .expr , "or" , step .expr ));
171+ }
172+ return s ;
173+ }
174+ } else { // step is non-optional
175+ if (s .isConditional ) {
176+ return Step .newUnconditionalOptionalStep (
177+ trueCondition ,
178+ astMutator .newGlobalCall (
179+ Operator .CONDITIONAL .getFunction (),
180+ s .cond ,
181+ s .expr ,
182+ astMutator .newGlobalCall (Function .OPTIONAL_OF .getFunction (), step .expr )));
183+ } else {
184+ return Step .newUnconditionalNonOptionalStep (
185+ trueCondition , astMutator .newMemberCall (s .expr , "orValue" , step .expr ));
186+ }
187+ }
188+ }
161189
162- return RuleOptimizationResult .create (result , isOptionalResult );
190+ private Step combineWhenCurrentIsNonOptional (
191+ Step s , Step step , AstMutator astMutator , CelMutableAst trueCondition ) {
192+ // non-optional.combine(non-optional) // non-optional
193+ // (non-optional && conditional).combine(optional) // optional
194+ // (non-optional && unconditional).combine(optional) // non-optional
195+ //
196+ // The last combination case is unusual, but effectively it means that the non-optional value
197+ // prunes away
198+ // the potential optional output.
199+ if (step .isOptional ) {
200+ if (s .isConditional ) {
201+ return Step .newUnconditionalOptionalStep (
202+ trueCondition ,
203+ astMutator .newGlobalCall (
204+ Operator .CONDITIONAL .getFunction (),
205+ s .cond ,
206+ astMutator .newGlobalCall (Function .OPTIONAL_OF .getFunction (), s .expr ),
207+ step .expr ));
208+ } else {
209+ // If the condition is trivially true, none of the matches in the rule causes the result
210+ // to become optional, and the rule is not the last match, then this will introduce
211+ // unreachable outputs or rules (pruning away 'step').
212+ return s ;
213+ }
214+ } else { // step is non-optional
215+ return Step .newUnconditionalNonOptionalStep (
216+ trueCondition ,
217+ astMutator .newGlobalCall (Operator .CONDITIONAL .getFunction (), s .cond , s .expr , step .expr ));
218+ }
219+ }
220+
221+ private static boolean isOptionalNone (CelMutableAst ast ) {
222+ CelExpr expr = ast .toParsedAst ().getExpr ();
223+ return expr .getKind ().equals (Kind .CALL )
224+ && expr .call ().function ().equals ("optional.none" )
225+ && expr .call ().args ().isEmpty ();
226+ }
227+
228+ private static CelMutableAst newTrueAst (Cel cel ) {
229+ try {
230+ return CelMutableAst .fromCelAst (cel .compile ("true" ).getAst ());
231+ } catch (CelValidationException e ) {
232+ throw new IllegalStateException ("Failed to compile 'true'" , e );
233+ }
163234 }
164235
165236 private CelMutableAst inlineCompiledVariables (
@@ -186,11 +257,6 @@ private CelMutableAst inlineCompiledVariables(
186257 return mutatedAst ;
187258 }
188259
189- static RuleComposer newInstance (
190- CelCompiledRule compiledRule , String variablePrefix , int iterationLimit ) {
191- return new RuleComposer (compiledRule , variablePrefix , iterationLimit );
192- }
193-
194260 private void assertComposedAstIsValid (
195261 Cel cel , CelMutableAst composedAst , String failureMessage , Long ... ids ) {
196262 assertComposedAstIsValid (cel , composedAst , failureMessage , Arrays .asList (ids ));
@@ -206,10 +272,55 @@ private void assertComposedAstIsValid(
206272 }
207273 }
208274
209- private RuleComposer (CelCompiledRule compiledRule , String variablePrefix , int iterationLimit ) {
210- this .compiledRule = checkNotNull (compiledRule );
211- this .variablePrefix = variablePrefix ;
212- this .astMutator = AstMutator .newInstance (iterationLimit );
275+ // Step represents an intermediate stage of rule and match expression composition.
276+ //
277+ // The CelCompiledRule and CelCompiledMatch types are meant to represent standalone tuples of
278+ // condition and output expressions, and have no notion of how the order of combination would
279+ // impact composition since composition rules may vary based on the policy execution semantic,
280+ // e.g. first-match versus logical-or, logical-and, or accumulation.
281+ private static class Step {
282+ /**
283+ * Indicates whether the output step has an optional result. Individual conditional attributes
284+ * are not optional; however, rules and subrules can have optional output.
285+ */
286+ private final boolean isOptional ;
287+
288+ /** True if the condition expression is not trivially true. */
289+ private final boolean isConditional ;
290+
291+ /** The condition associated with the output. */
292+ private final CelMutableAst cond ;
293+
294+ /** The output expression for the step. */
295+ private final CelMutableAst expr ;
296+
297+ private Step (
298+ boolean isOptional , boolean isConditional , CelMutableAst cond , CelMutableAst expr ) {
299+ this .isOptional = isOptional ;
300+ this .isConditional = isConditional ;
301+ this .cond = cond ;
302+ this .expr = expr ;
303+ }
304+
305+ private static Step newOptionalStep (
306+ boolean isConditional , CelMutableAst cond , CelMutableAst expr ) {
307+ return new Step (/* isOptional= */ true , isConditional , cond , expr );
308+ }
309+
310+ private static Step newNonOptionalStep (
311+ boolean isConditional , CelMutableAst cond , CelMutableAst expr ) {
312+ return new Step (/* isOptional= */ false , isConditional , cond , expr );
313+ }
314+
315+ private static Step newUnconditionalOptionalStep (
316+ CelMutableAst trueCondition , CelMutableAst expr ) {
317+ return new Step (/* isOptional= */ true , /* isConditional= */ false , trueCondition , expr );
318+ }
319+
320+ private static Step newUnconditionalNonOptionalStep (
321+ CelMutableAst trueCondition , CelMutableAst expr ) {
322+ return new Step (/* isOptional= */ false , /* isConditional= */ false , trueCondition , expr );
323+ }
213324 }
214325
215326 static final class RuleCompositionException extends RuntimeException {
@@ -225,4 +336,10 @@ private RuleCompositionException(
225336 this .compileException = e ;
226337 }
227338 }
339+
340+ private RuleComposer (CelCompiledRule compiledRule , String variablePrefix , int iterationLimit ) {
341+ this .compiledRule = checkNotNull (compiledRule );
342+ this .variablePrefix = variablePrefix ;
343+ this .astMutator = AstMutator .newInstance (iterationLimit );
344+ }
228345}
0 commit comments