From 15640a7b5c4a32257e649b09816bc0ec5ec9aa41 Mon Sep 17 00:00:00 2001 From: Sean Cunningham Date: Mon, 5 Jan 2026 09:54:37 -0500 Subject: [PATCH] Disallow multiple origin nodes. --- pkg/ast/ast.go | 14 +++++++++----- pkg/ast/ast_metrics.go | 3 --- pkg/ast/ast_test.go | 6 ++++++ pkg/testdata/rules.go | 26 ++++++++++++++++++++++++++ 4 files changed, 41 insertions(+), 8 deletions(-) diff --git a/pkg/ast/ast.go b/pkg/ast/ast.go index 239140b..bcdd637 100644 --- a/pkg/ast/ast.go +++ b/pkg/ast/ast.go @@ -25,6 +25,7 @@ var ( ErrRootNodeWithoutEventSrc = errors.New("root node has no event source") ErrInvalidWindow = errors.New("invalid window") ErrMissingOrigin = errors.New("missing origin event") + ErrMultipleOrigin = errors.New("multiple origin events") ErrInvalidAnchor = errors.New("invalid negate anchor") ErrNoTermIdx = errors.New("no term idx") ) @@ -87,14 +88,14 @@ type AstEventT struct { type builderT struct { CurrentNodeId uint32 CurrentDepth uint32 - HasOrigin bool + OriginCnt int } func NewBuilder() *builderT { return &builderT{ CurrentNodeId: uint32(0), CurrentDepth: uint32(0), - HasOrigin: false, + OriginCnt: 0, } } @@ -140,8 +141,11 @@ func BuildTree(tree *parser.TreeT) (*AstT, error) { return nil, err } - if !rb.HasOrigin { + switch { + case rb.OriginCnt == 0: return nil, parserNode.WrapError(ErrMissingOrigin) + case rb.OriginCnt > 1: + return nil, parserNode.WrapError(ErrMultipleOrigin) } ast.Nodes = append(ast.Nodes, rule) @@ -235,7 +239,7 @@ func (b *builderT) buildMatcherChildren(parserNode *parser.NodeT, machineAddress } // Implied that the root node has an origin event - b.HasOrigin = true + b.OriginCnt++ parserNode.Metadata.Event.Origin = true err = b.descendTree(func() error { @@ -316,7 +320,7 @@ func (b *builderT) buildMachineChildren(parserNode *parser.NodeT, machineAddress // If the child has an event/data source, then it is not a state machine. Build it via buildMatcherNodes if parserChildNode.Metadata.Event.Origin { - b.HasOrigin = true + b.OriginCnt++ } if parserChildNode.Metadata.Event.Source == "" { diff --git a/pkg/ast/ast_metrics.go b/pkg/ast/ast_metrics.go index 5249a0d..b381c4f 100644 --- a/pkg/ast/ast_metrics.go +++ b/pkg/ast/ast_metrics.go @@ -41,9 +41,6 @@ func (b *builderT) buildPromQLNode(parserNode *parser.NodeT, machineAddress *Ast } if parserNode.Metadata.Event != nil { - if parserNode.Metadata.Event.Origin { - b.HasOrigin = true - } pn.Event = &AstEventT{ Source: parserNode.Metadata.Event.Source, Origin: parserNode.Metadata.Event.Origin, diff --git a/pkg/ast/ast_test.go b/pkg/ast/ast_test.go index 5d49271..58a5bf6 100644 --- a/pkg/ast/ast_test.go +++ b/pkg/ast/ast_test.go @@ -185,6 +185,12 @@ func TestAstFail(t *testing.T) { line: 11, col: 9, }, + "Fail_MultipleOrigin": { + rule: testdata.TestFailMultipleOrigin, + err: ErrMultipleOrigin, + line: 11, + col: 17, + }, } for name, test := range tests { diff --git a/pkg/testdata/rules.go b/pkg/testdata/rules.go index 9bdf804..cb302be 100644 --- a/pkg/testdata/rules.go +++ b/pkg/testdata/rules.go @@ -1106,3 +1106,29 @@ rules: match: - regex: "io.vertx.core.VertxException: Thread blocked" ` + +var TestFailMultipleOrigin = ` +rules: + - cre: + id: TestFailMultipleOrigin + metadata: + id: "J7uRQTGpGMyL1iFpssnB3S" + hash: "rdJLgqYgkEp8jg8Qks1qqq" + generation: 1 + rule: + set: + window: 50s + match: + - promql: + event: + source: cre.metrics + origin: true + expr: 'sum(rate(http_requests_total[5m])) by (service)' + interval: 10s + - set: + event: + source: kafka + origin: true + match: + - regex: "io.vertx.core.VertxException: Thread blocked" +`