Addressed issue with anchors and merge nodes

Highlighted in https://github.com/daveshanley/vacuum/issues/508

The comment from @Jakousa annoyed me. Rude.
This commit is contained in:
quobix
2024-07-05 13:21:00 -04:00
parent 246ad9faee
commit c2b58717b8
2 changed files with 223 additions and 33 deletions

View File

@@ -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

View File

@@ -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)
}