2828import dev .cel .common .formats .YamlHelper .YamlNodeType ;
2929import dev .cel .common .formats .YamlParserContextImpl ;
3030import dev .cel .common .internal .CelCodePointArray ;
31+ import dev .cel .policy .CelPolicy .EvaluationSemantic ;
3132import dev .cel .policy .CelPolicy .Import ;
3233import dev .cel .policy .CelPolicy .Invariant ;
3334import dev .cel .policy .CelPolicy .Match ;
@@ -271,6 +272,8 @@ public CelPolicy.Rule parseRule(
271272 return ruleBuilder .build ();
272273 }
273274
275+ boolean hasMatch = false ;
276+ boolean hasAggregate = false ;
274277 for (NodeTuple nodeTuple : ((MappingNode ) node ).getValue ()) {
275278 Node key = nodeTuple .getKeyNode ();
276279 long tagId = ctx .collectMetadata (key );
@@ -290,8 +293,24 @@ public CelPolicy.Rule parseRule(
290293 ruleBuilder .addVariables (parseVariables (ctx , policyBuilder , value ));
291294 break ;
292295 case "match" :
293- ruleBuilder .addMatches (parseMatches (ctx , policyBuilder , value ));
296+ if (hasAggregate ) {
297+ ctx .reportError (tagId , "Only one of 'match' or 'aggregate' may be set in a rule" );
298+ }
299+ hasMatch = true ;
300+ ruleBuilder
301+ .addMatches (parseMatches (ctx , policyBuilder , value , false ))
302+ .setSemantic (EvaluationSemantic .FIRST_MATCH );
303+ break ;
304+ case "aggregate" :
305+ if (hasMatch ) {
306+ ctx .reportError (tagId , "Only one of 'match' or 'aggregate' may be set in a rule" );
307+ }
308+ hasAggregate = true ;
309+ ruleBuilder
310+ .addMatches (parseMatches (ctx , policyBuilder , value , true ))
311+ .setSemantic (EvaluationSemantic .AGGREGATE );
294312 break ;
313+
295314 default :
296315 tagVisitor .visitRuleTag (ctx , tagId , fieldName , value , policyBuilder , ruleBuilder );
297316 break ;
@@ -301,7 +320,10 @@ public CelPolicy.Rule parseRule(
301320 }
302321
303322 private ImmutableSet <CelPolicy .Match > parseMatches (
304- PolicyParserContext <Node > ctx , CelPolicy .Builder policyBuilder , Node node ) {
323+ PolicyParserContext <Node > ctx ,
324+ CelPolicy .Builder policyBuilder ,
325+ Node node ,
326+ boolean isAggregate ) {
305327 long valueId = ctx .collectMetadata (node );
306328 ImmutableSet .Builder <CelPolicy .Match > matchesBuilder = ImmutableSet .builder ();
307329 if (!assertYamlType (ctx , valueId , node , YamlNodeType .LIST )) {
@@ -310,7 +332,7 @@ private ImmutableSet<CelPolicy.Match> parseMatches(
310332
311333 SequenceNode matchListNode = (SequenceNode ) node ;
312334 for (Node elementNode : matchListNode .getValue ()) {
313- matchesBuilder .add (parseMatch (ctx , policyBuilder , elementNode ));
335+ matchesBuilder .add (parseMatchInternal (ctx , policyBuilder , elementNode , isAggregate ));
314336 }
315337
316338 return matchesBuilder .build ();
@@ -319,6 +341,14 @@ private ImmutableSet<CelPolicy.Match> parseMatches(
319341 @ Override
320342 public CelPolicy .Match parseMatch (
321343 PolicyParserContext <Node > ctx , CelPolicy .Builder policyBuilder , Node node ) {
344+ return parseMatchInternal (ctx , policyBuilder , node , false );
345+ }
346+
347+ private CelPolicy .Match parseMatchInternal (
348+ PolicyParserContext <Node > ctx ,
349+ CelPolicy .Builder policyBuilder ,
350+ Node node ,
351+ boolean isAggregate ) {
322352 long nodeId = ctx .collectMetadata (node );
323353 if (!assertYamlType (ctx , nodeId , node , YamlNodeType .MAP )) {
324354 return ERROR_MATCH ;
@@ -339,6 +369,20 @@ public CelPolicy.Match parseMatch(
339369 matchBuilder .setCondition (ctx .newSourceString (value ));
340370 break ;
341371 case "output" :
372+ if (isAggregate ) {
373+ ctx .reportError (tagId , "Rule aggregate requires 'emit' tag instead of 'output'" );
374+ }
375+ matchBuilder
376+ .result ()
377+ .filter (result -> result .kind ().equals (Match .Result .Kind .RULE ))
378+ .ifPresent (
379+ result -> ctx .reportError (tagId , "Only the rule or the output may be set" ));
380+ matchBuilder .setResult (Match .Result .ofOutput (ctx .newSourceString (value )));
381+ break ;
382+ case "emit" :
383+ if (!isAggregate ) {
384+ ctx .reportError (tagId , "Rule match requires 'output' tag instead of 'emit'" );
385+ }
342386 matchBuilder
343387 .result ()
344388 .filter (result -> result .kind ().equals (Match .Result .Kind .RULE ))
0 commit comments