yq/pkg/yqlib/operator_traverse_path_test.go

556 lines
12 KiB
Go

package yqlib
import (
"testing"
)
var mergeDocSample = `foo: &foo
a: foo_a
thing: foo_thing
c: foo_c
bar: &bar
b: bar_b
thing: bar_thing
c: bar_c
foobarList:
b: foobarList_b
<<: [*foo,*bar]
c: foobarList_c
foobar:
c: foobar_c
<<: *foo
thing: foobar_thing
`
// cannot use merge anchors with arrays
var badAliasSample = `
_common: &common-docker-file
- FROM ubuntu:18.04
steps:
<<: *common-docker-file
`
var traversePathOperatorScenarios = []expressionScenario{
{
skipDoc: true,
description: "access merge anchors",
document: "foo: &foo {x: y}\nbar:\n <<: *foo\n",
expression: `.bar["<<"] | alias`,
expected: []string{
"D0, P[bar <<], (!!str)::foo\n",
},
},
{
skipDoc: true,
description: "dynamically set parent and key",
expression: `.a.b.c = 3 | .a.b.c`,
expected: []string{
"D0, P[a b c], (!!int)::3\n",
},
},
{
skipDoc: true,
description: "dynamically set parent and key in array",
expression: `.a.b[0] = 3 | .a.b[0]`,
expected: []string{
"D0, P[a b 0], (!!int)::3\n",
},
},
{
skipDoc: true,
description: "dynamically set parent and key",
expression: `.a.b = ["x","y"] | .a.b[1]`,
expected: []string{
"D0, P[a b 1], (!!str)::y\n",
},
},
{
skipDoc: true,
description: "splat empty map",
document: "{}",
expression: ".[]",
expected: []string{},
},
{
skipDoc: true,
document: `[[1]]`,
expression: `.[0][0]`,
expected: []string{
"D0, P[0 0], (!!int)::1\n",
},
},
{
skipDoc: true,
expression: `.cat["12"] = "things"`,
expected: []string{
"D0, P[], ()::cat:\n \"12\": things\n",
},
},
{
skipDoc: true,
document: `blah: {}`,
expression: `.blah.cat = "cool"`,
expected: []string{
"D0, P[], (!!map)::blah:\n cat: cool\n",
},
},
{
skipDoc: true,
document: `blah: []`,
expression: `.blah.0 = "cool"`,
expected: []string{
"D0, P[], (!!map)::blah:\n - cool\n",
},
},
{
skipDoc: true,
document: `b: cat`,
expression: ".b\n",
expected: []string{
"D0, P[b], (!!str)::cat\n",
},
},
{
skipDoc: true,
document: `[[[1]]]`,
expression: `.[0][0][0]`,
expected: []string{
"D0, P[0 0 0], (!!int)::1\n",
},
},
{
skipDoc: true,
expression: `.["cat"] = "thing"`,
expected: []string{
"D0, P[], ()::cat: thing\n",
},
},
{
description: "Simple map navigation",
document: `{a: {b: apple}}`,
expression: `.a`,
expected: []string{
"D0, P[a], (!!map)::{b: apple}\n",
},
},
{
description: "Splat",
subdescription: "Often used to pipe children into other operators",
document: `[{b: apple}, {c: banana}]`,
expression: `.[]`,
expected: []string{
"D0, P[0], (!!map)::{b: apple}\n",
"D0, P[1], (!!map)::{c: banana}\n",
},
},
{
description: "Optional Splat",
subdescription: "Just like splat, but won't error if you run it against scalars",
document: `"cat"`,
expression: `.[]`,
expected: []string{},
},
{
description: "Special characters",
subdescription: "Use quotes with square brackets around path elements with special characters",
document: `{"{}": frog}`,
expression: `.["{}"]`,
expected: []string{
"D0, P[{}], (!!str)::frog\n",
},
},
{
description: "Nested special characters",
document: `a: {"key.withdots": {"another.key": apple}}`,
expression: `.a["key.withdots"]["another.key"]`,
expected: []string{
"D0, P[a key.withdots another.key], (!!str)::apple\n",
},
},
{
description: "Keys with spaces",
subdescription: "Use quotes with square brackets around path elements with special characters",
document: `{"red rabbit": frog}`,
expression: `.["red rabbit"]`,
expected: []string{
"D0, P[red rabbit], (!!str)::frog\n",
},
},
{
skipDoc: true,
document: `{"flying fox": frog}`,
expression: `.["flying fox"]`,
expected: []string{
"D0, P[flying fox], (!!str)::frog\n",
},
},
{
skipDoc: true,
document: `c: dog`,
expression: `.[.a.b] as $x | .`,
expected: []string{
"D0, P[], (!!map)::c: dog\n",
},
},
{
description: "Dynamic keys",
subdescription: `Expressions within [] can be used to dynamically lookup / calculate keys`,
document: `{b: apple, apple: crispy yum, banana: soft yum}`,
expression: `.[.b]`,
expected: []string{
"D0, P[apple], (!!str)::crispy yum\n",
},
},
{
skipDoc: true,
document: `{b: apple, fruit: {apple: yum, banana: smooth}}`,
expression: `.fruit[.b]`,
expected: []string{
"D0, P[fruit apple], (!!str)::yum\n",
},
},
{
description: "Children don't exist",
subdescription: "Nodes are added dynamically while traversing",
document: `{c: banana}`,
expression: `.a.b`,
expected: []string{
"D0, P[a b], (!!null)::null\n",
},
},
{
description: "Optional identifier",
subdescription: "Like jq, does not output an error when the yaml is not an array or object as expected",
document: `[1,2,3]`,
expression: `.a?`,
expected: []string{},
},
{
skipDoc: true,
document: `[[1,2,3], {a: frog}]`,
expression: `.[] | .["a"]?`,
expected: []string{"D0, P[1 a], (!!str)::frog\n"},
},
{
skipDoc: true,
document: ``,
expression: `.[1].a`,
expected: []string{
"D0, P[1 a], (!!null)::null\n",
},
},
{
skipDoc: true,
document: `{}`,
expression: `.a[1]`,
expected: []string{
"D0, P[a 1], (!!null)::null\n",
},
},
{
description: "Wildcard matching",
document: `{a: {cat: apple, mad: things}}`,
expression: `.a."*a*"`,
expected: []string{
"D0, P[a cat], (!!str)::apple\n",
"D0, P[a mad], (!!str)::things\n",
},
},
{
skipDoc: true,
document: `{a: {cat: {b: 3}, mad: {b: 4}, fad: {c: t}}}`,
expression: `.a."*a*".b`,
expected: []string{
"D0, P[a cat b], (!!int)::3\n",
"D0, P[a mad b], (!!int)::4\n",
"D0, P[a fad b], (!!null)::null\n",
},
},
{
skipDoc: true,
document: `{a: {cat: apple, mad: things}}`,
expression: `.a | (.cat, .mad)`,
expected: []string{
"D0, P[a cat], (!!str)::apple\n",
"D0, P[a mad], (!!str)::things\n",
},
},
{
skipDoc: true,
document: `{a: {cat: apple, mad: things}}`,
expression: `.a | (.cat, .mad, .fad)`,
expected: []string{
"D0, P[a cat], (!!str)::apple\n",
"D0, P[a mad], (!!str)::things\n",
"D0, P[a fad], (!!null)::null\n",
},
},
{
skipDoc: true,
document: `{a: {cat: apple, mad: things}}`,
expression: `.a | (.cat, .mad, .fad) | select( (. == null) | not)`,
expected: []string{
"D0, P[a cat], (!!str)::apple\n",
"D0, P[a mad], (!!str)::things\n",
},
},
{
description: "Aliases",
document: `{a: &cat {c: frog}, b: *cat}`,
expression: `.b`,
expected: []string{
"D0, P[b], (alias)::*cat\n",
},
},
{
description: "Traversing aliases with splat",
document: `{a: &cat {c: frog}, b: *cat}`,
expression: `.b[]`,
expected: []string{
"D0, P[a c], (!!str)::frog\n",
},
},
{
description: "Traversing aliases explicitly",
document: `{a: &cat {c: frog}, b: *cat}`,
expression: `.b.c`,
expected: []string{
"D0, P[a c], (!!str)::frog\n",
},
},
{
description: "Traversing arrays by index",
document: `[1,2,3]`,
expression: `.[0]`,
expected: []string{
"D0, P[0], (!!int)::1\n",
},
},
{
description: "Traversing nested arrays by index",
dontFormatInputForDoc: true,
document: `[[], [cat]]`,
expression: `.[1][0]`,
expected: []string{
"D0, P[1 0], (!!str)::cat\n",
},
},
{
description: "Maps with numeric keys",
document: `{2: cat}`,
expression: `.[2]`,
expected: []string{
"D0, P[2], (!!str)::cat\n",
},
},
{
description: "Maps with non existing numeric keys",
document: `{a: b}`,
expression: `.[0]`,
expected: []string{
"D0, P[0], (!!null)::null\n",
},
},
{
skipDoc: true,
document: mergeDocSample,
expression: `.foobar`,
expected: []string{
"D0, P[foobar], (!!map)::c: foobar_c\n!!merge <<: *foo\nthing: foobar_thing\n",
},
},
{
description: "Traversing merge anchors",
document: mergeDocSample,
expression: `.foobar.a`,
expected: []string{
"D0, P[foo a], (!!str)::foo_a\n",
},
},
{
description: "Traversing merge anchors with override",
document: mergeDocSample,
expression: `.foobar.c`,
expected: []string{
"D0, P[foo c], (!!str)::foo_c\n",
},
},
{
description: "Traversing merge anchors with local override",
document: mergeDocSample,
expression: `.foobar.thing`,
expected: []string{
"D0, P[foobar thing], (!!str)::foobar_thing\n",
},
},
{
description: "Splatting merge anchors",
document: mergeDocSample,
expression: `.foobar[]`,
expected: []string{
"D0, P[foo c], (!!str)::foo_c\n",
"D0, P[foo a], (!!str)::foo_a\n",
"D0, P[foobar thing], (!!str)::foobar_thing\n",
},
},
{
skipDoc: true,
document: mergeDocSample,
expression: `.foobarList`,
expected: []string{
"D0, P[foobarList], (!!map)::b: foobarList_b\n!!merge <<: [*foo, *bar]\nc: foobarList_c\n",
},
},
{
skipDoc: true,
document: mergeDocSample,
expression: `.foobarList.a`,
expected: []string{
"D0, P[foo a], (!!str)::foo_a\n",
},
},
{
description: "Traversing merge anchor lists",
subdescription: "Note that the later merge anchors override previous",
document: mergeDocSample,
expression: `.foobarList.thing`,
expected: []string{
"D0, P[bar thing], (!!str)::bar_thing\n",
},
},
{
skipDoc: true,
document: mergeDocSample,
expression: `.foobarList.c`,
expected: []string{
"D0, P[foobarList c], (!!str)::foobarList_c\n",
},
},
{
skipDoc: true,
document: mergeDocSample,
expression: `.foobarList.b`,
expected: []string{
"D0, P[bar b], (!!str)::bar_b\n",
},
},
{
description: "Splatting merge anchor lists",
document: mergeDocSample,
expression: `.foobarList[]`,
expected: []string{
"D0, P[bar b], (!!str)::bar_b\n",
"D0, P[foo a], (!!str)::foo_a\n",
"D0, P[bar thing], (!!str)::bar_thing\n",
"D0, P[foobarList c], (!!str)::foobarList_c\n",
},
},
{
skipDoc: true,
document: `[a,b,c]`,
expression: `.[]`,
expected: []string{
"D0, P[0], (!!str)::a\n",
"D0, P[1], (!!str)::b\n",
"D0, P[2], (!!str)::c\n",
},
},
{
skipDoc: true,
document: `[a,b,c]`,
expression: `[]`,
expected: []string{
"D0, P[], (!!seq)::[]\n",
},
},
{
skipDoc: true,
document: `{a: [a,b,c]}`,
expression: `.a[0]`,
expected: []string{
"D0, P[a 0], (!!str)::a\n",
},
},
{
description: "Select multiple indices",
document: `{a: [a,b,c]}`,
expression: `.a[0, 2]`,
expected: []string{
"D0, P[a 0], (!!str)::a\n",
"D0, P[a 2], (!!str)::c\n",
},
},
{
skipDoc: true,
document: `{a: [a,b,c]}`,
expression: `.a[0, 2]`,
expected: []string{
"D0, P[a 0], (!!str)::a\n",
"D0, P[a 2], (!!str)::c\n",
},
},
{
skipDoc: true,
document: `{a: [a,b,c]}`,
expression: `.a[-1]`,
expected: []string{
"D0, P[a 2], (!!str)::c\n",
},
},
{
skipDoc: true,
document: `{a: [a,b,c]}`,
expression: `.a[-2]`,
expected: []string{
"D0, P[a 1], (!!str)::b\n",
},
},
{
skipDoc: true,
document: `{a: [a,b,c]}`,
expression: `.a[]`,
expected: []string{
"D0, P[a 0], (!!str)::a\n",
"D0, P[a 1], (!!str)::b\n",
"D0, P[a 2], (!!str)::c\n",
},
},
{
skipDoc: true,
document: `{a: [a,b,c]}`,
expression: `.a[]`,
expected: []string{
"D0, P[a 0], (!!str)::a\n",
"D0, P[a 1], (!!str)::b\n",
"D0, P[a 2], (!!str)::c\n",
},
},
{
skipDoc: true,
document: `{a: [a,b,c]}`,
expression: `.a | .[]`,
expected: []string{
"D0, P[a 0], (!!str)::a\n",
"D0, P[a 1], (!!str)::b\n",
"D0, P[a 2], (!!str)::c\n",
},
},
{
skipDoc: true,
document: badAliasSample,
expression: ".steps[]",
expectedError: "can only use merge anchors with maps (!!map), but got !!seq",
},
}
func TestTraversePathOperatorScenarios(t *testing.T) {
for _, tt := range traversePathOperatorScenarios {
testScenario(t, &tt)
}
documentOperatorScenarios(t, "traverse-read", traversePathOperatorScenarios)
}