From c2b58717b8732fa63395aee383463d4aca56b206 Mon Sep 17 00:00:00 2001 From: quobix Date: Fri, 5 Jul 2024 13:21:00 -0400 Subject: [PATCH] Addressed issue with anchors and merge nodes Highlighted in https://github.com/daveshanley/vacuum/issues/508 The comment from @Jakousa annoyed me. Rude. --- utils/utils.go | 106 +++++++++++++++++++++++++------ utils/utils_test.go | 150 ++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 223 insertions(+), 33 deletions(-) diff --git a/utils/utils.go b/utils/utils.go index fdb041b..61c357d 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -221,18 +221,21 @@ func FindFirstKeyNode(key string, nodes []*yaml.Node, depth int) (keyNode *yaml. if depth > 40 { return nil, nil } + if nodes != nil && len(nodes) > 0 && nodes[0].Tag == "!!merge" { + nodes = NodeAlias(nodes[1]).Content + } for i, v := range nodes { if key != "" && key == v.Value { if i+1 >= len(nodes) { return v, NodeAlias(nodes[i]) // this is the node we need. } - return v, NodeAlias(nodes[i+1]) // next node is what we need. + return NodeAlias(v), NodeAlias(nodes[i+1]) // next node is what we need. } if len(v.Content) > 0 { depth++ x, y := FindFirstKeyNode(key, v.Content, depth) if x != nil && y != nil { - return x, y + return NodeAlias(x), NodeAlias(y) } } } @@ -258,16 +261,19 @@ type KeyNodeSearch struct { // FindKeyNodeTop is a non-recursive search of top level nodes for a key, will not look at content. // Returns the key and value func FindKeyNodeTop(key string, nodes []*yaml.Node) (keyNode *yaml.Node, valueNode *yaml.Node) { - for i, v := range nodes { + if nodes != nil && len(nodes) > 0 && nodes[0].Tag == "!!merge" { + nodes = NodeAlias(nodes[1]).Content + } + for i := 0; i < len(nodes); i++ { + v := nodes[i] if i%2 != 0 { continue } - if strings.EqualFold(key, v.Value) { if i+1 >= len(nodes) { - return v, nodes[i] + return NodeAlias(v), NodeAlias(nodes[i]) } - return v, nodes[i+1] // next node is what we need. + return NodeAlias(v), NodeAlias(nodes[i+1]) // next node is what we need. } } return nil, nil @@ -276,25 +282,27 @@ func FindKeyNodeTop(key string, nodes []*yaml.Node) (keyNode *yaml.Node, valueNo // FindKeyNode is a non-recursive search of a *yaml.Node Content for a child node with a key. // Returns the key and value func FindKeyNode(key string, nodes []*yaml.Node) (keyNode *yaml.Node, valueNode *yaml.Node) { - // numNodes := len(nodes) + if nodes != nil && len(nodes) > 0 && nodes[0].Tag == "!!merge" { + nodes = NodeAlias(nodes[1]).Content + } for i, v := range nodes { if i%2 == 0 && key == v.Value { if len(nodes) <= i+1 { - return v, nodes[i] + return NodeAlias(v), NodeAlias(nodes[i]) } - return v, nodes[i+1] // next node is what we need. + return NodeAlias(v), NodeAlias(nodes[i+1]) // next node is what we need. } for x, j := range v.Content { if key == j.Value { if IsNodeMap(v) { if x+1 == len(v.Content) { - return v, v.Content[x] + return NodeAlias(v), NodeAlias(v.Content[x]) } - return v, v.Content[x+1] // next node is what we need. + return NodeAlias(v), NodeAlias(v.Content[x+1]) // next node is what we need. } if IsNodeArray(v) { - return v, v.Content[x] + return NodeAlias(v), NodeAlias(v.Content[x]) } } } @@ -306,25 +314,37 @@ func FindKeyNode(key string, nodes []*yaml.Node) (keyNode *yaml.Node, valueNode // generally different things are required from different node trees, so depending on what this function is looking at // it will return different things. func FindKeyNodeFull(key string, nodes []*yaml.Node) (keyNode *yaml.Node, labelNode *yaml.Node, valueNode *yaml.Node) { - for i := range nodes { + if nodes != nil && len(nodes) > 0 && nodes[0].Tag == "!!merge" { + nodes = NodeAlias(nodes[1]).Content + } + for i := 0; i < len(nodes); i++ { if i%2 == 0 && key == nodes[i].Value { if i+1 >= len(nodes) { - return nodes[i], nodes[i], nodes[i] + return NodeAlias(nodes[i]), NodeAlias(nodes[i]), NodeAlias(nodes[i]) } - return nodes[i], nodes[i], nodes[i+1] // next node is what we need. + return NodeAlias(nodes[i]), NodeAlias(nodes[i]), NodeAlias(nodes[i+1]) // next node is what we need. } } for _, v := range nodes { - for x := range v.Content { + for x := 0; x < len(v.Content); x++ { + r := v.Content[x] + if x%2 == 0 { + if r.Tag == "!!merge" { + if len(nodes) > x+1 { + v = NodeAlias(nodes[x+1]) + } + } + } + if key == v.Content[x].Value { if IsNodeMap(v) { if x+1 == len(v.Content) { return v, v.Content[x], NodeAlias(v.Content[x]) } - return v, v.Content[x], NodeAlias(v.Content[x+1]) + return NodeAlias(v), NodeAlias(v.Content[x]), NodeAlias(v.Content[x+1]) } if IsNodeArray(v) { - return v, v.Content[x], NodeAlias(v.Content[x]) + return NodeAlias(v), NodeAlias(v.Content[x]), NodeAlias(v.Content[x]) } } } @@ -335,12 +355,26 @@ func FindKeyNodeFull(key string, nodes []*yaml.Node) (keyNode *yaml.Node, labelN // FindKeyNodeFullTop is an overloaded version of FindKeyNodeFull. This version only looks at the top // level of the node and not the children. func FindKeyNodeFullTop(key string, nodes []*yaml.Node) (keyNode *yaml.Node, labelNode *yaml.Node, valueNode *yaml.Node) { - for i := range nodes { + if nodes != nil && len(nodes) >= 0 && nodes[0].Tag == "!!merge" { + nodes = NodeAlias(nodes[1]).Content + } + for i := 0; i < len(nodes); i++ { + v := nodes[i] + if i%2 == 0 { + if v.Tag == "!!merge" { + if len(nodes) > i+1 { + v = NodeAlias(nodes[i+1]) + if len(v.Content) > 0 { + nodes = append(nodes, v.Content...) + } + } + } + } if i%2 != 0 { continue } if i%2 == 0 && key == nodes[i].Value { - return nodes[i], nodes[i], NodeAlias(nodes[i+1]) // next node is what we need. + return NodeAlias(nodes[i]), NodeAlias(nodes[i]), NodeAlias(nodes[i+1]) // next node is what we need. } } return nil, nil, nil @@ -417,11 +451,43 @@ func IsNodeAlias(node *yaml.Node) (*yaml.Node, bool) { return node, false } +func NodeMerge(nodes []*yaml.Node) *yaml.Node { + for i, v := range nodes { + if v.Tag == "!!merge" { + if i+1 < len(nodes) { + return NodeAlias(nodes[i+1]) + } + } + } + if len(nodes) > 0 { + return NodeAlias(nodes[0]) + } + return nil +} + // NodeAlias checks if the node is an alias, and lifts out the anchor func NodeAlias(node *yaml.Node) *yaml.Node { + if node == nil { return nil } + + content := node.Content + if node.Kind == yaml.AliasNode { + content = node.Alias.Content + } + + for i, n := range content { + if i%2 == 0 { + if n.Tag == "!!merge" { + g := NodeMerge(content[i+1:]) + if g != nil { + node = g + } + } + } + } + if node.Kind == yaml.AliasNode { node = node.Alias return node diff --git a/utils/utils_test.go b/utils/utils_test.go index f9c1c5d..5290ed6 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -1002,19 +1002,143 @@ func TestDetermineJSONWhitespaceLength_None(t *testing.T) { assert.Equal(t, 0, DetermineWhitespaceLength(string(someBytes))) } -func TestTimeoutFind(t *testing.T) { - a := &yaml.Node{ - Value: "chicken", - } - b := &yaml.Node{ - Value: "nuggets", - } +func TestFindFirstKeyNode_DoubleMerge(t *testing.T) { + yml := []byte(`openapi: 3.0.3 - // loopy loop. - a.Content = append(a.Content, b) - b.Content = append(b.Content, a) +t-k: &anchorB + important-field: a nice string +x-a: &anchorA + <<: *anchorB +x-b: + <<: *anchorA +`) + + var rootNode yaml.Node + _ = yaml.Unmarshal(yml, &rootNode) + + k, v := FindFirstKeyNode("important-field", rootNode.Content[0].Content[7].Content, 0) + assert.NotNil(t, k) + assert.NotNil(t, v) + assert.Equal(t, "a nice string", v.Value) - nodes, err := FindNodesWithoutDeserializing(a, "$..nuggets") - assert.Error(t, err) - assert.Nil(t, nodes) +} + +func TestFindKeyNodeTop_DoubleMerge(t *testing.T) { + yml := []byte(`openapi: 3.0.3 + +t-k: &anchorB + important-field: a nice string +x-a: &anchorA + <<: *anchorB +x-b: + <<: *anchorA +`) + + var rootNode yaml.Node + _ = yaml.Unmarshal(yml, &rootNode) + + k, v := FindKeyNodeTop("important-field", rootNode.Content[0].Content[7].Content) + assert.NotNil(t, k) + assert.NotNil(t, v) + assert.Equal(t, "a nice string", v.Value) + +} + +func TestFindKeyNode_DoubleMerge(t *testing.T) { + yml := []byte(`openapi: 3.0.3 + +t-k: &anchorB + important-field: a nice string +x-a: &anchorA + <<: *anchorB +x-b: + <<: *anchorA +`) + + var rootNode yaml.Node + _ = yaml.Unmarshal(yml, &rootNode) + + k, v := FindKeyNode("important-field", rootNode.Content[0].Content[7].Content) + assert.NotNil(t, k) + assert.NotNil(t, v) + assert.Equal(t, "a nice string", v.Value) + +} + +func TestFindKeyNodeFull_DoubleMerge(t *testing.T) { + yml := []byte(`openapi: 3.0.3 +any-thing: &anchorH + important-field: a nice string +t-k: &anchorB + panda: + <<: *anchorH +x-a: &anchorA + <<: *anchorB +x-b: + <<: *anchorA + +`) + + var rootNode yaml.Node + ee := yaml.Unmarshal(yml, &rootNode) + assert.NoError(t, ee) + + k, l, v := FindKeyNodeFull("important-field", rootNode.Content[0].Content[9].Content) + assert.NotNil(t, l) + assert.NotNil(t, k) + assert.NotNil(t, v) + assert.Equal(t, "a nice string", v.Value) + +} + +func TestFindKeyNodeFullTop_DoubleMerge(t *testing.T) { + yml := []byte(`openapi: 3.0.3 +any-thing: &anchorH + important-field: a nice string +t-k: &anchorB + <<: *anchorH +x-a: &anchorA + <<: *anchorB +x-b: + <<: *anchorA + +`) + + var rootNode yaml.Node + ee := yaml.Unmarshal(yml, &rootNode) + assert.NoError(t, ee) + + k, l, v := FindKeyNodeFullTop("important-field", rootNode.Content[0].Content[9].Content) + assert.NotNil(t, l) + assert.NotNil(t, k) + assert.NotNil(t, v) + assert.Equal(t, "a nice string", v.Value) + +} + +func TestNodeMerge(t *testing.T) { + yml := []byte(`openapi: 3.0.3 +any-thing: &anchorH + important-field: a nice string +t-k: &anchorB + <<: *anchorH +x-a: &anchorA + <<: *anchorB +x-b: + <<: *anchorA + +`) + + var rootNode yaml.Node + ee := yaml.Unmarshal(yml, &rootNode) + assert.NoError(t, ee) + + n := NodeMerge(rootNode.Content[0].Content[9].Content) + assert.NotNil(t, n) + assert.Equal(t, "a nice string", n.Content[1].Value) +} + +func TestNodeMerge_NoNodes(t *testing.T) { + n := NodeMerge(nil) + assert.Nil(t, n) }