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,22 @@ 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 .addMatches (parseMatches (ctx , policyBuilder , value , false ));
232+ ruleBuilder .setSemantic (EvaluationSemantic .FIRST_MATCH );
225233 break ;
234+ case "aggregate" :
235+ if (hasMatch ) {
236+ ctx .reportError (tagId , "Only one of 'match' or 'aggregate' may be set in a rule" );
237+ }
238+ hasAggregate = true ;
239+ ruleBuilder .addMatches (parseMatches (ctx , policyBuilder , value , true ));
240+ ruleBuilder .setSemantic (EvaluationSemantic .AGGREGATE );
241+ break ;
242+
226243 default :
227244 tagVisitor .visitRuleTag (ctx , tagId , fieldName , value , policyBuilder , ruleBuilder );
228245 break ;
@@ -232,7 +249,10 @@ public CelPolicy.Rule parseRule(
232249 }
233250
234251 private ImmutableSet <CelPolicy .Match > parseMatches (
235- PolicyParserContext <Node > ctx , CelPolicy .Builder policyBuilder , Node node ) {
252+ PolicyParserContext <Node > ctx ,
253+ CelPolicy .Builder policyBuilder ,
254+ Node node ,
255+ boolean isAggregate ) {
236256 long valueId = ctx .collectMetadata (node );
237257 ImmutableSet .Builder <CelPolicy .Match > matchesBuilder = ImmutableSet .builder ();
238258 if (!assertYamlType (ctx , valueId , node , YamlNodeType .LIST )) {
@@ -241,7 +261,7 @@ private ImmutableSet<CelPolicy.Match> parseMatches(
241261
242262 SequenceNode matchListNode = (SequenceNode ) node ;
243263 for (Node elementNode : matchListNode .getValue ()) {
244- matchesBuilder .add (parseMatch (ctx , policyBuilder , elementNode ));
264+ matchesBuilder .add (parseMatchInternal (ctx , policyBuilder , elementNode , isAggregate ));
245265 }
246266
247267 return matchesBuilder .build ();
@@ -250,6 +270,14 @@ private ImmutableSet<CelPolicy.Match> parseMatches(
250270 @ Override
251271 public CelPolicy .Match parseMatch (
252272 PolicyParserContext <Node > ctx , CelPolicy .Builder policyBuilder , Node node ) {
273+ return parseMatchInternal (ctx , policyBuilder , node , false );
274+ }
275+
276+ private CelPolicy .Match parseMatchInternal (
277+ PolicyParserContext <Node > ctx ,
278+ CelPolicy .Builder policyBuilder ,
279+ Node node ,
280+ boolean isAggregate ) {
253281 long nodeId = ctx .collectMetadata (node );
254282 if (!assertYamlType (ctx , nodeId , node , YamlNodeType .MAP )) {
255283 return ERROR_MATCH ;
@@ -270,6 +298,20 @@ public CelPolicy.Match parseMatch(
270298 matchBuilder .setCondition (ctx .newSourceString (value ));
271299 break ;
272300 case "output" :
301+ if (isAggregate ) {
302+ ctx .reportError (tagId , "Rule aggregate requires 'emit' tag instead of 'output'" );
303+ }
304+ matchBuilder
305+ .result ()
306+ .filter (result -> result .kind ().equals (Match .Result .Kind .RULE ))
307+ .ifPresent (
308+ result -> ctx .reportError (tagId , "Only the rule or the output may be set" ));
309+ matchBuilder .setResult (Match .Result .ofOutput (ctx .newSourceString (value )));
310+ break ;
311+ case "emit" :
312+ if (!isAggregate ) {
313+ ctx .reportError (tagId , "Rule match requires 'output' tag instead of 'emit'" );
314+ }
273315 matchBuilder
274316 .result ()
275317 .filter (result -> result .kind ().equals (Match .Result .Kind .RULE ))
@@ -409,6 +451,8 @@ private Variable parseVariableObject(
409451 return builder .build ();
410452 }
411453
454+
455+
412456 private ParserImpl (
413457 TagVisitor <Node > tagVisitor ,
414458 boolean enableSimpleVariables ,
0 commit comments