2727import dev .cel .common .formats .YamlHelper .YamlNodeType ;
2828import dev .cel .common .formats .YamlParserContextImpl ;
2929import dev .cel .common .internal .CelCodePointArray ;
30+ import dev .cel .policy .CelPolicy .EvaluationSemantic ;
3031import dev .cel .policy .CelPolicy .Import ;
3132import dev .cel .policy .CelPolicy .Match ;
3233import dev .cel .policy .CelPolicy .Match .Result ;
@@ -202,6 +203,8 @@ public CelPolicy.Rule parseRule(
202203 return ruleBuilder .build ();
203204 }
204205
206+ boolean hasMatch = false ;
207+ boolean hasAggregate = false ;
205208 for (NodeTuple nodeTuple : ((MappingNode ) node ).getValue ()) {
206209 Node key = nodeTuple .getKeyNode ();
207210 long tagId = ctx .collectMetadata (key );
@@ -221,8 +224,24 @@ public CelPolicy.Rule parseRule(
221224 ruleBuilder .addVariables (parseVariables (ctx , policyBuilder , value ));
222225 break ;
223226 case "match" :
224- ruleBuilder .addMatches (parseMatches (ctx , policyBuilder , value ));
227+ if (hasAggregate ) {
228+ ctx .reportError (tagId , "Only one of 'match' or 'aggregate' may be set in a rule" );
229+ }
230+ hasMatch = true ;
231+ ruleBuilder
232+ .addMatches (parseMatches (ctx , policyBuilder , value , false ))
233+ .setSemantic (EvaluationSemantic .FIRST_MATCH );
225234 break ;
235+ case "aggregate" :
236+ if (hasMatch ) {
237+ ctx .reportError (tagId , "Only one of 'match' or 'aggregate' may be set in a rule" );
238+ }
239+ hasAggregate = true ;
240+ ruleBuilder
241+ .addMatches (parseMatches (ctx , policyBuilder , value , true ))
242+ .setSemantic (EvaluationSemantic .AGGREGATE );
243+ break ;
244+
226245 default :
227246 tagVisitor .visitRuleTag (ctx , tagId , fieldName , value , policyBuilder , ruleBuilder );
228247 break ;
@@ -232,7 +251,10 @@ public CelPolicy.Rule parseRule(
232251 }
233252
234253 private ImmutableSet <CelPolicy .Match > parseMatches (
235- PolicyParserContext <Node > ctx , CelPolicy .Builder policyBuilder , Node node ) {
254+ PolicyParserContext <Node > ctx ,
255+ CelPolicy .Builder policyBuilder ,
256+ Node node ,
257+ boolean isAggregate ) {
236258 long valueId = ctx .collectMetadata (node );
237259 ImmutableSet .Builder <CelPolicy .Match > matchesBuilder = ImmutableSet .builder ();
238260 if (!assertYamlType (ctx , valueId , node , YamlNodeType .LIST )) {
@@ -241,7 +263,7 @@ private ImmutableSet<CelPolicy.Match> parseMatches(
241263
242264 SequenceNode matchListNode = (SequenceNode ) node ;
243265 for (Node elementNode : matchListNode .getValue ()) {
244- matchesBuilder .add (parseMatch (ctx , policyBuilder , elementNode ));
266+ matchesBuilder .add (parseMatchInternal (ctx , policyBuilder , elementNode , isAggregate ));
245267 }
246268
247269 return matchesBuilder .build ();
@@ -250,6 +272,14 @@ private ImmutableSet<CelPolicy.Match> parseMatches(
250272 @ Override
251273 public CelPolicy .Match parseMatch (
252274 PolicyParserContext <Node > ctx , CelPolicy .Builder policyBuilder , Node node ) {
275+ return parseMatchInternal (ctx , policyBuilder , node , false );
276+ }
277+
278+ private CelPolicy .Match parseMatchInternal (
279+ PolicyParserContext <Node > ctx ,
280+ CelPolicy .Builder policyBuilder ,
281+ Node node ,
282+ boolean isAggregate ) {
253283 long nodeId = ctx .collectMetadata (node );
254284 if (!assertYamlType (ctx , nodeId , node , YamlNodeType .MAP )) {
255285 return ERROR_MATCH ;
@@ -270,6 +300,20 @@ public CelPolicy.Match parseMatch(
270300 matchBuilder .setCondition (ctx .newSourceString (value ));
271301 break ;
272302 case "output" :
303+ if (isAggregate ) {
304+ ctx .reportError (tagId , "Rule aggregate requires 'emit' tag instead of 'output'" );
305+ }
306+ matchBuilder
307+ .result ()
308+ .filter (result -> result .kind ().equals (Match .Result .Kind .RULE ))
309+ .ifPresent (
310+ result -> ctx .reportError (tagId , "Only the rule or the output may be set" ));
311+ matchBuilder .setResult (Match .Result .ofOutput (ctx .newSourceString (value )));
312+ break ;
313+ case "emit" :
314+ if (!isAggregate ) {
315+ ctx .reportError (tagId , "Rule match requires 'output' tag instead of 'emit'" );
316+ }
273317 matchBuilder
274318 .result ()
275319 .filter (result -> result .kind ().equals (Match .Result .Kind .RULE ))
@@ -409,6 +453,8 @@ private Variable parseVariableObject(
409453 return builder .build ();
410454 }
411455
456+
457+
412458 private ParserImpl (
413459 TagVisitor <Node > tagVisitor ,
414460 boolean enableSimpleVariables ,
0 commit comments