package selector import ( "fmt" "math" "testing" "github.com/stretchr/testify/require" "code.sonr.org/go/ucan/pkg/policy/limits" ) 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()) sel, err = Parse(".foo_bar") 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_bar") require.Empty(t, sel[0].Index()) sel, err = Parse(".foo-bar") 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-bar") require.Empty(t, sel[0].Index()) sel, err = Parse(".foo*bar") require.ErrorContains(t, err, "invalid segment") }) 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, 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("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) 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) }) t.Run("non quoted", func(t *testing.T) { _, err := Parse(".[foo]") require.NotNil(t, err) }) t.Run("slice with negative start and positive end", func(t *testing.T) { 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("slice with start greater than end", func(t *testing.T) { sel, err := Parse(".[5: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{5, 2}) require.Empty(t, sel[1].Field()) require.Empty(t, sel[1].Index()) }) t.Run("slice on string", func(t *testing.T) { sel, err := Parse(`.["foo"].[1:3]`) require.NoError(t, err) require.Equal(t, 4, 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()) require.True(t, sel[2].Identity()) require.False(t, sel[2].Optional()) require.False(t, sel[2].Iterator()) require.Empty(t, sel[2].Slice()) require.Empty(t, sel[2].Field()) require.Empty(t, sel[2].Index()) require.False(t, sel[3].Identity()) require.False(t, sel[3].Optional()) require.False(t, sel[3].Iterator()) require.Equal(t, sel[3].Slice(), []int64{1, 3}) require.Empty(t, sel[3].Field()) require.Empty(t, sel[3].Index()) }) t.Run("slice on array", func(t *testing.T) { sel, err := Parse(`.[1:3]`) 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{1, 3}) require.Empty(t, sel[1].Field()) require.Empty(t, sel[1].Index()) }) t.Run("index on array", func(t *testing.T) { sel, err := Parse(`.[1]`) 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(), 1) }) t.Run("invalid slice on object", func(t *testing.T) { _, err := Parse(`.["foo":"bar"]`) require.Error(t, err) require.Contains(t, err.Error(), "invalid segment") }) t.Run("index on object", 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("slice with non-integer start", func(t *testing.T) { _, err := Parse(".[foo:3]") require.Error(t, err) }) t.Run("slice with non-integer end", func(t *testing.T) { _, err := Parse(".[1:bar]") require.Error(t, err) }) t.Run("index with non-integer", func(t *testing.T) { _, err := Parse(".[foo]") require.Error(t, err) }) t.Run("extended field names", func(t *testing.T) { validFields := []string{ ".basic", ".user_name", ".user-name", ".userName$special", ".αβγ", // Greek letters ".użytkownik", // Polish characters ".用户", // Chinese characters ".사용자", // Korean characters "._private", ".number123", ".camelCase", ".snake_case", ".kebab-case", ".mixed_kebab-case", ".with$dollar", ".MIXED_Case_123", ".unicodeø", } for _, field := range validFields { sel, err := Parse(field) require.NoError(t, err, "field: %s", field) require.NotNil(t, sel) } invalidFields := []string{ ".123number", // Can't start with digit ".@special", // @ not allowed ".space name", // No spaces ".#hashtag", // No # ".name!", // No ! ".{brackets}", // No brackets ".name/with/slashes", // No slashes } for _, field := range invalidFields { sel, err := Parse(field) require.Error(t, err, "field: %s", field) require.Nil(t, sel) } }) t.Run("integer overflow", func(t *testing.T) { sel, err := Parse(fmt.Sprintf(".[%d]", limits.MaxInt53+1)) require.Error(t, err) require.Nil(t, sel) sel, err = Parse(fmt.Sprintf(".[%d]", limits.MinInt53-1)) require.Error(t, err) require.Nil(t, sel) // Test slice overflow sel, err = Parse(fmt.Sprintf(".[%d:42]", limits.MaxInt53+1)) require.Error(t, err) require.Nil(t, sel) sel, err = Parse(fmt.Sprintf(".[1:%d]", limits.MaxInt53+1)) require.Error(t, err) require.Nil(t, sel) }) }