selector: rework to match the spec, cleanup lots of edge cases
- removed field selection against a list - a selector return exactly one node, or nil - implemented the full set of slicing, including negative indexes - plenty of other cleanup and simplification
This commit is contained in:
@@ -2,6 +2,7 @@ package selector
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -33,54 +34,64 @@ func Parse(str string) (Selector, error) {
|
|||||||
if opt {
|
if opt {
|
||||||
seg = tok[0 : len(tok)-1]
|
seg = tok[0 : len(tok)-1]
|
||||||
}
|
}
|
||||||
switch seg {
|
switch {
|
||||||
case ".":
|
case seg == ".":
|
||||||
if len(sel) > 0 && sel[len(sel)-1].Identity() {
|
if len(sel) > 0 && sel[len(sel)-1].Identity() {
|
||||||
return nil, newParseError("selector contains unsupported recursive descent segment: '..'", str, col, tok)
|
return nil, newParseError("selector contains unsupported recursive descent segment: '..'", str, col, tok)
|
||||||
}
|
}
|
||||||
sel = append(sel, segment{str: ".", identity: true})
|
sel = append(sel, segment{str: ".", identity: true})
|
||||||
case "[]":
|
|
||||||
sel = append(sel, segment{str: tok, optional: opt, iterator: true})
|
|
||||||
default:
|
|
||||||
if strings.HasPrefix(seg, "[") && strings.HasSuffix(seg, "]") {
|
|
||||||
lookup := seg[1 : len(seg)-1]
|
|
||||||
|
|
||||||
if indexRegex.MatchString(lookup) { // index
|
case seg == "[]":
|
||||||
idx, err := strconv.Atoi(lookup)
|
sel = append(sel, segment{str: tok, optional: opt, iterator: true})
|
||||||
if err != nil {
|
|
||||||
return nil, newParseError("invalid index", str, col, tok)
|
case strings.HasPrefix(seg, "[") && strings.HasSuffix(seg, "]"):
|
||||||
}
|
lookup := seg[1 : len(seg)-1]
|
||||||
sel = append(sel, segment{str: tok, optional: opt, index: idx})
|
|
||||||
} else if strings.HasPrefix(lookup, "\"") && strings.HasSuffix(lookup, "\"") { // explicit field
|
switch {
|
||||||
sel = append(sel, segment{str: tok, optional: opt, field: lookup[1 : len(lookup)-1]})
|
// index, [123]
|
||||||
} else if sliceRegex.MatchString(lookup) { // slice [3:5] or [:5] or [3:]
|
case indexRegex.MatchString(lookup):
|
||||||
var rng []int
|
idx, err := strconv.Atoi(lookup)
|
||||||
splt := strings.Split(lookup, ":")
|
if err != nil {
|
||||||
if splt[0] == "" {
|
return nil, newParseError("invalid index", str, col, tok)
|
||||||
rng = append(rng, 0)
|
|
||||||
} else {
|
|
||||||
i, err := strconv.Atoi(splt[0])
|
|
||||||
if err != nil {
|
|
||||||
return nil, newParseError("invalid slice index", str, col, tok)
|
|
||||||
}
|
|
||||||
rng = append(rng, i)
|
|
||||||
}
|
|
||||||
if splt[1] != "" {
|
|
||||||
i, err := strconv.Atoi(splt[1])
|
|
||||||
if err != nil {
|
|
||||||
return nil, newParseError("invalid slice index", str, col, tok)
|
|
||||||
}
|
|
||||||
rng = append(rng, i)
|
|
||||||
}
|
|
||||||
sel = append(sel, segment{str: tok, optional: opt, slice: rng})
|
|
||||||
} else {
|
|
||||||
return nil, newParseError(fmt.Sprintf("invalid segment: %s", seg), str, col, tok)
|
|
||||||
}
|
}
|
||||||
} else if fieldRegex.MatchString(seg) {
|
sel = append(sel, segment{str: tok, optional: opt, index: idx})
|
||||||
sel = append(sel, segment{str: tok, optional: opt, field: seg[1:]})
|
|
||||||
} else {
|
// explicit field, ["abcd"]
|
||||||
|
case strings.HasPrefix(lookup, "\"") && strings.HasSuffix(lookup, "\""):
|
||||||
|
sel = append(sel, segment{str: tok, optional: opt, field: lookup[1 : len(lookup)-1]})
|
||||||
|
|
||||||
|
// slice [3:5] or [:5] or [3:], also negative numbers
|
||||||
|
case sliceRegex.MatchString(lookup):
|
||||||
|
var rng [2]int64
|
||||||
|
splt := strings.Split(lookup, ":")
|
||||||
|
if splt[0] == "" {
|
||||||
|
rng[0] = math.MinInt
|
||||||
|
} else {
|
||||||
|
i, err := strconv.ParseInt(splt[0], 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, newParseError("invalid slice index", str, col, tok)
|
||||||
|
}
|
||||||
|
rng[0] = i
|
||||||
|
}
|
||||||
|
if splt[1] == "" {
|
||||||
|
rng[1] = math.MaxInt
|
||||||
|
} else {
|
||||||
|
i, err := strconv.ParseInt(splt[1], 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, newParseError("invalid slice index", str, col, tok)
|
||||||
|
}
|
||||||
|
rng[1] = i
|
||||||
|
}
|
||||||
|
sel = append(sel, segment{str: tok, optional: opt, slice: rng[:]})
|
||||||
|
|
||||||
|
default:
|
||||||
return nil, newParseError(fmt.Sprintf("invalid segment: %s", seg), str, col, tok)
|
return nil, newParseError(fmt.Sprintf("invalid segment: %s", seg), str, col, tok)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case fieldRegex.MatchString(seg):
|
||||||
|
sel = append(sel, segment{str: tok, optional: opt, field: seg[1:]})
|
||||||
|
default:
|
||||||
|
return nil, newParseError(fmt.Sprintf("invalid segment: %s", seg), str, col, tok)
|
||||||
}
|
}
|
||||||
col += len(tok)
|
col += len(tok)
|
||||||
}
|
}
|
||||||
|
|||||||
427
pkg/policy/selector/parsing_test.go
Normal file
427
pkg/policy/selector/parsing_test.go
Normal file
@@ -0,0 +1,427 @@
|
|||||||
|
package selector
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParse(t *testing.T) {
|
||||||
|
t.Run("identity", func(t *testing.T) {
|
||||||
|
sel, err := Parse(".")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, len(sel))
|
||||||
|
require.True(t, sel[0].Identity())
|
||||||
|
require.False(t, sel[0].Optional())
|
||||||
|
require.False(t, sel[0].Iterator())
|
||||||
|
require.Empty(t, sel[0].Slice())
|
||||||
|
require.Empty(t, sel[0].Field())
|
||||||
|
require.Empty(t, sel[0].Index())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("dotted field name", func(t *testing.T) {
|
||||||
|
sel, err := Parse(".foo")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, len(sel))
|
||||||
|
require.False(t, sel[0].Identity())
|
||||||
|
require.False(t, sel[0].Optional())
|
||||||
|
require.False(t, sel[0].Iterator())
|
||||||
|
require.Empty(t, sel[0].Slice())
|
||||||
|
require.Equal(t, sel[0].Field(), "foo")
|
||||||
|
require.Empty(t, sel[0].Index())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("explicit field", func(t *testing.T) {
|
||||||
|
sel, err := Parse(`.["foo"]`)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 2, len(sel))
|
||||||
|
require.True(t, sel[0].Identity())
|
||||||
|
require.False(t, sel[0].Optional())
|
||||||
|
require.False(t, sel[0].Iterator())
|
||||||
|
require.Empty(t, sel[0].Slice())
|
||||||
|
require.Empty(t, sel[0].Field())
|
||||||
|
require.Empty(t, sel[0].Index())
|
||||||
|
require.False(t, sel[1].Identity())
|
||||||
|
require.False(t, sel[1].Optional())
|
||||||
|
require.False(t, sel[1].Iterator())
|
||||||
|
require.Empty(t, sel[1].Slice())
|
||||||
|
require.Equal(t, sel[1].Field(), "foo")
|
||||||
|
require.Empty(t, sel[1].Index())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("iterator, collection value", func(t *testing.T) {
|
||||||
|
sel, err := Parse(".[]")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 2, len(sel))
|
||||||
|
require.True(t, sel[0].Identity())
|
||||||
|
require.False(t, sel[0].Optional())
|
||||||
|
require.False(t, sel[0].Iterator())
|
||||||
|
require.Empty(t, sel[0].Slice())
|
||||||
|
require.Empty(t, sel[0].Field())
|
||||||
|
require.Empty(t, sel[0].Index())
|
||||||
|
require.False(t, sel[1].Identity())
|
||||||
|
require.False(t, sel[1].Optional())
|
||||||
|
require.True(t, sel[1].Iterator())
|
||||||
|
require.Empty(t, sel[1].Slice())
|
||||||
|
require.Empty(t, sel[1].Field())
|
||||||
|
require.Empty(t, sel[1].Index())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("index", func(t *testing.T) {
|
||||||
|
sel, err := Parse(".[138]")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 2, len(sel))
|
||||||
|
require.True(t, sel[0].Identity())
|
||||||
|
require.False(t, sel[0].Optional())
|
||||||
|
require.False(t, sel[0].Iterator())
|
||||||
|
require.Empty(t, sel[0].Slice())
|
||||||
|
require.Empty(t, sel[0].Field())
|
||||||
|
require.Empty(t, sel[0].Index())
|
||||||
|
require.False(t, sel[1].Identity())
|
||||||
|
require.False(t, sel[1].Optional())
|
||||||
|
require.False(t, sel[1].Iterator())
|
||||||
|
require.Empty(t, sel[1].Slice())
|
||||||
|
require.Empty(t, sel[1].Field())
|
||||||
|
require.Equal(t, sel[1].Index(), 138)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("negative index", func(t *testing.T) {
|
||||||
|
sel, err := Parse(".[-138]")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 2, len(sel))
|
||||||
|
require.True(t, sel[0].Identity())
|
||||||
|
require.False(t, sel[0].Optional())
|
||||||
|
require.False(t, sel[0].Iterator())
|
||||||
|
require.Empty(t, sel[0].Slice())
|
||||||
|
require.Empty(t, sel[0].Field())
|
||||||
|
require.Empty(t, sel[0].Index())
|
||||||
|
require.False(t, sel[1].Identity())
|
||||||
|
require.False(t, sel[1].Optional())
|
||||||
|
require.False(t, sel[1].Iterator())
|
||||||
|
require.Empty(t, sel[1].Slice())
|
||||||
|
require.Empty(t, sel[1].Field())
|
||||||
|
require.Equal(t, sel[1].Index(), -138)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("List slice", func(t *testing.T) {
|
||||||
|
sel, err := Parse(".[7:11]")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 2, len(sel))
|
||||||
|
require.True(t, sel[0].Identity())
|
||||||
|
require.False(t, sel[0].Optional())
|
||||||
|
require.False(t, sel[0].Iterator())
|
||||||
|
require.Empty(t, sel[0].Slice())
|
||||||
|
require.Empty(t, sel[0].Field())
|
||||||
|
require.Empty(t, sel[0].Index())
|
||||||
|
require.False(t, sel[1].Identity())
|
||||||
|
require.False(t, sel[1].Optional())
|
||||||
|
require.False(t, sel[1].Iterator())
|
||||||
|
require.Equal(t, sel[1].Slice(), []int64{7, 11})
|
||||||
|
require.Empty(t, sel[1].Field())
|
||||||
|
require.Empty(t, sel[1].Index())
|
||||||
|
|
||||||
|
sel, err = Parse(".[2:]")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 2, len(sel))
|
||||||
|
require.True(t, sel[0].Identity())
|
||||||
|
require.False(t, sel[0].Optional())
|
||||||
|
require.False(t, sel[0].Iterator())
|
||||||
|
require.Empty(t, sel[0].Slice())
|
||||||
|
require.Empty(t, sel[0].Field())
|
||||||
|
require.Empty(t, sel[0].Index())
|
||||||
|
require.False(t, sel[1].Identity())
|
||||||
|
require.False(t, sel[1].Optional())
|
||||||
|
require.False(t, sel[1].Iterator())
|
||||||
|
require.Equal(t, sel[1].Slice(), []int64{2, math.MaxInt})
|
||||||
|
require.Empty(t, sel[1].Field())
|
||||||
|
require.Empty(t, sel[1].Index())
|
||||||
|
|
||||||
|
sel, err = Parse(".[:42]")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 2, len(sel))
|
||||||
|
require.True(t, sel[0].Identity())
|
||||||
|
require.False(t, sel[0].Optional())
|
||||||
|
require.False(t, sel[0].Iterator())
|
||||||
|
require.Empty(t, sel[0].Slice())
|
||||||
|
require.Empty(t, sel[0].Field())
|
||||||
|
require.Empty(t, sel[0].Index())
|
||||||
|
require.False(t, sel[1].Identity())
|
||||||
|
require.False(t, sel[1].Optional())
|
||||||
|
require.False(t, sel[1].Iterator())
|
||||||
|
require.Equal(t, sel[1].Slice(), []int64{math.MinInt, 42})
|
||||||
|
require.Empty(t, sel[1].Field())
|
||||||
|
require.Empty(t, sel[1].Index())
|
||||||
|
|
||||||
|
sel, err = Parse(".[0:-2]")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 2, len(sel))
|
||||||
|
require.True(t, sel[0].Identity())
|
||||||
|
require.False(t, sel[0].Optional())
|
||||||
|
require.False(t, sel[0].Iterator())
|
||||||
|
require.Empty(t, sel[0].Slice())
|
||||||
|
require.Empty(t, sel[0].Field())
|
||||||
|
require.Empty(t, sel[0].Index())
|
||||||
|
require.False(t, sel[1].Identity())
|
||||||
|
require.False(t, sel[1].Optional())
|
||||||
|
require.False(t, sel[1].Iterator())
|
||||||
|
require.Equal(t, sel[1].Slice(), []int64{0, -2})
|
||||||
|
require.Empty(t, sel[1].Field())
|
||||||
|
require.Empty(t, sel[1].Index())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("optional identity", func(t *testing.T) {
|
||||||
|
sel, err := Parse(".?")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, len(sel))
|
||||||
|
require.True(t, sel[0].Identity())
|
||||||
|
require.True(t, sel[0].Optional())
|
||||||
|
require.False(t, sel[0].Iterator())
|
||||||
|
require.Empty(t, sel[0].Slice())
|
||||||
|
require.Empty(t, sel[0].Field())
|
||||||
|
require.Empty(t, sel[0].Index())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("optional dotted field name", func(t *testing.T) {
|
||||||
|
sel, err := Parse(".foo?")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, len(sel))
|
||||||
|
require.False(t, sel[0].Identity())
|
||||||
|
require.True(t, sel[0].Optional())
|
||||||
|
require.False(t, sel[0].Iterator())
|
||||||
|
require.Empty(t, sel[0].Slice())
|
||||||
|
require.Equal(t, sel[0].Field(), "foo")
|
||||||
|
require.Empty(t, sel[0].Index())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("optional explicit field", func(t *testing.T) {
|
||||||
|
sel, err := Parse(`.["foo"]?`)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 2, len(sel))
|
||||||
|
require.True(t, sel[0].Identity())
|
||||||
|
require.False(t, sel[0].Optional())
|
||||||
|
require.False(t, sel[0].Iterator())
|
||||||
|
require.Empty(t, sel[0].Slice())
|
||||||
|
require.Empty(t, sel[0].Field())
|
||||||
|
require.Empty(t, sel[0].Index())
|
||||||
|
require.False(t, sel[1].Identity())
|
||||||
|
require.True(t, sel[1].Optional())
|
||||||
|
require.False(t, sel[1].Iterator())
|
||||||
|
require.Empty(t, sel[1].Slice())
|
||||||
|
require.Equal(t, sel[1].Field(), "foo")
|
||||||
|
require.Empty(t, sel[1].Index())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("optional iterator", func(t *testing.T) {
|
||||||
|
sel, err := Parse(".[]?")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 2, len(sel))
|
||||||
|
require.True(t, sel[0].Identity())
|
||||||
|
require.False(t, sel[0].Optional())
|
||||||
|
require.False(t, sel[0].Iterator())
|
||||||
|
require.Empty(t, sel[0].Slice())
|
||||||
|
require.Empty(t, sel[0].Field())
|
||||||
|
require.Empty(t, sel[0].Index())
|
||||||
|
require.False(t, sel[1].Identity())
|
||||||
|
require.True(t, sel[1].Optional())
|
||||||
|
require.True(t, sel[1].Iterator())
|
||||||
|
require.Empty(t, sel[1].Slice())
|
||||||
|
require.Empty(t, sel[1].Field())
|
||||||
|
require.Empty(t, sel[1].Index())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("optional index", func(t *testing.T) {
|
||||||
|
sel, err := Parse(".[138]?")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 2, len(sel))
|
||||||
|
require.True(t, sel[0].Identity())
|
||||||
|
require.False(t, sel[0].Optional())
|
||||||
|
require.False(t, sel[0].Iterator())
|
||||||
|
require.Empty(t, sel[0].Slice())
|
||||||
|
require.Empty(t, sel[0].Field())
|
||||||
|
require.Empty(t, sel[0].Index())
|
||||||
|
require.False(t, sel[1].Identity())
|
||||||
|
require.True(t, sel[1].Optional())
|
||||||
|
require.False(t, sel[1].Iterator())
|
||||||
|
require.Empty(t, sel[1].Slice())
|
||||||
|
require.Empty(t, sel[1].Field())
|
||||||
|
require.Equal(t, sel[1].Index(), 138)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("optional negative index", func(t *testing.T) {
|
||||||
|
sel, err := Parse(".[-138]?")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 2, len(sel))
|
||||||
|
require.True(t, sel[0].Identity())
|
||||||
|
require.False(t, sel[0].Optional())
|
||||||
|
require.False(t, sel[0].Iterator())
|
||||||
|
require.Empty(t, sel[0].Slice())
|
||||||
|
require.Empty(t, sel[0].Field())
|
||||||
|
require.Empty(t, sel[0].Index())
|
||||||
|
require.False(t, sel[1].Identity())
|
||||||
|
require.True(t, sel[1].Optional())
|
||||||
|
require.False(t, sel[1].Iterator())
|
||||||
|
require.Empty(t, sel[1].Slice())
|
||||||
|
require.Empty(t, sel[1].Field())
|
||||||
|
require.Equal(t, sel[1].Index(), -138)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("optional list slice", func(t *testing.T) {
|
||||||
|
sel, err := Parse(".[7:11]?")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 2, len(sel))
|
||||||
|
require.True(t, sel[0].Identity())
|
||||||
|
require.False(t, sel[0].Optional())
|
||||||
|
require.False(t, sel[0].Iterator())
|
||||||
|
require.Empty(t, sel[0].Slice())
|
||||||
|
require.Empty(t, sel[0].Field())
|
||||||
|
require.Empty(t, sel[0].Index())
|
||||||
|
require.False(t, sel[1].Identity())
|
||||||
|
require.True(t, sel[1].Optional())
|
||||||
|
require.False(t, sel[1].Iterator())
|
||||||
|
require.Equal(t, sel[1].Slice(), []int64{7, 11})
|
||||||
|
require.Empty(t, sel[1].Field())
|
||||||
|
require.Empty(t, sel[1].Index())
|
||||||
|
|
||||||
|
sel, err = Parse(".[2:]?")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 2, len(sel))
|
||||||
|
require.True(t, sel[0].Identity())
|
||||||
|
require.False(t, sel[0].Optional())
|
||||||
|
require.False(t, sel[0].Iterator())
|
||||||
|
require.Empty(t, sel[0].Slice())
|
||||||
|
require.Empty(t, sel[0].Field())
|
||||||
|
require.Empty(t, sel[0].Index())
|
||||||
|
require.False(t, sel[1].Identity())
|
||||||
|
require.True(t, sel[1].Optional())
|
||||||
|
require.False(t, sel[1].Iterator())
|
||||||
|
require.Equal(t, sel[1].Slice(), []int64{2, math.MaxInt})
|
||||||
|
require.Empty(t, sel[1].Field())
|
||||||
|
require.Empty(t, sel[1].Index())
|
||||||
|
|
||||||
|
sel, err = Parse(".[:42]?")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 2, len(sel))
|
||||||
|
require.True(t, sel[0].Identity())
|
||||||
|
require.False(t, sel[0].Optional())
|
||||||
|
require.False(t, sel[0].Iterator())
|
||||||
|
require.Empty(t, sel[0].Slice())
|
||||||
|
require.Empty(t, sel[0].Field())
|
||||||
|
require.Empty(t, sel[0].Index())
|
||||||
|
require.False(t, sel[1].Identity())
|
||||||
|
require.True(t, sel[1].Optional())
|
||||||
|
require.False(t, sel[1].Iterator())
|
||||||
|
require.Equal(t, sel[1].Slice(), []int64{math.MinInt, 42})
|
||||||
|
require.Empty(t, sel[1].Field())
|
||||||
|
require.Empty(t, sel[1].Index())
|
||||||
|
|
||||||
|
sel, err = Parse(".[0:-2]?")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 2, len(sel))
|
||||||
|
require.True(t, sel[0].Identity())
|
||||||
|
require.False(t, sel[0].Optional())
|
||||||
|
require.False(t, sel[0].Iterator())
|
||||||
|
require.Empty(t, sel[0].Slice())
|
||||||
|
require.Empty(t, sel[0].Field())
|
||||||
|
require.Empty(t, sel[0].Index())
|
||||||
|
require.False(t, sel[1].Identity())
|
||||||
|
require.True(t, sel[1].Optional())
|
||||||
|
require.False(t, sel[1].Iterator())
|
||||||
|
require.Equal(t, sel[1].Slice(), []int64{0, -2})
|
||||||
|
require.Empty(t, sel[1].Field())
|
||||||
|
require.Empty(t, sel[1].Index())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("idempotent optional", func(t *testing.T) {
|
||||||
|
sel, err := Parse(".foo???")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 2, len(sel))
|
||||||
|
require.True(t, sel[0].Identity())
|
||||||
|
require.False(t, sel[0].Optional())
|
||||||
|
require.False(t, sel[0].Iterator())
|
||||||
|
require.Empty(t, sel[0].Slice())
|
||||||
|
require.Empty(t, sel[0].Field())
|
||||||
|
require.Empty(t, sel[0].Index())
|
||||||
|
require.False(t, sel[1].Identity())
|
||||||
|
require.True(t, sel[1].Optional())
|
||||||
|
require.False(t, sel[1].Iterator())
|
||||||
|
require.Empty(t, sel[1].Slice())
|
||||||
|
require.Equal(t, sel[1].Field(), "foo")
|
||||||
|
require.Empty(t, sel[1].Index())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("deny multi dot", func(t *testing.T) {
|
||||||
|
_, err := Parse("..")
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("nesting", func(t *testing.T) {
|
||||||
|
str := `.foo.["bar"].[138]?.baz[1:]`
|
||||||
|
sel, err := Parse(str)
|
||||||
|
require.NoError(t, err)
|
||||||
|
printSegments(sel)
|
||||||
|
require.Equal(t, str, sel.String())
|
||||||
|
require.Equal(t, 7, len(sel))
|
||||||
|
require.False(t, sel[0].Identity())
|
||||||
|
require.False(t, sel[0].Optional())
|
||||||
|
require.False(t, sel[0].Iterator())
|
||||||
|
require.Empty(t, sel[0].Slice())
|
||||||
|
require.Equal(t, sel[0].Field(), "foo")
|
||||||
|
require.Empty(t, sel[0].Index())
|
||||||
|
require.True(t, sel[1].Identity())
|
||||||
|
require.False(t, sel[1].Optional())
|
||||||
|
require.False(t, sel[1].Iterator())
|
||||||
|
require.Empty(t, sel[1].Slice())
|
||||||
|
require.Empty(t, sel[1].Field())
|
||||||
|
require.Empty(t, sel[1].Index())
|
||||||
|
require.False(t, sel[2].Identity())
|
||||||
|
require.False(t, sel[2].Optional())
|
||||||
|
require.False(t, sel[2].Iterator())
|
||||||
|
require.Empty(t, sel[2].Slice())
|
||||||
|
require.Equal(t, sel[2].Field(), "bar")
|
||||||
|
require.Empty(t, sel[2].Index())
|
||||||
|
require.True(t, sel[3].Identity())
|
||||||
|
require.False(t, sel[3].Optional())
|
||||||
|
require.False(t, sel[3].Iterator())
|
||||||
|
require.Empty(t, sel[3].Slice())
|
||||||
|
require.Empty(t, sel[3].Field())
|
||||||
|
require.Empty(t, sel[3].Index())
|
||||||
|
require.False(t, sel[4].Identity())
|
||||||
|
require.True(t, sel[4].Optional())
|
||||||
|
require.False(t, sel[4].Iterator())
|
||||||
|
require.Empty(t, sel[4].Slice())
|
||||||
|
require.Empty(t, sel[4].Field())
|
||||||
|
require.Equal(t, sel[4].Index(), 138)
|
||||||
|
require.False(t, sel[5].Identity())
|
||||||
|
require.False(t, sel[5].Optional())
|
||||||
|
require.False(t, sel[5].Iterator())
|
||||||
|
require.Empty(t, sel[5].Slice())
|
||||||
|
require.Equal(t, sel[5].Field(), "baz")
|
||||||
|
require.Empty(t, sel[5].Index())
|
||||||
|
require.False(t, sel[6].Identity())
|
||||||
|
require.False(t, sel[6].Optional())
|
||||||
|
require.False(t, sel[6].Iterator())
|
||||||
|
require.Equal(t, sel[6].Slice(), []int64{1, math.MaxInt})
|
||||||
|
require.Empty(t, sel[6].Field())
|
||||||
|
require.Empty(t, sel[6].Index())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("non dotted", func(t *testing.T) {
|
||||||
|
_, err := Parse("foo")
|
||||||
|
require.NotNil(t, err)
|
||||||
|
fmt.Println(err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("non quoted", func(t *testing.T) {
|
||||||
|
_, err := Parse(".[foo]")
|
||||||
|
require.NotNil(t, err)
|
||||||
|
fmt.Println(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func printSegments(s Selector) {
|
||||||
|
for i, seg := range s {
|
||||||
|
fmt.Printf("%d: %s\n", i, seg.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,15 @@
|
|||||||
package selector
|
package selector
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ipld/go-ipld-prime"
|
"github.com/ipld/go-ipld-prime"
|
||||||
"github.com/ipld/go-ipld-prime/datamodel"
|
"github.com/ipld/go-ipld-prime/datamodel"
|
||||||
|
"github.com/ipld/go-ipld-prime/fluent/qp"
|
||||||
"github.com/ipld/go-ipld-prime/node/basicnode"
|
"github.com/ipld/go-ipld-prime/node/basicnode"
|
||||||
"github.com/ipld/go-ipld-prime/schema"
|
"github.com/ipld/go-ipld-prime/schema"
|
||||||
)
|
)
|
||||||
@@ -15,16 +19,20 @@ import (
|
|||||||
type Selector []segment
|
type Selector []segment
|
||||||
|
|
||||||
// Select perform the selection described by the selector on the input IPLD DAG.
|
// Select perform the selection described by the selector on the input IPLD DAG.
|
||||||
// If no error, Select returns either one ipld.Node or a []ipld.Node.
|
// Select can return:
|
||||||
func (s Selector) Select(subject ipld.Node) (ipld.Node, []ipld.Node, error) {
|
// - exactly one matched IPLD node
|
||||||
|
// - an error that can be checked with IsResolutionErr(err) to distinguish not being able to resolve to a node,
|
||||||
|
// or another type of error.
|
||||||
|
// - nil and no error, if the selector couldn't match on an optional segment (with ?).
|
||||||
|
func (s Selector) Select(subject ipld.Node) (ipld.Node, error) {
|
||||||
return resolve(s, subject, nil)
|
return resolve(s, subject, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MatchPath tells if the selector operates on the given (string only) path segments.
|
// // MatchPath tells if the selector operates on the given (string only) path segments.
|
||||||
// It returns the segments that didn't get consumed by the matching.
|
// // It returns the segments that didn't get consumed by the matching.
|
||||||
func (s Selector) MatchPath(pathSegment ...string) (bool, []string) {
|
// func (s Selector) MatchPath(pathSegment ...string) (bool, []string) {
|
||||||
return matchPath(s, pathSegment)
|
// return matchPath(s, pathSegment)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (s Selector) String() string {
|
func (s Selector) String() string {
|
||||||
var res strings.Builder
|
var res strings.Builder
|
||||||
@@ -39,7 +47,7 @@ type segment struct {
|
|||||||
identity bool
|
identity bool
|
||||||
optional bool
|
optional bool
|
||||||
iterator bool
|
iterator bool
|
||||||
slice []int
|
slice []int64 // either 0-length or 2-length
|
||||||
field string
|
field string
|
||||||
index int
|
index int
|
||||||
}
|
}
|
||||||
@@ -65,7 +73,7 @@ func (s segment) Iterator() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Slice flags that this segment targets a range of a slice.
|
// Slice flags that this segment targets a range of a slice.
|
||||||
func (s segment) Slice() []int {
|
func (s segment) Slice() []int64 {
|
||||||
return s.slice
|
return s.slice
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,337 +87,222 @@ func (s segment) Index() int {
|
|||||||
return s.index
|
return s.index
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolve(sel Selector, subject ipld.Node, at []string) (ipld.Node, []ipld.Node, error) {
|
func resolve(sel Selector, subject ipld.Node, at []string) (ipld.Node, error) {
|
||||||
cur := subject
|
errIfNotOptional := func(s segment, err error) error {
|
||||||
for i, seg := range sel {
|
if !s.Optional() {
|
||||||
if seg.Identity() {
|
return err
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1st level: handle the different segment types (iterator, field, slice, index)
|
|
||||||
// 2nd level: handle different node kinds (list, map, string, bytes)
|
|
||||||
switch {
|
|
||||||
case seg.Iterator():
|
|
||||||
if cur == nil || cur.Kind() == datamodel.Kind_Null {
|
|
||||||
if seg.Optional() {
|
|
||||||
// build empty list
|
|
||||||
nb := basicnode.Prototype.List.NewBuilder()
|
|
||||||
assembler, err := nb.BeginList(0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = assembler.Finish(); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nb.Build(), nil, nil
|
|
||||||
} else {
|
|
||||||
return nil, nil, newResolutionError(fmt.Sprintf("can not iterate over kind: %s", kindString(cur)), at)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var many []ipld.Node
|
|
||||||
switch cur.Kind() {
|
|
||||||
case datamodel.Kind_List:
|
|
||||||
it := cur.ListIterator()
|
|
||||||
for !it.Done() {
|
|
||||||
_, v, err := it.Next()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if there are more iterator segments
|
|
||||||
if len(sel) > i+1 && sel[i+1].Iterator() {
|
|
||||||
if v.Kind() == datamodel.Kind_List {
|
|
||||||
// recursively resolve the remaining selector segments
|
|
||||||
var o ipld.Node
|
|
||||||
var m []ipld.Node
|
|
||||||
o, m, err = resolve(sel[i+1:], v, at)
|
|
||||||
if err != nil {
|
|
||||||
// if the segment is optional and an error occurs, skip the current iteration.
|
|
||||||
if seg.Optional() {
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if m != nil {
|
|
||||||
many = append(many, m...)
|
|
||||||
} else if o != nil {
|
|
||||||
many = append(many, o)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// if the current value is not a list and the next segment is optional, skip the current iteration
|
|
||||||
if sel[i+1].Optional() {
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
return nil, nil, newResolutionError(fmt.Sprintf("can not iterate over kind: %s", kindString(v)), at)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// if there are no more iterator segments, append the current value to the result
|
|
||||||
many = append(many, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case datamodel.Kind_Map:
|
|
||||||
it := cur.MapIterator()
|
|
||||||
for !it.Done() {
|
|
||||||
_, v, err := it.Next()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(sel) > i+1 && sel[i+1].Iterator() {
|
|
||||||
if v.Kind() == datamodel.Kind_List {
|
|
||||||
var o ipld.Node
|
|
||||||
var m []ipld.Node
|
|
||||||
o, m, err = resolve(sel[i+1:], v, at)
|
|
||||||
if err != nil {
|
|
||||||
if seg.Optional() {
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if m != nil {
|
|
||||||
many = append(many, m...)
|
|
||||||
} else if o != nil {
|
|
||||||
many = append(many, o)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if sel[i+1].Optional() {
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
return nil, nil, newResolutionError(fmt.Sprintf("can not iterate over kind: %s", kindString(v)), at)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
many = append(many, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil, nil, newResolutionError(fmt.Sprintf("can not iterate over kind: %s", kindString(cur)), at)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, many, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
case seg.Field() != "":
|
|
||||||
at = append(at, seg.Field())
|
|
||||||
if cur == nil {
|
|
||||||
if seg.Optional() {
|
|
||||||
cur = nil
|
|
||||||
} else {
|
|
||||||
return nil, nil, newResolutionError(fmt.Sprintf("can not access field: %s on kind: %s", seg.Field(), kindString(cur)), at)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
switch cur.Kind() {
|
|
||||||
case datamodel.Kind_Map:
|
|
||||||
n, err := cur.LookupByString(seg.Field())
|
|
||||||
if err != nil {
|
|
||||||
if isMissing(err) {
|
|
||||||
if seg.Optional() {
|
|
||||||
cur = nil
|
|
||||||
} else {
|
|
||||||
return nil, nil, newResolutionError(fmt.Sprintf("object has no field named: %s", seg.Field()), at)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cur = n
|
|
||||||
}
|
|
||||||
case datamodel.Kind_List:
|
|
||||||
var many []ipld.Node
|
|
||||||
it := cur.ListIterator()
|
|
||||||
for !it.Done() {
|
|
||||||
_, v, err := it.Next()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if v.Kind() == datamodel.Kind_Map {
|
|
||||||
n, err := v.LookupByString(seg.Field())
|
|
||||||
if err == nil {
|
|
||||||
many = append(many, n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(many) > 0 {
|
|
||||||
cur = nil
|
|
||||||
return nil, many, nil
|
|
||||||
} else if seg.Optional() {
|
|
||||||
cur = nil
|
|
||||||
} else {
|
|
||||||
return nil, nil, newResolutionError(fmt.Sprintf("no elements in list have field named: %s", seg.Field()), at)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
if seg.Optional() {
|
|
||||||
cur = nil
|
|
||||||
} else {
|
|
||||||
return nil, nil, newResolutionError(fmt.Sprintf("can not access field: %s on kind: %s", seg.Field(), kindString(cur)), at)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case seg.Slice() != nil:
|
|
||||||
if cur == nil {
|
|
||||||
if seg.Optional() {
|
|
||||||
cur = nil
|
|
||||||
} else {
|
|
||||||
return nil, nil, newResolutionError(fmt.Sprintf("can not slice on kind: %s", kindString(cur)), at)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
slice := seg.Slice()
|
|
||||||
var start, end, length int64
|
|
||||||
switch cur.Kind() {
|
|
||||||
case datamodel.Kind_List:
|
|
||||||
length = cur.Length()
|
|
||||||
start, end = resolveSliceIndices(slice, length)
|
|
||||||
case datamodel.Kind_Bytes:
|
|
||||||
b, _ := cur.AsBytes()
|
|
||||||
length = int64(len(b))
|
|
||||||
start, end = resolveSliceIndices(slice, length)
|
|
||||||
case datamodel.Kind_String:
|
|
||||||
str, _ := cur.AsString()
|
|
||||||
length = int64(len(str))
|
|
||||||
start, end = resolveSliceIndices(slice, length)
|
|
||||||
default:
|
|
||||||
return nil, nil, newResolutionError(fmt.Sprintf("can not slice on kind: %s", kindString(cur)), at)
|
|
||||||
}
|
|
||||||
|
|
||||||
if start < 0 || end < start || end > length {
|
|
||||||
if seg.Optional() {
|
|
||||||
cur = nil
|
|
||||||
} else {
|
|
||||||
return nil, nil, newResolutionError(fmt.Sprintf("slice out of bounds: [%d:%d]", start, end), at)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
switch cur.Kind() {
|
|
||||||
case datamodel.Kind_List:
|
|
||||||
if end > cur.Length() {
|
|
||||||
end = cur.Length()
|
|
||||||
}
|
|
||||||
nb := basicnode.Prototype.List.NewBuilder()
|
|
||||||
assembler, _ := nb.BeginList(int64(end - start))
|
|
||||||
for i := start; i < end; i++ {
|
|
||||||
item, _ := cur.LookupByIndex(int64(i))
|
|
||||||
assembler.AssembleValue().AssignNode(item)
|
|
||||||
}
|
|
||||||
assembler.Finish()
|
|
||||||
cur = nb.Build()
|
|
||||||
case datamodel.Kind_Bytes:
|
|
||||||
b, _ := cur.AsBytes()
|
|
||||||
l := int64(len(b))
|
|
||||||
if end > l {
|
|
||||||
end = l
|
|
||||||
}
|
|
||||||
cur = basicnode.NewBytes(b[start:end])
|
|
||||||
case datamodel.Kind_String:
|
|
||||||
str, _ := cur.AsString()
|
|
||||||
l := int64(len(str))
|
|
||||||
if end > l {
|
|
||||||
end = l
|
|
||||||
}
|
|
||||||
cur = basicnode.NewString(str[start:end])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
default: // Index()
|
|
||||||
at = append(at, fmt.Sprintf("%d", seg.Index()))
|
|
||||||
if cur == nil {
|
|
||||||
if seg.Optional() {
|
|
||||||
cur = nil
|
|
||||||
} else {
|
|
||||||
return nil, nil, newResolutionError(fmt.Sprintf("can not access index: %d on kind: %s", seg.Index(), kindString(cur)), at)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
idx := seg.Index()
|
|
||||||
switch cur.Kind() {
|
|
||||||
case datamodel.Kind_List:
|
|
||||||
if idx < 0 {
|
|
||||||
idx = int(cur.Length()) + idx
|
|
||||||
}
|
|
||||||
if idx < 0 || idx >= int(cur.Length()) {
|
|
||||||
if seg.Optional() {
|
|
||||||
cur = nil
|
|
||||||
} else {
|
|
||||||
return nil, nil, newResolutionError(fmt.Sprintf("index out of bounds: %d", seg.Index()), at)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cur, _ = cur.LookupByIndex(int64(idx))
|
|
||||||
}
|
|
||||||
case datamodel.Kind_String:
|
|
||||||
str, _ := cur.AsString()
|
|
||||||
if idx < 0 {
|
|
||||||
idx = len(str) + idx
|
|
||||||
}
|
|
||||||
if idx < 0 || idx >= len(str) {
|
|
||||||
if seg.Optional() {
|
|
||||||
cur = nil
|
|
||||||
} else {
|
|
||||||
return nil, nil, newResolutionError(fmt.Sprintf("index out of bounds: %d", seg.Index()), at)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cur = basicnode.NewString(string(str[idx]))
|
|
||||||
}
|
|
||||||
case datamodel.Kind_Bytes:
|
|
||||||
b, _ := cur.AsBytes()
|
|
||||||
if idx < 0 {
|
|
||||||
idx = len(b) + idx
|
|
||||||
}
|
|
||||||
if idx < 0 || idx >= len(b) {
|
|
||||||
if seg.Optional() {
|
|
||||||
cur = nil
|
|
||||||
} else {
|
|
||||||
return nil, nil, newResolutionError(fmt.Sprintf("index out of bounds: %d", seg.Index()), at)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cur = basicnode.NewInt(int64(b[idx]))
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil, nil, newResolutionError(fmt.Sprintf("can not access index: %d on kind: %s", seg.Index(), kindString(cur)), at)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return cur, nil, nil
|
cur := subject
|
||||||
}
|
|
||||||
|
|
||||||
func matchPath(sel Selector, path []string) (bool, []string) {
|
|
||||||
for _, seg := range sel {
|
for _, seg := range sel {
|
||||||
if len(path) == 0 {
|
// 1st level: handle the different segment types (iterator, field, slice, index)
|
||||||
return true, path
|
// 2nd level: handle different node kinds (list, map, string, bytes)
|
||||||
}
|
|
||||||
switch {
|
switch {
|
||||||
case seg.Identity():
|
case seg.Identity():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
case seg.Iterator():
|
case seg.Iterator():
|
||||||
// we have reached a [] iterator, it should have matched earlier
|
switch {
|
||||||
return false, nil
|
case cur == nil || cur.Kind() == datamodel.Kind_Null:
|
||||||
|
if seg.Optional() {
|
||||||
|
// build an empty list
|
||||||
|
n, err := qp.BuildList(basicnode.Prototype.Any, 0, func(_ datamodel.ListAssembler) {})
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
return nil, newResolutionError(fmt.Sprintf("can not iterate over kind: %s", kindString(cur)), at)
|
||||||
|
|
||||||
|
case cur.Kind() == datamodel.Kind_List:
|
||||||
|
// iterators are no-op on list
|
||||||
|
continue
|
||||||
|
|
||||||
|
case cur.Kind() == datamodel.Kind_Map:
|
||||||
|
// iterators on maps collect the values
|
||||||
|
return qp.BuildList(basicnode.Prototype.Any, cur.Length(), func(l datamodel.ListAssembler) {
|
||||||
|
it := cur.MapIterator()
|
||||||
|
for !it.Done() {
|
||||||
|
_, v, err := it.Next()
|
||||||
|
if err != nil {
|
||||||
|
panic(err) // recovered by BuildList
|
||||||
|
}
|
||||||
|
qp.ListEntry(l, qp.Node(v))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, newResolutionError(fmt.Sprintf("can not iterate over kind: %s", kindString(cur)), at)
|
||||||
|
}
|
||||||
|
|
||||||
case seg.Field() != "":
|
case seg.Field() != "":
|
||||||
// if exact match on the segment, we continue
|
at = append(at, seg.Field())
|
||||||
if path[0] == seg.Field() {
|
switch {
|
||||||
path = path[1:]
|
case cur == nil:
|
||||||
continue
|
err := newResolutionError(fmt.Sprintf("can not access field: %s on kind: %s", seg.Field(), kindString(cur)), at)
|
||||||
}
|
return nil, errIfNotOptional(seg, err)
|
||||||
return false, nil
|
|
||||||
|
|
||||||
case seg.Slice() != nil:
|
case cur.Kind() == datamodel.Kind_Map:
|
||||||
// we have reached a [<int>:<int>] slicing, it should have matched earlier
|
n, err := cur.LookupByString(seg.Field())
|
||||||
return false, nil
|
if err != nil {
|
||||||
|
if !isMissing(err) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if seg.Optional() {
|
||||||
|
cur = nil
|
||||||
|
} else {
|
||||||
|
return nil, newResolutionError(fmt.Sprintf("object has no field named: %s", seg.Field()), at)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cur = n
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
err := newResolutionError(fmt.Sprintf("can not access field: %s on kind: %s", seg.Field(), kindString(cur)), at)
|
||||||
|
return nil, errIfNotOptional(seg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
case len(seg.Slice()) > 0:
|
||||||
|
if cur == nil {
|
||||||
|
err := newResolutionError(fmt.Sprintf("can not slice on kind: %s", kindString(cur)), at)
|
||||||
|
return nil, errIfNotOptional(seg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
slice := seg.Slice()
|
||||||
|
var start, end, length int64
|
||||||
|
switch cur.Kind() {
|
||||||
|
case datamodel.Kind_List:
|
||||||
|
length = cur.Length()
|
||||||
|
start, end = resolveSliceIndices(slice, length)
|
||||||
|
case datamodel.Kind_Bytes:
|
||||||
|
b, _ := cur.AsBytes()
|
||||||
|
length = int64(len(b))
|
||||||
|
start, end = resolveSliceIndices(slice, length)
|
||||||
|
|
||||||
|
// TODO: cleanup if confirmed that slicing/indexing on string is not legal
|
||||||
|
// case datamodel.Kind_String:
|
||||||
|
// str, _ := cur.AsString()
|
||||||
|
// length = int64(len(str))
|
||||||
|
// start, end = resolveSliceIndices(slice, length)
|
||||||
|
default:
|
||||||
|
return nil, newResolutionError(fmt.Sprintf("can not slice on kind: %s", kindString(cur)), at)
|
||||||
|
}
|
||||||
|
|
||||||
|
if start < 0 || end < start || end > length {
|
||||||
|
err := newResolutionError(fmt.Sprintf("slice out of bounds: [%d:%d]", start, end), at)
|
||||||
|
return nil, errIfNotOptional(seg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch cur.Kind() {
|
||||||
|
case datamodel.Kind_List:
|
||||||
|
sliced, err := qp.BuildList(basicnode.Prototype.Any, end-start, func(l datamodel.ListAssembler) {
|
||||||
|
for i := start; i <= end; i++ {
|
||||||
|
item, err := cur.LookupByIndex(i)
|
||||||
|
if err != nil {
|
||||||
|
panic(err) // recovered by BuildList
|
||||||
|
}
|
||||||
|
qp.ListEntry(l, qp.Node(item))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, errIfNotOptional(seg, err)
|
||||||
|
}
|
||||||
|
cur = sliced
|
||||||
|
case datamodel.Kind_Bytes:
|
||||||
|
b, _ := cur.AsBytes()
|
||||||
|
cur = basicnode.NewBytes(b[start:end])
|
||||||
|
// TODO: cleanup if confirmed that slicing/indexing on string is not legal
|
||||||
|
// case datamodel.Kind_String:
|
||||||
|
// str, _ := cur.AsString()
|
||||||
|
// cur = basicnode.NewString(str[start:end])
|
||||||
|
}
|
||||||
|
return cur, nil
|
||||||
|
|
||||||
default: // Index()
|
default: // Index()
|
||||||
// we have reached a [<int>] indexing, it should have matched earlier
|
at = append(at, strconv.Itoa(seg.Index()))
|
||||||
return false, nil
|
|
||||||
|
if cur == nil {
|
||||||
|
err := newResolutionError(fmt.Sprintf("can not access index: %d on kind: %s", seg.Index(), kindString(cur)), at)
|
||||||
|
return nil, errIfNotOptional(seg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
idx := seg.Index()
|
||||||
|
switch cur.Kind() {
|
||||||
|
case datamodel.Kind_List:
|
||||||
|
if idx < 0 {
|
||||||
|
idx = int(cur.Length()) + idx
|
||||||
|
}
|
||||||
|
if idx < 0 || idx >= int(cur.Length()) {
|
||||||
|
err := newResolutionError(fmt.Sprintf("index out of bounds: %d", seg.Index()), at)
|
||||||
|
return nil, errIfNotOptional(seg, err)
|
||||||
|
}
|
||||||
|
cur, _ = cur.LookupByIndex(int64(idx))
|
||||||
|
|
||||||
|
// TODO: cleanup if confirmed that slicing/indexing on string is not legal
|
||||||
|
// case datamodel.Kind_String:
|
||||||
|
// str, _ := cur.AsString()
|
||||||
|
// if idx < 0 {
|
||||||
|
// idx = len(str) + idx
|
||||||
|
// }
|
||||||
|
// if idx < 0 || idx >= len(str) {
|
||||||
|
// err := newResolutionError(fmt.Sprintf("index out of bounds: %d", seg.Index()), at)
|
||||||
|
// return nil, errIfNotOptional(seg, err)
|
||||||
|
// }
|
||||||
|
// cur = basicnode.NewString(string(str[idx]))
|
||||||
|
|
||||||
|
case datamodel.Kind_Bytes:
|
||||||
|
b, _ := cur.AsBytes()
|
||||||
|
if idx < 0 {
|
||||||
|
idx = len(b) + idx
|
||||||
|
}
|
||||||
|
if idx < 0 || idx >= len(b) {
|
||||||
|
err := newResolutionError(fmt.Sprintf("index out of bounds: %d", seg.Index()), at)
|
||||||
|
return nil, errIfNotOptional(seg, err)
|
||||||
|
}
|
||||||
|
cur = basicnode.NewInt(int64(b[idx]))
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, newResolutionError(fmt.Sprintf("can not access index: %d on kind: %s", seg.Index(), kindString(cur)), at)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true, path
|
|
||||||
|
// segment exhausted, we return where we are
|
||||||
|
return cur, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// func matchPath(sel Selector, path []string) (bool, []string) {
|
||||||
|
// for _, seg := range sel {
|
||||||
|
// if len(path) == 0 {
|
||||||
|
// return true, path
|
||||||
|
// }
|
||||||
|
// switch {
|
||||||
|
// case seg.Identity():
|
||||||
|
// continue
|
||||||
|
//
|
||||||
|
// case seg.Iterator():
|
||||||
|
// // we have reached a [] iterator, it should have matched earlier
|
||||||
|
// return false, nil
|
||||||
|
//
|
||||||
|
// case seg.Field() != "":
|
||||||
|
// // if exact match on the segment, we continue
|
||||||
|
// if path[0] == seg.Field() {
|
||||||
|
// path = path[1:]
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
// return false, nil
|
||||||
|
//
|
||||||
|
// case seg.Slice() != nil:
|
||||||
|
// // we have reached a [<int>:<int>] slicing, it should have matched earlier
|
||||||
|
// return false, nil
|
||||||
|
//
|
||||||
|
// default: // Index()
|
||||||
|
// // we have reached a [<int>] indexing, it should have matched earlier
|
||||||
|
// return false, nil
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return true, path
|
||||||
|
// }
|
||||||
|
|
||||||
// resolveSliceIndices resolves the start and end indices for slicing a list or byte array.
|
// resolveSliceIndices resolves the start and end indices for slicing a list or byte array.
|
||||||
//
|
//
|
||||||
// It takes the slice indices from the selector segment and the length of the list or byte array,
|
// It takes the slice indices from the selector segment and the length of the list or byte array,
|
||||||
@@ -422,26 +315,30 @@ func matchPath(sel Selector, path []string) (bool, []string) {
|
|||||||
// Returns:
|
// Returns:
|
||||||
// - start: The resolved start index for slicing.
|
// - start: The resolved start index for slicing.
|
||||||
// - end: The resolved end index for slicing.
|
// - end: The resolved end index for slicing.
|
||||||
func resolveSliceIndices(slice []int, length int64) (int64, int64) {
|
func resolveSliceIndices(slice []int64, length int64) (start int64, end int64) {
|
||||||
start, end := int64(0), length
|
if len(slice) != 2 {
|
||||||
if len(slice) > 0 {
|
panic("should always be 2-length")
|
||||||
start = int64(slice[0])
|
|
||||||
if start < 0 {
|
|
||||||
start = length + start
|
|
||||||
if start < 0 {
|
|
||||||
start = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if len(slice) > 1 {
|
|
||||||
end = int64(slice[1])
|
start, end = slice[0], slice[1]
|
||||||
if end <= 0 {
|
|
||||||
end = length + end
|
// adjust boundaries
|
||||||
if end < start {
|
switch {
|
||||||
end = start
|
case slice[0] == math.MinInt:
|
||||||
}
|
start = 0
|
||||||
}
|
case slice[0] < 0:
|
||||||
|
start = length + slice[0]
|
||||||
}
|
}
|
||||||
|
switch {
|
||||||
|
case slice[1] == math.MaxInt:
|
||||||
|
end = length - 1
|
||||||
|
case slice[1] < 0:
|
||||||
|
end = length + slice[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: is backward iteration allowed?
|
||||||
|
// if yes, we need to return a +1 or -1 "step"
|
||||||
|
// if no, we need to error
|
||||||
|
|
||||||
return start, end
|
return start, end
|
||||||
}
|
}
|
||||||
@@ -466,6 +363,10 @@ func isMissing(err error) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsResolutionErr(err error) bool {
|
||||||
|
return errors.As(err, &resolutionerr{})
|
||||||
|
}
|
||||||
|
|
||||||
type resolutionerr struct {
|
type resolutionerr struct {
|
||||||
msg string
|
msg string
|
||||||
at []string
|
at []string
|
||||||
|
|||||||
@@ -14,239 +14,6 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParse(t *testing.T) {
|
|
||||||
t.Run("identity", func(t *testing.T) {
|
|
||||||
sel, err := Parse(".")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, 1, len(sel))
|
|
||||||
require.True(t, sel[0].Identity())
|
|
||||||
require.False(t, sel[0].Optional())
|
|
||||||
require.False(t, sel[0].Iterator())
|
|
||||||
require.Empty(t, sel[0].Slice())
|
|
||||||
require.Empty(t, sel[0].Field())
|
|
||||||
require.Empty(t, sel[0].Index())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("field", func(t *testing.T) {
|
|
||||||
sel, err := Parse(".foo")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, 1, len(sel))
|
|
||||||
require.False(t, sel[0].Identity())
|
|
||||||
require.False(t, sel[0].Optional())
|
|
||||||
require.False(t, sel[0].Iterator())
|
|
||||||
require.Empty(t, sel[0].Slice())
|
|
||||||
require.Equal(t, sel[0].Field(), "foo")
|
|
||||||
require.Empty(t, sel[0].Index())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("explicit field", func(t *testing.T) {
|
|
||||||
sel, err := Parse(`.["foo"]`)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, 2, len(sel))
|
|
||||||
require.True(t, sel[0].Identity())
|
|
||||||
require.False(t, sel[0].Optional())
|
|
||||||
require.False(t, sel[0].Iterator())
|
|
||||||
require.Empty(t, sel[0].Slice())
|
|
||||||
require.Empty(t, sel[0].Field())
|
|
||||||
require.Empty(t, sel[0].Index())
|
|
||||||
require.False(t, sel[1].Identity())
|
|
||||||
require.False(t, sel[1].Optional())
|
|
||||||
require.False(t, sel[1].Iterator())
|
|
||||||
require.Empty(t, sel[1].Slice())
|
|
||||||
require.Equal(t, sel[1].Field(), "foo")
|
|
||||||
require.Empty(t, sel[1].Index())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("index", func(t *testing.T) {
|
|
||||||
sel, err := Parse(".[138]")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, 2, len(sel))
|
|
||||||
require.True(t, sel[0].Identity())
|
|
||||||
require.False(t, sel[0].Optional())
|
|
||||||
require.False(t, sel[0].Iterator())
|
|
||||||
require.Empty(t, sel[0].Slice())
|
|
||||||
require.Empty(t, sel[0].Field())
|
|
||||||
require.Empty(t, sel[0].Index())
|
|
||||||
require.False(t, sel[1].Identity())
|
|
||||||
require.False(t, sel[1].Optional())
|
|
||||||
require.False(t, sel[1].Iterator())
|
|
||||||
require.Empty(t, sel[1].Slice())
|
|
||||||
require.Empty(t, sel[1].Field())
|
|
||||||
require.Equal(t, sel[1].Index(), 138)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("negative index", func(t *testing.T) {
|
|
||||||
sel, err := Parse(".[-138]")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, 2, len(sel))
|
|
||||||
require.True(t, sel[0].Identity())
|
|
||||||
require.False(t, sel[0].Optional())
|
|
||||||
require.False(t, sel[0].Iterator())
|
|
||||||
require.Empty(t, sel[0].Slice())
|
|
||||||
require.Empty(t, sel[0].Field())
|
|
||||||
require.Empty(t, sel[0].Index())
|
|
||||||
require.False(t, sel[1].Identity())
|
|
||||||
require.False(t, sel[1].Optional())
|
|
||||||
require.False(t, sel[1].Iterator())
|
|
||||||
require.Empty(t, sel[1].Slice())
|
|
||||||
require.Empty(t, sel[1].Field())
|
|
||||||
require.Equal(t, sel[1].Index(), -138)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("iterator", func(t *testing.T) {
|
|
||||||
sel, err := Parse(".[]")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, 2, len(sel))
|
|
||||||
require.True(t, sel[0].Identity())
|
|
||||||
require.False(t, sel[0].Optional())
|
|
||||||
require.False(t, sel[0].Iterator())
|
|
||||||
require.Empty(t, sel[0].Slice())
|
|
||||||
require.Empty(t, sel[0].Field())
|
|
||||||
require.Empty(t, sel[0].Index())
|
|
||||||
require.False(t, sel[1].Identity())
|
|
||||||
require.False(t, sel[1].Optional())
|
|
||||||
require.True(t, sel[1].Iterator())
|
|
||||||
require.Empty(t, sel[1].Slice())
|
|
||||||
require.Empty(t, sel[1].Field())
|
|
||||||
require.Empty(t, sel[1].Index())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("optional field", func(t *testing.T) {
|
|
||||||
sel, err := Parse(".foo?")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, 1, len(sel))
|
|
||||||
require.False(t, sel[0].Identity())
|
|
||||||
require.True(t, sel[0].Optional())
|
|
||||||
require.False(t, sel[0].Iterator())
|
|
||||||
require.Empty(t, sel[0].Slice())
|
|
||||||
require.Equal(t, sel[0].Field(), "foo")
|
|
||||||
require.Empty(t, sel[0].Index())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("optional explicit field", func(t *testing.T) {
|
|
||||||
sel, err := Parse(`.["foo"]?`)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, 2, len(sel))
|
|
||||||
require.True(t, sel[0].Identity())
|
|
||||||
require.False(t, sel[0].Optional())
|
|
||||||
require.False(t, sel[0].Iterator())
|
|
||||||
require.Empty(t, sel[0].Slice())
|
|
||||||
require.Empty(t, sel[0].Field())
|
|
||||||
require.Empty(t, sel[0].Index())
|
|
||||||
require.False(t, sel[1].Identity())
|
|
||||||
require.True(t, sel[1].Optional())
|
|
||||||
require.False(t, sel[1].Iterator())
|
|
||||||
require.Empty(t, sel[1].Slice())
|
|
||||||
require.Equal(t, sel[1].Field(), "foo")
|
|
||||||
require.Empty(t, sel[1].Index())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("optional index", func(t *testing.T) {
|
|
||||||
sel, err := Parse(".[138]?")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, 2, len(sel))
|
|
||||||
require.True(t, sel[0].Identity())
|
|
||||||
require.False(t, sel[0].Optional())
|
|
||||||
require.False(t, sel[0].Iterator())
|
|
||||||
require.Empty(t, sel[0].Slice())
|
|
||||||
require.Empty(t, sel[0].Field())
|
|
||||||
require.Empty(t, sel[0].Index())
|
|
||||||
require.False(t, sel[1].Identity())
|
|
||||||
require.True(t, sel[1].Optional())
|
|
||||||
require.False(t, sel[1].Iterator())
|
|
||||||
require.Empty(t, sel[1].Slice())
|
|
||||||
require.Empty(t, sel[1].Field())
|
|
||||||
require.Equal(t, sel[1].Index(), 138)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("optional iterator", func(t *testing.T) {
|
|
||||||
sel, err := Parse(".[]?")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, 2, len(sel))
|
|
||||||
require.True(t, sel[0].Identity())
|
|
||||||
require.False(t, sel[0].Optional())
|
|
||||||
require.False(t, sel[0].Iterator())
|
|
||||||
require.Empty(t, sel[0].Slice())
|
|
||||||
require.Empty(t, sel[0].Field())
|
|
||||||
require.Empty(t, sel[0].Index())
|
|
||||||
require.False(t, sel[1].Identity())
|
|
||||||
require.True(t, sel[1].Optional())
|
|
||||||
require.True(t, sel[1].Iterator())
|
|
||||||
require.Empty(t, sel[1].Slice())
|
|
||||||
require.Empty(t, sel[1].Field())
|
|
||||||
require.Empty(t, sel[1].Index())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("nesting", func(t *testing.T) {
|
|
||||||
str := `.foo.["bar"].[138]?.baz[1:]`
|
|
||||||
sel, err := Parse(str)
|
|
||||||
require.NoError(t, err)
|
|
||||||
printSegments(sel)
|
|
||||||
require.Equal(t, str, sel.String())
|
|
||||||
require.Equal(t, 7, len(sel))
|
|
||||||
require.False(t, sel[0].Identity())
|
|
||||||
require.False(t, sel[0].Optional())
|
|
||||||
require.False(t, sel[0].Iterator())
|
|
||||||
require.Empty(t, sel[0].Slice())
|
|
||||||
require.Equal(t, sel[0].Field(), "foo")
|
|
||||||
require.Empty(t, sel[0].Index())
|
|
||||||
require.True(t, sel[1].Identity())
|
|
||||||
require.False(t, sel[1].Optional())
|
|
||||||
require.False(t, sel[1].Iterator())
|
|
||||||
require.Empty(t, sel[1].Slice())
|
|
||||||
require.Empty(t, sel[1].Field())
|
|
||||||
require.Empty(t, sel[1].Index())
|
|
||||||
require.False(t, sel[2].Identity())
|
|
||||||
require.False(t, sel[2].Optional())
|
|
||||||
require.False(t, sel[2].Iterator())
|
|
||||||
require.Empty(t, sel[2].Slice())
|
|
||||||
require.Equal(t, sel[2].Field(), "bar")
|
|
||||||
require.Empty(t, sel[2].Index())
|
|
||||||
require.True(t, sel[3].Identity())
|
|
||||||
require.False(t, sel[3].Optional())
|
|
||||||
require.False(t, sel[3].Iterator())
|
|
||||||
require.Empty(t, sel[3].Slice())
|
|
||||||
require.Empty(t, sel[3].Field())
|
|
||||||
require.Empty(t, sel[3].Index())
|
|
||||||
require.False(t, sel[4].Identity())
|
|
||||||
require.True(t, sel[4].Optional())
|
|
||||||
require.False(t, sel[4].Iterator())
|
|
||||||
require.Empty(t, sel[4].Slice())
|
|
||||||
require.Empty(t, sel[4].Field())
|
|
||||||
require.Equal(t, sel[4].Index(), 138)
|
|
||||||
require.False(t, sel[5].Identity())
|
|
||||||
require.False(t, sel[5].Optional())
|
|
||||||
require.False(t, sel[5].Iterator())
|
|
||||||
require.Empty(t, sel[5].Slice())
|
|
||||||
require.Equal(t, sel[5].Field(), "baz")
|
|
||||||
require.Empty(t, sel[5].Index())
|
|
||||||
require.False(t, sel[6].Identity())
|
|
||||||
require.False(t, sel[6].Optional())
|
|
||||||
require.False(t, sel[6].Iterator())
|
|
||||||
require.Equal(t, sel[6].Slice(), []int{1})
|
|
||||||
require.Empty(t, sel[6].Field())
|
|
||||||
require.Empty(t, sel[6].Index())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("non dotted", func(t *testing.T) {
|
|
||||||
_, err := Parse("foo")
|
|
||||||
require.NotNil(t, err)
|
|
||||||
fmt.Println(err)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("non quoted", func(t *testing.T) {
|
|
||||||
_, err := Parse(".[foo]")
|
|
||||||
require.NotNil(t, err)
|
|
||||||
fmt.Println(err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func printSegments(s Selector) {
|
|
||||||
for i, seg := range s {
|
|
||||||
fmt.Printf("%d: %s\n", i, seg.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSelect(t *testing.T) {
|
func TestSelect(t *testing.T) {
|
||||||
type name struct {
|
type name struct {
|
||||||
First string
|
First string
|
||||||
@@ -313,14 +80,13 @@ func TestSelect(t *testing.T) {
|
|||||||
sel, err := Parse(".")
|
sel, err := Parse(".")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
one, many, err := sel.Select(anode)
|
res, err := sel.Select(anode)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotEmpty(t, one)
|
require.NotEmpty(t, res)
|
||||||
require.Empty(t, many)
|
|
||||||
|
|
||||||
fmt.Println(printer.Sprint(one))
|
fmt.Println(printer.Sprint(res))
|
||||||
|
|
||||||
age := must.Int(must.Node(one.LookupByString("age")))
|
age := must.Int(must.Node(res.LookupByString("age")))
|
||||||
require.Equal(t, int64(alice.Age), age)
|
require.Equal(t, int64(alice.Age), age)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -328,24 +94,22 @@ func TestSelect(t *testing.T) {
|
|||||||
sel, err := Parse(".name.first")
|
sel, err := Parse(".name.first")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
one, many, err := sel.Select(anode)
|
res, err := sel.Select(anode)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotEmpty(t, one)
|
require.NotEmpty(t, res)
|
||||||
require.Empty(t, many)
|
|
||||||
|
|
||||||
fmt.Println(printer.Sprint(one))
|
fmt.Println(printer.Sprint(res))
|
||||||
|
|
||||||
name := must.String(one)
|
name := must.String(res)
|
||||||
require.Equal(t, alice.Name.First, name)
|
require.Equal(t, alice.Name.First, name)
|
||||||
|
|
||||||
one, many, err = sel.Select(bnode)
|
res, err = sel.Select(bnode)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotEmpty(t, one)
|
require.NotEmpty(t, res)
|
||||||
require.Empty(t, many)
|
|
||||||
|
|
||||||
fmt.Println(printer.Sprint(one))
|
fmt.Println(printer.Sprint(res))
|
||||||
|
|
||||||
name = must.String(one)
|
name = must.String(res)
|
||||||
require.Equal(t, bob.Name.First, name)
|
require.Equal(t, bob.Name.First, name)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -353,109 +117,88 @@ func TestSelect(t *testing.T) {
|
|||||||
sel, err := Parse(".name.middle?")
|
sel, err := Parse(".name.middle?")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
one, many, err := sel.Select(anode)
|
res, err := sel.Select(anode)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotEmpty(t, one)
|
require.NotEmpty(t, res)
|
||||||
require.Empty(t, many)
|
|
||||||
|
|
||||||
fmt.Println(printer.Sprint(one))
|
fmt.Println(printer.Sprint(res))
|
||||||
|
|
||||||
name := must.String(one)
|
name := must.String(res)
|
||||||
require.Equal(t, *alice.Name.Middle, name)
|
require.Equal(t, *alice.Name.Middle, name)
|
||||||
|
|
||||||
one, many, err = sel.Select(bnode)
|
res, err = sel.Select(bnode)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Empty(t, one)
|
require.Empty(t, res)
|
||||||
require.Empty(t, many)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("not exists", func(t *testing.T) {
|
t.Run("not exists", func(t *testing.T) {
|
||||||
sel, err := Parse(".name.foo")
|
sel, err := Parse(".name.foo")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
one, many, err := sel.Select(anode)
|
res, err := sel.Select(anode)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Empty(t, one)
|
require.Empty(t, res)
|
||||||
require.Empty(t, many)
|
|
||||||
|
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
|
|
||||||
require.ErrorAs(t, err, &resolutionerr{}, "error was not a resolution error")
|
require.ErrorAs(t, err, &resolutionerr{}, "error should be a resolution error")
|
||||||
|
require.True(t, IsResolutionErr(err))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("optional not exists", func(t *testing.T) {
|
t.Run("optional not exists", func(t *testing.T) {
|
||||||
sel, err := Parse(".name.foo?")
|
sel, err := Parse(".name.foo?")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
one, many, err := sel.Select(anode)
|
one, err := sel.Select(anode)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Empty(t, one)
|
require.Empty(t, one)
|
||||||
require.Empty(t, many)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("iterator", func(t *testing.T) {
|
t.Run("iterator", func(t *testing.T) {
|
||||||
sel, err := Parse(".interests[]")
|
sel, err := Parse(".interests[]")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
one, many, err := sel.Select(anode)
|
res, err := sel.Select(anode)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Empty(t, one)
|
require.NotEmpty(t, res)
|
||||||
require.NotEmpty(t, many)
|
|
||||||
|
|
||||||
for _, n := range many {
|
fmt.Println(printer.Sprint(res))
|
||||||
fmt.Println(printer.Sprint(n))
|
|
||||||
}
|
|
||||||
|
|
||||||
iname := must.String(must.Node(many[0].LookupByString("name")))
|
iname := must.String(must.Node(must.Node(res.LookupByIndex(0)).LookupByString("name")))
|
||||||
require.Equal(t, alice.Interests[0].Name, iname)
|
require.Equal(t, alice.Interests[0].Name, iname)
|
||||||
|
|
||||||
iname = must.String(must.Node(many[1].LookupByString("name")))
|
iname = must.String(must.Node(must.Node(res.LookupByIndex(1)).LookupByString("name")))
|
||||||
require.Equal(t, alice.Interests[1].Name, iname)
|
require.Equal(t, alice.Interests[1].Name, iname)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("map iterator", func(t *testing.T) {
|
// TODO: fully test slicing + negative numbers
|
||||||
sel, err := Parse(".interests[0][]")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
one, many, err := sel.Select(anode)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Empty(t, one)
|
|
||||||
require.NotEmpty(t, many)
|
|
||||||
|
|
||||||
for _, n := range many {
|
|
||||||
fmt.Println(printer.Sprint(n))
|
|
||||||
}
|
|
||||||
|
|
||||||
require.Equal(t, alice.Interests[0].Name, must.String(many[0]))
|
|
||||||
require.Equal(t, alice.Interests[0].Experience, int(must.Int(many[2])))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMatch(t *testing.T) {
|
// func TestMatch(t *testing.T) {
|
||||||
for _, tc := range []struct {
|
// for _, tc := range []struct {
|
||||||
sel string
|
// sel string
|
||||||
path []string
|
// path []string
|
||||||
want bool
|
// want bool
|
||||||
remaining []string
|
// remaining []string
|
||||||
}{
|
// }{
|
||||||
{sel: ".foo.bar", path: []string{"foo", "bar"}, want: true, remaining: []string{}},
|
// {sel: ".foo.bar", path: []string{"foo", "bar"}, want: true, remaining: []string{}},
|
||||||
{sel: ".foo.bar", path: []string{"foo"}, want: true, remaining: []string{}},
|
// {sel: ".foo.bar", path: []string{"foo"}, want: true, remaining: []string{}},
|
||||||
{sel: ".foo.bar", path: []string{"foo", "bar", "baz"}, want: true, remaining: []string{"baz"}},
|
// {sel: ".foo.bar", path: []string{"foo", "bar", "baz"}, want: true, remaining: []string{"baz"}},
|
||||||
{sel: ".foo.bar", path: []string{"foo", "faa"}, want: false},
|
// {sel: ".foo.bar", path: []string{"foo", "faa"}, want: false},
|
||||||
{sel: ".foo.[]", path: []string{"foo", "faa"}, want: false},
|
// {sel: ".foo.[]", path: []string{"foo", "faa"}, want: false},
|
||||||
{sel: ".foo.[]", path: []string{"foo"}, want: true, remaining: []string{}},
|
// {sel: ".foo.[]", path: []string{"foo"}, want: true, remaining: []string{}},
|
||||||
{sel: ".foo.bar?", path: []string{"foo"}, want: true, remaining: []string{}},
|
// {sel: ".foo.bar?", path: []string{"foo"}, want: true, remaining: []string{}},
|
||||||
{sel: ".foo.bar?", path: []string{"foo", "bar"}, want: true, remaining: []string{}},
|
// {sel: ".foo.bar?", path: []string{"foo", "bar"}, want: true, remaining: []string{}},
|
||||||
{sel: ".foo.bar?", path: []string{"foo", "baz"}, want: false},
|
// {sel: ".foo.bar?", path: []string{"foo", "baz"}, want: false},
|
||||||
} {
|
// } {
|
||||||
t.Run(tc.sel, func(t *testing.T) {
|
// t.Run(tc.sel, func(t *testing.T) {
|
||||||
sel := MustParse(tc.sel)
|
// sel := MustParse(tc.sel)
|
||||||
res, remain := sel.MatchPath(tc.path...)
|
// res, remain := sel.MatchPath(tc.path...)
|
||||||
require.Equal(t, tc.want, res)
|
// require.Equal(t, tc.want, res)
|
||||||
require.EqualValues(t, tc.remaining, remain)
|
// require.EqualValues(t, tc.remaining, remain)
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
func FuzzParse(f *testing.F) {
|
func FuzzParse(f *testing.F) {
|
||||||
selectorCorpus := []string{
|
selectorCorpus := []string{
|
||||||
@@ -520,6 +263,6 @@ func FuzzParseAndSelect(f *testing.F) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// look for panic()
|
// look for panic()
|
||||||
_, _, _ = sel.Select(node)
|
_, _ = sel.Select(node)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ func TestSupportedForms(t *testing.T) {
|
|||||||
Output string
|
Output string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass
|
// Pass and return a node
|
||||||
for _, testcase := range []Testcase{
|
for _, testcase := range []Testcase{
|
||||||
{Name: "Identity", Selector: `.`, Input: `{"x":1}`, Output: `{"x":1}`},
|
{Name: "Identity", Selector: `.`, Input: `{"x":1}`, Output: `{"x":1}`},
|
||||||
{Name: "Iterator", Selector: `.[]`, Input: `[1, 2]`, Output: `[1, 2]`},
|
{Name: "Iterator", Selector: `.[]`, Input: `[1, 2]`, Output: `[1, 2]`},
|
||||||
@@ -52,35 +52,16 @@ func TestSupportedForms(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// attempt to select
|
// attempt to select
|
||||||
node, nodes, err := sel.Select(makeNode(t, tc.Input))
|
res, err := sel.Select(makeNode(t, tc.Input))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotEqual(t, node != nil, len(nodes) > 0) // XOR (only one of node or nodes should be set)
|
require.NotNil(t, res)
|
||||||
|
|
||||||
// make an IPLD List node from a []datamodel.Node
|
|
||||||
if node == nil {
|
|
||||||
nb := basicnode.Prototype.List.NewBuilder()
|
|
||||||
la, err := nb.BeginList(int64(len(nodes)))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
for _, n := range nodes {
|
|
||||||
// TODO: This code is probably not needed if the Select operation properly prunes nil values - e.g.: Optional Iterator
|
|
||||||
if n == nil {
|
|
||||||
n = datamodel.Null
|
|
||||||
}
|
|
||||||
|
|
||||||
require.NoError(t, la.AssembleValue().AssignNode(n))
|
|
||||||
}
|
|
||||||
require.NoError(t, la.Finish())
|
|
||||||
|
|
||||||
node = nb.Build()
|
|
||||||
}
|
|
||||||
|
|
||||||
exp := makeNode(t, tc.Output)
|
exp := makeNode(t, tc.Output)
|
||||||
equalIPLD(t, exp, node)
|
equalIPLD(t, exp, res)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// null
|
// No error and return null, as optional
|
||||||
for _, testcase := range []Testcase{
|
for _, testcase := range []Testcase{
|
||||||
{Name: "Optional Missing Key", Selector: `.x?`, Input: `{}`},
|
{Name: "Optional Missing Key", Selector: `.x?`, Input: `{}`},
|
||||||
{Name: "Optional Null Key", Selector: `.x?`, Input: `null`},
|
{Name: "Optional Null Key", Selector: `.x?`, Input: `null`},
|
||||||
@@ -97,16 +78,13 @@ func TestSupportedForms(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// attempt to select
|
// attempt to select
|
||||||
node, nodes, err := sel.Select(makeNode(t, tc.Input))
|
res, err := sel.Select(makeNode(t, tc.Input))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
// TODO: should Select return a single node which is sometimes a list or null?
|
require.Nil(t, res)
|
||||||
// require.Equal(t, datamodel.Null, node)
|
|
||||||
assert.Nil(t, node)
|
|
||||||
assert.Empty(t, nodes)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// error
|
// fail to select and return an error
|
||||||
for _, testcase := range []Testcase{
|
for _, testcase := range []Testcase{
|
||||||
{Name: "Null Iterator", Selector: `.[]`, Input: `null`},
|
{Name: "Null Iterator", Selector: `.[]`, Input: `null`},
|
||||||
{Name: "Nested Iterator", Selector: `.[][]`, Input: `[[1], 2, [3]]`},
|
{Name: "Nested Iterator", Selector: `.[][]`, Input: `[[1], 2, [3]]`},
|
||||||
@@ -124,10 +102,10 @@ func TestSupportedForms(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// attempt to select
|
// attempt to select
|
||||||
node, nodes, err := sel.Select(makeNode(t, tc.Input))
|
res, err := sel.Select(makeNode(t, tc.Input))
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.Nil(t, node)
|
require.True(t, selector.IsResolutionErr(err))
|
||||||
assert.Empty(t, nodes)
|
require.Nil(t, res)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user