diff --git a/literal/literal.go b/literal/literal.go index 61d949e..d5fe54e 100644 --- a/literal/literal.go +++ b/literal/literal.go @@ -1,124 +1,52 @@ package literal import ( - "fmt" - "github.com/ipld/go-ipld-prime" - "github.com/ipld/go-ipld-prime/datamodel" "github.com/ipld/go-ipld-prime/node/basicnode" ) -var ErrType = fmt.Errorf("literal is not this type") - -const ( - Kind_IPLD = "ipld" - Kind_Int = "int" - Kind_Float = "float" - Kind_String = "string" -) - -type Literal interface { - Kind() string // ipld | integer | float | string - AsNode() (ipld.Node, error) - AsInt() (int64, error) - AsFloat() (float64, error) - AsString() (string, error) +func Node(n ipld.Node) ipld.Node { + return n } -type literal struct{} - -func (l literal) AsFloat() (float64, error) { - return 0, ErrType -} - -func (l literal) AsInt() (int64, error) { - return 0, ErrType -} - -func (l literal) AsNode() (datamodel.Node, error) { - return nil, ErrType -} - -func (l literal) AsString() (string, error) { - return "", ErrType -} - -type node struct { - literal - value ipld.Node -} - -func (l node) AsNode() (datamodel.Node, error) { - return l.value, nil -} - -func (l node) Kind() string { - return Kind_IPLD -} - -func Node(n ipld.Node) Literal { - return node{value: n} -} - -func Link(cid ipld.Link) Literal { +func Link(cid ipld.Link) ipld.Node { nb := basicnode.Prototype.Link.NewBuilder() nb.AssignLink(cid) - return node{value: nb.Build()} + return nb.Build() } -func Bool(val bool) Literal { +func Bool(val bool) ipld.Node { nb := basicnode.Prototype.Bool.NewBuilder() nb.AssignBool(val) - return node{value: nb.Build()} + return nb.Build() } -type nint struct { - literal - value int64 +func Int(val int64) ipld.Node { + nb := basicnode.Prototype.Int.NewBuilder() + nb.AssignInt(val) + return nb.Build() } -func (l nint) AsInt() (int64, error) { - return l.value, nil +func Float(val float64) ipld.Node { + nb := basicnode.Prototype.Float.NewBuilder() + nb.AssignFloat(val) + return nb.Build() } -func (l nint) Kind() string { - return Kind_Int +func String(val string) ipld.Node { + nb := basicnode.Prototype.String.NewBuilder() + nb.AssignString(val) + return nb.Build() } -func Int(num int64) Literal { - return nint{value: num} +func Bytes(val []byte) ipld.Node { + nb := basicnode.Prototype.Bytes.NewBuilder() + nb.AssignBytes(val) + return nb.Build() } -type nfloat struct { - literal - value float64 -} - -func (l nfloat) AsFloat() (float64, error) { - return l.value, nil -} - -func (l nfloat) Kind() string { - return Kind_Float -} - -func Float(num float64) Literal { - return nfloat{value: num} -} - -type str struct { - literal - value string -} - -func (l str) AsString() (string, error) { - return l.value, nil -} - -func (l str) Kind() string { - return Kind_String -} - -func String(s string) Literal { - return str{value: s} +func Null() ipld.Node { + nb := basicnode.Prototype.Any.NewBuilder() + nb.AssignNull() + return nb.Build() } diff --git a/match.go b/match.go index 63266a2..95203ec 100644 --- a/match.go +++ b/match.go @@ -5,8 +5,6 @@ import ( "fmt" "github.com/ipld/go-ipld-prime" - "github.com/ipld/go-ipld-prime/datamodel" - "github.com/storacha-network/go-ucanto/core/policy/literal" "github.com/storacha-network/go-ucanto/core/policy/selector" ) @@ -25,58 +23,43 @@ func matchStatement(statement Statement, node ipld.Node) (bool, error) { switch statement.Kind() { case Kind_Equal: if s, ok := statement.(EqualityStatement); ok { - n, err := selectNode(s.Selector(), node) - if err != nil { - if _, ok := err.(datamodel.ErrNotExists); ok { - return false, nil - } - return false, fmt.Errorf("selecting node: %w", err) + one, _, err := selector.Select(s.Selector(), node) + if err != nil || one == nil { + return false, nil } - return isDeepEqual(s.Value(), n) + return isDeepEqual(s.Value(), one) } case Kind_GreaterThan: if s, ok := statement.(InequalityStatement); ok { - n, err := selectNode(s.Selector(), node) - if err != nil { - if _, ok := err.(datamodel.ErrNotExists); ok { - return false, nil - } - return false, fmt.Errorf("selecting node: %w", err) + one, _, err := selector.Select(s.Selector(), node) + if err != nil || one == nil { + return false, nil } - return isOrdered(s, n, gt) + return isOrdered(s.Value(), one, gt) } case Kind_GreaterThanOrEqual: if s, ok := statement.(InequalityStatement); ok { - n, err := selectNode(s.Selector(), node) - if err != nil { - if _, ok := err.(datamodel.ErrNotExists); ok { - return false, nil - } - return false, fmt.Errorf("selecting node: %w", err) + one, _, err := selector.Select(s.Selector(), node) + if err != nil || one == nil { + return false, nil } - return isOrdered(s, n, gte) + return isOrdered(s.Value(), one, gte) } case Kind_LessThan: if s, ok := statement.(InequalityStatement); ok { - n, err := selectNode(s.Selector(), node) - if err != nil { - if _, ok := err.(datamodel.ErrNotExists); ok { - return false, nil - } - return false, fmt.Errorf("selecting node: %w", err) + one, _, err := selector.Select(s.Selector(), node) + if err != nil || one == nil { + return false, nil } - return isOrdered(s, n, lt) + return isOrdered(s.Value(), one, lt) } case Kind_LessThanOrEqual: if s, ok := statement.(InequalityStatement); ok { - n, err := selectNode(s.Selector(), node) - if err != nil { - if _, ok := err.(datamodel.ErrNotExists); ok { - return false, nil - } - return false, fmt.Errorf("selecting node: %w", err) + one, _, err := selector.Select(s.Selector(), node) + if err != nil || one == nil { + return false, nil } - return isOrdered(s, n, lte) + return isOrdered(s.Value(), one, lte) } case Kind_Negation: if s, ok := statement.(NegationStatement); ok { @@ -101,6 +84,9 @@ func matchStatement(statement Statement, node ipld.Node) (bool, error) { } case Kind_Disjunction: if s, ok := statement.(DisjunctionStatement); ok { + if len(s.Value()) == 0 { + return true, nil + } for _, cs := range s.Value() { r, err := matchStatement(cs, node) if err != nil { @@ -116,124 +102,102 @@ func matchStatement(statement Statement, node ipld.Node) (bool, error) { case Kind_Universal: case Kind_Existential: } - return false, fmt.Errorf("statement kind not implemented: %s", statement.Kind()) + return false, fmt.Errorf("unimplemented statement kind: %s", statement.Kind()) } -func selectNode(sel selector.Selector, node ipld.Node) (child ipld.Node, err error) { - if sel.Identity() { - child = node - } else if sel.Field() != "" { - child, err = node.LookupByString(sel.Field()) - } else { - child, err = node.LookupByIndex(int64(sel.Index())) - } - return -} - -func isOrdered(stmt InequalityStatement, node ipld.Node, satisfies func(order int) bool) (bool, error) { - if stmt.Value().Kind() == literal.Kind_Int && node.Kind() == ipld.Kind_Int { - a, err := node.AsInt() +func isOrdered(expected ipld.Node, actual ipld.Node, satisfies func(order int) bool) (bool, error) { + if expected.Kind() == ipld.Kind_Int && actual.Kind() == ipld.Kind_Int { + a, err := actual.AsInt() if err != nil { return false, fmt.Errorf("extracting node int: %w", err) } - b, err := stmt.Value().AsInt() + b, err := expected.AsInt() if err != nil { return false, fmt.Errorf("extracting selector int: %w", err) } return satisfies(cmp.Compare(a, b)), nil } - if stmt.Value().Kind() == literal.Kind_Float && node.Kind() == ipld.Kind_Float { - a, err := node.AsFloat() + if expected.Kind() == ipld.Kind_Float && actual.Kind() == ipld.Kind_Float { + a, err := actual.AsFloat() if err != nil { return false, fmt.Errorf("extracting node float: %w", err) } - b, err := stmt.Value().AsFloat() + b, err := expected.AsFloat() if err != nil { return false, fmt.Errorf("extracting selector float: %w", err) } return satisfies(cmp.Compare(a, b)), nil } - return false, fmt.Errorf("selector type %s is not compatible with node type %s: kind mismatch: need int or float", stmt.Value().Kind(), node.Kind()) + return false, fmt.Errorf("unsupported IPLD kinds in ordered comparison: %s %s", expected.Kind(), actual.Kind()) } -func isDeepEqual(value literal.Literal, node ipld.Node) (bool, error) { - switch value.Kind() { - case literal.Kind_String: - if node.Kind() != ipld.Kind_String { - return false, nil - } - a, err := node.AsString() +func isDeepEqual(expected ipld.Node, actual ipld.Node) (bool, error) { + if expected.Kind() != actual.Kind() { + return false, nil + } + // TODO: should be easy enough to do the basic types, map, struct and list + // might be harder. + switch expected.Kind() { + case ipld.Kind_String: + a, err := actual.AsString() if err != nil { return false, fmt.Errorf("extracting node string: %w", err) } - b, err := value.AsString() + b, err := expected.AsString() if err != nil { return false, fmt.Errorf("extracting selector string: %w", err) } return a == b, nil - case literal.Kind_Int: - if node.Kind() != ipld.Kind_Int { + case ipld.Kind_Int: + if actual.Kind() != ipld.Kind_Int { return false, nil } - a, err := node.AsInt() + a, err := actual.AsInt() if err != nil { return false, fmt.Errorf("extracting node int: %w", err) } - b, err := value.AsInt() + b, err := expected.AsInt() if err != nil { return false, fmt.Errorf("extracting selector int: %w", err) } return a == b, nil - case literal.Kind_Float: - if node.Kind() != ipld.Kind_Float { + case ipld.Kind_Float: + if actual.Kind() != ipld.Kind_Float { return false, nil } - a, err := node.AsFloat() + a, err := actual.AsFloat() if err != nil { return false, fmt.Errorf("extracting node float: %w", err) } - b, err := value.AsFloat() + b, err := expected.AsFloat() if err != nil { return false, fmt.Errorf("extracting selector float: %w", err) } return a == b, nil - case literal.Kind_IPLD: - v, err := value.AsNode() + case ipld.Kind_Bool: + a, err := actual.AsBool() if err != nil { - return false, fmt.Errorf("extracting selector node: %w", err) + return false, fmt.Errorf("extracting node boolean: %w", err) } - if v.Kind() != node.Kind() { - return false, nil + b, err := expected.AsBool() + if err != nil { + return false, fmt.Errorf("extracting selector node boolean: %w", err) } - // TODO: should be easy enough to do the basic types, map, struct and list - // might be harder. - switch v.Kind() { - case ipld.Kind_Bool: - a, err := node.AsBool() - if err != nil { - return false, fmt.Errorf("extracting node boolean: %w", err) - } - b, err := v.AsBool() - if err != nil { - return false, fmt.Errorf("extracting selector node boolean: %w", err) - } - return a == b, nil - case ipld.Kind_Link: - a, err := node.AsLink() - if err != nil { - return false, fmt.Errorf("extracting node link: %w", err) - } - b, err := v.AsLink() - if err != nil { - return false, fmt.Errorf("extracting selector node link: %w", err) - } - return a.Binary() == b.Binary(), nil + return a == b, nil + case ipld.Kind_Link: + a, err := actual.AsLink() + if err != nil { + return false, fmt.Errorf("extracting node link: %w", err) } - return false, fmt.Errorf("unsupported IPLD kind: %s", v.Kind()) + b, err := expected.AsLink() + if err != nil { + return false, fmt.Errorf("extracting selector node link: %w", err) + } + return a.Binary() == b.Binary(), nil } - return false, fmt.Errorf("unknown literal kind: %s", value.Kind()) + return false, fmt.Errorf("unsupported IPLD kind in equality comparison: %s", expected.Kind()) } func gt(order int) bool { return order == 1 } diff --git a/match_test.go b/match_test.go index 5d9f1f7..69e27dd 100644 --- a/match_test.go +++ b/match_test.go @@ -252,6 +252,11 @@ func TestMatch(t *testing.T) { ok, err = Match(pol, nd) require.NoError(t, err) require.False(t, ok) + + pol = Policy{And()} + ok, err = Match(pol, nd) + require.NoError(t, err) + require.True(t, ok) }) t.Run("disjunction", func(t *testing.T) { @@ -279,5 +284,10 @@ func TestMatch(t *testing.T) { ok, err = Match(pol, nd) require.NoError(t, err) require.False(t, ok) + + pol = Policy{Or()} + ok, err = Match(pol, nd) + require.NoError(t, err) + require.True(t, ok) }) } diff --git a/selector/selector.go b/selector/selector.go index c00f840..373a5b8 100644 --- a/selector/selector.go +++ b/selector/selector.go @@ -2,103 +2,232 @@ package selector import ( "fmt" + "regexp" "strconv" "strings" + + "github.com/ipld/go-ipld-prime" + "github.com/ipld/go-ipld-prime/datamodel" ) // Selector describes a UCAN policy selector, as specified here: // https://github.com/ucan-wg/delegation/blob/4094d5878b58f5d35055a3b93fccda0b8329ebae/README.md#selectors -type Selector interface { +type Selector []Segment + +func (s Selector) String() string { + var str string + for _, seg := range s { + str += seg.String() + } + return str +} + +type Segment interface { // Identity flags that this selector is the identity selector. Identity() bool // Optional flags that this selector is optional. Optional() bool + // Iterator flags that this selector is an iterator segment. + Iterator() bool + // Slice flags that this segemnt targets a range of a slice. + Slice() []int // Field is the name of a field in a struct/map. Field() string // Index is an index of a slice. Index() int - // String returns the selector's string representation. + // String returns the segment's string representation. String() string } -type selector struct { +var Identity = Segment(segment{".", true, false, false, nil, "", 0}) + +type segment struct { str string identity bool optional bool + iterator bool + slice []int field string index int } -func (s selector) Field() string { - return s.field -} - -func (s selector) Identity() bool { - return s.identity -} - -func (s selector) Index() int { - return s.index -} - -func (s selector) Optional() bool { - return s.optional -} - -func (s selector) String() string { +func (s segment) String() string { return s.str } -// TODO: probably regex or better parser -func Parse(sel string) (Selector, error) { - s := sel - if s == "." { - return selector{sel, true, false, "", 0}, nil +func (s segment) Identity() bool { + return s.identity +} + +func (s segment) Optional() bool { + return s.optional +} + +func (s segment) Iterator() bool { + return s.iterator +} + +func (s segment) Slice() []int { + return s.slice +} + +func (s segment) Field() string { + return s.field +} + +func (s segment) Index() int { + return s.index +} + +func Parse(str string) (Selector, error) { + if string(str[0]) != "." { + return nil, NewParseError("selector must start with identity segment '.'", str, 0, string(str[0])) } - optional := strings.HasSuffix(s, "?") - if optional { - s = s[0 : len(s)-1] + col := 0 + var sel Selector + for _, tok := range tokenize(str) { + seg := tok + opt := strings.HasSuffix(tok, "?") + if opt { + seg = tok[0 : len(tok)-1] + } + switch seg { + case ".": + if len(sel) > 0 && sel[len(sel)-1].Identity() { + return nil, NewParseError("selector contains unsupported recursive descent segment: '..'", str, col, tok) + } + sel = append(sel, Identity) + case "[]": + sel = append(sel, segment{tok, false, opt, true, nil, "", 0}) + default: + if strings.HasPrefix(seg, "[") && strings.HasSuffix(seg, "]") { + lookup := seg[1 : len(seg)-1] + + if regexp.MustCompile(`^-?\d+$`).MatchString(lookup) { // index + idx, err := strconv.Atoi(lookup) + if err != nil { + return nil, NewParseError("invalid index", str, col, tok) + } + sel = append(sel, segment{str: tok, optional: opt, index: idx}) + } else if strings.HasPrefix(lookup, "\"") && strings.HasSuffix(lookup, "\"") { // explicit field + sel = append(sel, segment{str: tok, optional: opt, field: lookup[1 : len(lookup)-1]}) + } else if regexp.MustCompile(`^((\-?\d+:\-?\d*)|(\-?\d*:\-?\d+))$`).MatchString(lookup) { // slice [3:5] or [:5] or [3:] + var rng []int + splt := strings.Split(lookup, ":") + if splt[0] == "" { + 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 regexp.MustCompile(`^\.[a-zA-Z_]*?$`).MatchString(seg) { + sel = append(sel, segment{str: tok, optional: opt, field: seg[1:]}) + } else { + return nil, NewParseError(fmt.Sprintf("invalid segment: %s", seg), str, col, tok) + } + } + col += len(tok) } + return sel, nil +} - dotted := strings.HasPrefix(s, ".") - if dotted { - s = s[1:] - } +func tokenize(str string) []string { + var toks []string + col := 0 + ofs := 0 + ctx := "" - // collection values - if s == "[]" { - return nil, fmt.Errorf("unsupported selector: %s", sel) - } + for col < len(str) { + char := string(str[col]) - if strings.HasPrefix(s, "[") && strings.HasSuffix(s, "]") { - s = s[1 : len(s)-1] - - // explicit field selector - if strings.HasPrefix(s, "\"") && strings.HasSuffix(s, "\"") { - return selector{sel, false, optional, s[1 : len(s)-1], 0}, nil + if char == "\"" && string(str[col-1]) != "\\" { + col++ + if ctx == "\"" { + ctx = "" + } else { + ctx = "\"" + } + continue } - // collection range - if strings.Contains(s, ":") { - return nil, fmt.Errorf("unsupported selector: %s", sel) + if ctx == "\"" { + col++ + continue } - // index selector - idx, err := strconv.Atoi(s) - if err != nil { - return nil, fmt.Errorf("parsing index selector value: %s", err) + if char == "." || char == "[" { + if ofs < col { + toks = append(toks, str[ofs:col]) + } + ofs = col } - - return selector{sel, false, optional, "", idx}, nil + col++ } - if !dotted { - return nil, fmt.Errorf("invalid selector: %s", sel) + if ofs < col && ctx != "\"" { + toks = append(toks, str[ofs:col]) } - // dotted field selector - return selector{sel, false, optional, s, 0}, nil + return toks +} + +type ParseError interface { + error + Name() string + Message() string + Source() string + Column() int + Token() string +} + +type parseerr struct { + msg string + src string + col int + tok string +} + +func (p parseerr) Name() string { + return "ParseError" +} + +func (p parseerr) Message() string { + return p.msg +} + +func (p parseerr) Column() int { + return p.col +} + +func (p parseerr) Error() string { + return p.msg +} + +func (p parseerr) Source() string { + return p.src +} + +func (p parseerr) Token() string { + return p.tok +} + +func NewParseError(message string, source string, column int, token string) error { + return parseerr{message, source, column, token} } func MustParse(sel string) Selector { @@ -108,3 +237,168 @@ func MustParse(sel string) Selector { } return s } + +func Select(sel Selector, subject ipld.Node) (ipld.Node, []ipld.Node, error) { + return resolve(sel, subject, nil) +} + +func resolve(sel Selector, subject ipld.Node, at []string) (ipld.Node, []ipld.Node, error) { + cur := subject + for i, seg := range sel { + if seg.Identity() { + continue + } else if seg.Iterator() { + if cur != nil && cur.Kind() == datamodel.Kind_List { + var many []ipld.Node + it := cur.ListIterator() + for { + if it.Done() { + break + } + + i, v, err := it.Next() + if err != nil { + return nil, nil, err + } + + key := fmt.Sprintf("%d", i) + o, m, err := resolve(sel[i+1:], v, append(at[:], key)) + if err != nil { + return nil, nil, err + } + + if m != nil { + many = append(many, m...) + } else { + many = append(many, o) + } + } + return nil, many, nil + } else if cur != nil && cur.Kind() == datamodel.Kind_Map { + var many []ipld.Node + it := cur.MapIterator() + for { + if it.Done() { + break + } + + k, v, err := it.Next() + if err != nil { + return nil, nil, err + } + + key, _ := k.AsString() + o, m, err := resolve(sel[i+1:], v, append(at[:], key)) + if err != nil { + return nil, nil, err + } + + if m != nil { + many = append(many, m...) + } else { + many = append(many, o) + } + } + return nil, many, nil + } else if seg.Optional() { + cur = nil + } else { + return nil, nil, NewResolutionError(fmt.Sprintf("can not iterate over kind: %s", kindString(cur)), at) + } + + } else if seg.Field() != "" { + at = append(at, seg.Field()) + if cur != nil && cur.Kind() == datamodel.Kind_Map { + n, err := cur.LookupByString(seg.Field()) + if err != nil { + if _, ok := err.(datamodel.ErrNotExists); ok { + 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 + } + } + cur = n + } else 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 if seg.Slice() != nil { + if cur != nil && cur.Kind() == datamodel.Kind_List { + return nil, nil, NewResolutionError("list slice selection not yet implemented", at) + } else if cur != nil && cur.Kind() == datamodel.Kind_Bytes { + return nil, nil, NewResolutionError("bytes slice selection not yet implemented", at) + } else if seg.Optional() { + cur = nil + } else { + return nil, nil, NewResolutionError(fmt.Sprintf("can not index: %s on kind: %s", seg.Field(), kindString(cur)), at) + } + } else { + at = append(at, fmt.Sprintf("%d", seg.Index())) + if cur != nil && cur.Kind() == datamodel.Kind_List { + n, err := cur.LookupByIndex(int64(seg.Index())) + if err != nil { + if _, ok := err.(datamodel.ErrNotExists); ok { + if seg.Optional() { + cur = nil + } else { + return nil, nil, NewResolutionError(fmt.Sprintf("index out of bounds: %d", seg.Index()), at) + } + } else { + return nil, nil, err + } + } + cur = n + } else 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) + } + } + } + + return cur, nil, nil +} + +func kindString(n datamodel.Node) string { + if n == nil { + return "null" + } + return n.Kind().String() +} + +type ResolutionError interface { + error + Name() string + Message() string + At() []string +} + +type resolutionerr struct { + msg string + at []string +} + +func (r resolutionerr) Name() string { + return "ResolutionError" +} + +func (r resolutionerr) Message() string { + return fmt.Sprintf("can not resolve path: .%s", strings.Join(r.at, ".")) +} + +func (r resolutionerr) At() []string { + return r.at +} + +func (r resolutionerr) Error() string { + return r.Message() +} + +func NewResolutionError(message string, at []string) error { + return resolutionerr{message, at} +} diff --git a/selector/selector_test.go b/selector/selector_test.go index d8fab0b..3173cd1 100644 --- a/selector/selector_test.go +++ b/selector/selector_test.go @@ -11,124 +11,229 @@ func TestParse(t *testing.T) { t.Run("identity", func(t *testing.T) { sel, err := Parse(".") require.NoError(t, err) - require.True(t, sel.Identity()) - require.False(t, sel.Optional()) - require.Empty(t, sel.Field()) - require.Empty(t, sel.Index()) + 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", func(t *testing.T) { + t.Run("field", func(t *testing.T) { sel, err := Parse(".foo") require.NoError(t, err) - require.False(t, sel.Identity()) - require.False(t, sel.Optional()) - require.Equal(t, sel.Field(), "foo") - require.Empty(t, sel.Index()) - }) - - t.Run("dotted explicit field", func(t *testing.T) { - sel, err := Parse(".[\"foo\"]") - require.NoError(t, err) - require.False(t, sel.Identity()) - require.False(t, sel.Optional()) - require.Equal(t, sel.Field(), "foo") - require.Empty(t, sel.Index()) - }) - - t.Run("dotted index", func(t *testing.T) { - sel, err := Parse(".[138]") - require.NoError(t, err) - require.False(t, sel.Identity()) - require.False(t, sel.Optional()) - require.Empty(t, sel.Field()) - require.Equal(t, sel.Index(), 138) + 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\"]") + sel, err := Parse(`.["foo"]`) require.NoError(t, err) - require.False(t, sel.Identity()) - require.False(t, sel.Optional()) - require.Equal(t, sel.Field(), "foo") - require.Empty(t, sel.Index()) + 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]") + sel, err := Parse(".[138]") require.NoError(t, err) - require.False(t, sel.Identity()) - require.False(t, sel.Optional()) - require.Empty(t, sel.Field()) - require.Equal(t, sel.Index(), 138) + 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]") + sel, err := Parse(".[-138]") require.NoError(t, err) - require.False(t, sel.Identity()) - require.False(t, sel.Optional()) - require.Empty(t, sel.Field()) - require.Equal(t, sel.Index(), -138) + 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("optional dotted field", func(t *testing.T) { + 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.False(t, sel.Identity()) - require.True(t, sel.Optional()) - require.Equal(t, sel.Field(), "foo") - require.Empty(t, sel.Index()) - }) - - t.Run("optional dotted explicit field", func(t *testing.T) { - sel, err := Parse(".[\"foo\"]?") - require.NoError(t, err) - require.False(t, sel.Identity()) - require.True(t, sel.Optional()) - require.Equal(t, sel.Field(), "foo") - require.Empty(t, sel.Index()) - }) - - t.Run("optional dotted index", func(t *testing.T) { - sel, err := Parse(".[138]?") - require.NoError(t, err) - require.False(t, sel.Identity()) - require.True(t, sel.Optional()) - require.Empty(t, sel.Field()) - require.Equal(t, sel.Index(), 138) + 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\"]?") + sel, err := Parse(`.["foo"]?`) require.NoError(t, err) - require.False(t, sel.Identity()) - require.True(t, sel.Optional()) - require.Equal(t, sel.Field(), "foo") - require.Empty(t, sel.Index()) + 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]?") + sel, err := Parse(".[138]?") require.NoError(t, err) - require.False(t, sel.Identity()) - require.True(t, sel.Optional()) - require.Empty(t, sel.Field()) - require.Equal(t, sel.Index(), 138) + 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) { + sel, err := Parse(`.foo.["bar"].[138]?.baz[1:]`) + require.NoError(t, err) + printSegments(sel) + 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") - if err == nil { - t.Fatalf("expected error parsing selector") - } + require.NotNil(t, err) fmt.Println(err) }) t.Run("non quoted", func(t *testing.T) { _, err := Parse(".[foo]") - if err == nil { - t.Fatalf("expected error parsing selector") - } + require.NotNil(t, err) fmt.Println(err) }) } + +func printSegments(s Selector) { + for i, seg := range s { + fmt.Printf("%d: %s\n", i, seg.String()) + } +} diff --git a/statement.go b/statement.go index cbedc9f..26201fa 100644 --- a/statement.go +++ b/statement.go @@ -3,7 +3,7 @@ package policy // https://github.com/ucan-wg/delegation/blob/4094d5878b58f5d35055a3b93fccda0b8329ebae/README.md#policy import ( - "github.com/storacha-network/go-ucanto/core/policy/literal" + "github.com/ipld/go-ipld-prime" "github.com/storacha-network/go-ucanto/core/policy/selector" ) @@ -30,13 +30,13 @@ type Statement interface { type EqualityStatement interface { Statement Selector() selector.Selector - Value() literal.Literal + Value() ipld.Node } type InequalityStatement interface { Statement Selector() selector.Selector - Value() literal.Literal + Value() ipld.Node } type WildcardStatement interface { @@ -73,14 +73,14 @@ type QuantifierStatement interface { type equality struct { kind string selector selector.Selector - value literal.Literal + value ipld.Node } func (e equality) Kind() string { return e.kind } -func (e equality) Value() literal.Literal { +func (e equality) Value() ipld.Node { return e.value } @@ -88,23 +88,23 @@ func (e equality) Selector() selector.Selector { return e.selector } -func Equal(selector selector.Selector, value literal.Literal) EqualityStatement { +func Equal(selector selector.Selector, value ipld.Node) EqualityStatement { return equality{Kind_Equal, selector, value} } -func GreaterThan(selector selector.Selector, value literal.Literal) InequalityStatement { +func GreaterThan(selector selector.Selector, value ipld.Node) InequalityStatement { return equality{Kind_GreaterThan, selector, value} } -func GreaterThanOrEqual(selector selector.Selector, value literal.Literal) InequalityStatement { +func GreaterThanOrEqual(selector selector.Selector, value ipld.Node) InequalityStatement { return equality{Kind_GreaterThanOrEqual, selector, value} } -func LessThan(selector selector.Selector, value literal.Literal) InequalityStatement { +func LessThan(selector selector.Selector, value ipld.Node) InequalityStatement { return equality{Kind_LessThan, selector, value} } -func LessThanOrEqual(selector selector.Selector, value literal.Literal) InequalityStatement { +func LessThanOrEqual(selector selector.Selector, value ipld.Node) InequalityStatement { return equality{Kind_LessThanOrEqual, selector, value} }