Skip to content
This repository was archived by the owner on Dec 12, 2025. It is now read-only.

Commit 4787e16

Browse files
committed
parser, replace: Support path as replace value
One of the feature is replacing with the captured state from a capture entry path. This change add support for it at the parser and create a unit test for the resolver since resolver has the feature already. Signed-off-by: Quique Llorente <ellorent@redhat.com>
1 parent ae49c34 commit 4787e16

File tree

4 files changed

+176
-41
lines changed

4 files changed

+176
-41
lines changed

nmpolicy/internal/parser/errors.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,17 +58,17 @@ func invalidPathError(msg string) *parserError {
5858
}
5959
}
6060

61-
func invalidEqualityFilterError(msg string) *parserError {
61+
func wrapWithInvalidEqualityFilterError(err error) *parserError {
6262
return &parserError{
6363
prefix: "invalid equality filter",
64-
msg: msg,
64+
inner: err,
6565
}
6666
}
6767

68-
func invalidReplaceError(msg string) *parserError {
68+
func wrapWithInvalidReplaceError(err error) *parserError {
6969
return &parserError{
7070
prefix: "invalid replace",
71-
msg: msg,
71+
inner: err,
7272
}
7373
}
7474

nmpolicy/internal/parser/parser.go

Lines changed: 23 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -200,34 +200,8 @@ func (p *parser) parseEqFilter() error {
200200
Meta: ast.Meta{Position: p.currentToken().Position},
201201
EqFilter: &ast.TernaryOperator{},
202202
}
203-
if p.lastNode == nil {
204-
return invalidEqualityFilterError("missing left hand argument")
205-
}
206-
if p.lastNode.Path == nil {
207-
return invalidEqualityFilterError("left hand argument is not a path")
208-
}
209-
210-
p.fillInPipedInOrCurrentState(&operator.EqFilter[0])
211-
212-
operator.EqFilter[1] = *p.lastNode
213-
214-
p.nextToken()
215-
216-
if p.currentToken().Type == lexer.STRING {
217-
if err := p.parseString(); err != nil {
218-
return err
219-
}
220-
operator.EqFilter[2] = *p.lastNode
221-
} else if p.currentToken().Type == lexer.IDENTITY {
222-
err := p.parsePath()
223-
if err != nil {
224-
return err
225-
}
226-
operator.EqFilter[2] = *p.lastNode
227-
} else if p.currentToken().Type == lexer.EOF {
228-
return invalidEqualityFilterError("missing right hand argument")
229-
} else {
230-
return invalidEqualityFilterError("right hand argument is not a string or identity")
203+
if err := p.fillInTernaryOperator(operator.EqFilter); err != nil {
204+
return wrapWithInvalidEqualityFilterError(err)
231205
}
232206
p.lastNode = operator
233207
return nil
@@ -238,29 +212,42 @@ func (p *parser) parseReplace() error {
238212
Meta: ast.Meta{Position: p.currentToken().Position},
239213
Replace: &ast.TernaryOperator{},
240214
}
215+
if err := p.fillInTernaryOperator(operator.Replace); err != nil {
216+
return wrapWithInvalidReplaceError(err)
217+
}
218+
p.lastNode = operator
219+
return nil
220+
}
221+
222+
func (p *parser) fillInTernaryOperator(operator *ast.TernaryOperator) error {
241223
if p.lastNode == nil {
242-
return invalidReplaceError("missing left hand argument")
224+
return fmt.Errorf("missing left hand argument")
243225
}
244226
if p.lastNode.Path == nil {
245-
return invalidReplaceError("left hand argument is not a path")
227+
return fmt.Errorf("left hand argument is not a path")
246228
}
247229

248-
p.fillInPipedInOrCurrentState(&operator.Replace[0])
230+
p.fillInPipedInOrCurrentState(&operator[0])
249231

250-
operator.Replace[1] = *p.lastNode
232+
operator[1] = *p.lastNode
251233

252234
p.nextToken()
253235
if p.currentToken().Type == lexer.STRING {
254236
if err := p.parseString(); err != nil {
255237
return err
256238
}
257-
operator.Replace[2] = *p.lastNode
239+
operator[2] = *p.lastNode
240+
} else if p.currentToken().Type == lexer.IDENTITY {
241+
err := p.parsePath()
242+
if err != nil {
243+
return err
244+
}
245+
operator[2] = *p.lastNode
258246
} else if p.currentToken().Type == lexer.EOF {
259-
return invalidReplaceError("missing right hand argument")
247+
return fmt.Errorf("missing right hand argument")
260248
} else {
261-
return invalidReplaceError("right hand argument is not a string")
249+
return fmt.Errorf("right hand argument is not a string or identity")
262250
}
263-
p.lastNode = operator
264251
return nil
265252
}
266253

nmpolicy/internal/parser/parser_test.go

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ func TestParser(t *testing.T) {
3232
testParsePath(t)
3333
testParseEqFilter(t)
3434
testParseReplace(t)
35+
testParseReplaceWithPath(t)
3536
testParseCapturePipeReplace(t)
3637

3738
testParseBasicFailures(t)
@@ -222,7 +223,7 @@ func testParseReplaceFailure(t *testing.T) {
222223
),
223224
),
224225

225-
expectError(`invalid replace: right hand argument is not a string
226+
expectError(`invalid replace: right hand argument is not a string or identity
226227
| routes.running.destination:=:=
227228
| ............................^`,
228229
fromTokens(
@@ -376,6 +377,57 @@ replace:
376377
runTest(t, tests)
377378
}
378379

380+
func testParseReplaceWithPath(t *testing.T) {
381+
var tests = []test{
382+
expectAST(t, `
383+
pos: 33
384+
replace:
385+
- pos: 0
386+
identity: currentState
387+
- pos: 0
388+
path:
389+
- pos: 0
390+
identity: routes
391+
- pos: 7
392+
identity: running
393+
- pos: 15
394+
identity: next-hop-interface
395+
- pos: 35
396+
path:
397+
- pos: 35
398+
identity: capture
399+
- pos: 43
400+
identity: primary-nic
401+
- pos: 55
402+
identity: interfaces
403+
- pos: 66
404+
number: 0
405+
- pos: 68
406+
identity: name
407+
`,
408+
fromTokens(
409+
identity("routes"),
410+
dot(),
411+
identity("running"),
412+
dot(),
413+
identity("next-hop-interface"),
414+
replace(),
415+
identity("capture"),
416+
dot(),
417+
identity("primary-nic"),
418+
dot(),
419+
identity("interfaces"),
420+
dot(),
421+
number(0),
422+
dot(),
423+
identity("name"),
424+
eof(),
425+
),
426+
),
427+
}
428+
runTest(t, tests)
429+
}
430+
379431
func testParseCapturePipeReplace(t *testing.T) {
380432
var tests = []test{
381433
expectAST(t, `

nmpolicy/internal/resolver/resolver_test.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,10 @@ func TestFilter(t *testing.T) {
135135
testFilterInvalidTypeOnPath(t)
136136
testFilterOptionalField(t)
137137
testFilterNonCaptureRefPathAtThirdArg(t)
138+
138139
testReplaceCurrentState(t)
139140
testReplaceCapturedState(t)
141+
testReplaceWithCaptureRef(t)
140142
})
141143
}
142144

@@ -727,3 +729,97 @@ bridge-routes:
727729
runTest(t, &testToRun)
728730
})
729731
}
732+
733+
func testReplaceWithCaptureRef(t *testing.T) {
734+
t.Run("Replace list of structs field from capture reference with capture reference value", func(t *testing.T) {
735+
testToRun := test{
736+
capturedStatesCache: `
737+
default-gw:
738+
state:
739+
routes:
740+
running:
741+
- destination: 0.0.0.0/0
742+
next-hop-address: 192.168.100.1
743+
next-hop-interface: br1
744+
table-id: 254
745+
746+
br1-bridge:
747+
state:
748+
interfaces:
749+
- name: br1
750+
type: linux-bridge
751+
bridge:
752+
port:
753+
- name: eth3
754+
`,
755+
756+
captureASTPool: `
757+
default-gw-br1-first-port:
758+
pos: 1
759+
replace:
760+
- pos: 2
761+
path:
762+
- pos: 3
763+
identity: capture
764+
- pos: 4
765+
identity: default-gw
766+
- pos: 3
767+
path:
768+
- pos: 4
769+
identity: routes
770+
- pos: 5
771+
identity: running
772+
- pos: 6
773+
identity: next-hop-interface
774+
- pos: 7
775+
path:
776+
- pos: 8
777+
identity: capture
778+
- pos: 9
779+
identity: br1-bridge
780+
- pos: 10
781+
identity: interfaces
782+
- pos: 11
783+
number: 0
784+
- pos: 12
785+
identity: bridge
786+
- pos: 13
787+
identity: port
788+
- pos: 14
789+
number: 0
790+
- pos: 15
791+
identity: name
792+
`,
793+
794+
expectedCapturedStates: `
795+
default-gw:
796+
state:
797+
routes:
798+
running:
799+
- destination: 0.0.0.0/0
800+
next-hop-address: 192.168.100.1
801+
next-hop-interface: br1
802+
table-id: 254
803+
804+
br1-bridge:
805+
state:
806+
interfaces:
807+
- name: br1
808+
type: linux-bridge
809+
bridge:
810+
port:
811+
- name: eth3
812+
813+
default-gw-br1-first-port:
814+
state:
815+
routes:
816+
running:
817+
- destination: 0.0.0.0/0
818+
next-hop-address: 192.168.100.1
819+
next-hop-interface: eth3
820+
table-id: 254
821+
`,
822+
}
823+
runTest(t, &testToRun)
824+
})
825+
}

0 commit comments

Comments
 (0)