feat: better selector
This commit is contained in:
@@ -1,124 +1,52 @@
|
|||||||
package literal
|
package literal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"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/node/basicnode"
|
"github.com/ipld/go-ipld-prime/node/basicnode"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrType = fmt.Errorf("literal is not this type")
|
func Node(n ipld.Node) ipld.Node {
|
||||||
|
return n
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type literal struct{}
|
func Link(cid ipld.Link) ipld.Node {
|
||||||
|
|
||||||
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 {
|
|
||||||
nb := basicnode.Prototype.Link.NewBuilder()
|
nb := basicnode.Prototype.Link.NewBuilder()
|
||||||
nb.AssignLink(cid)
|
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 := basicnode.Prototype.Bool.NewBuilder()
|
||||||
nb.AssignBool(val)
|
nb.AssignBool(val)
|
||||||
return node{value: nb.Build()}
|
return nb.Build()
|
||||||
}
|
}
|
||||||
|
|
||||||
type nint struct {
|
func Int(val int64) ipld.Node {
|
||||||
literal
|
nb := basicnode.Prototype.Int.NewBuilder()
|
||||||
value int64
|
nb.AssignInt(val)
|
||||||
|
return nb.Build()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l nint) AsInt() (int64, error) {
|
func Float(val float64) ipld.Node {
|
||||||
return l.value, nil
|
nb := basicnode.Prototype.Float.NewBuilder()
|
||||||
|
nb.AssignFloat(val)
|
||||||
|
return nb.Build()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l nint) Kind() string {
|
func String(val string) ipld.Node {
|
||||||
return Kind_Int
|
nb := basicnode.Prototype.String.NewBuilder()
|
||||||
|
nb.AssignString(val)
|
||||||
|
return nb.Build()
|
||||||
}
|
}
|
||||||
|
|
||||||
func Int(num int64) Literal {
|
func Bytes(val []byte) ipld.Node {
|
||||||
return nint{value: num}
|
nb := basicnode.Prototype.Bytes.NewBuilder()
|
||||||
|
nb.AssignBytes(val)
|
||||||
|
return nb.Build()
|
||||||
}
|
}
|
||||||
|
|
||||||
type nfloat struct {
|
func Null() ipld.Node {
|
||||||
literal
|
nb := basicnode.Prototype.Any.NewBuilder()
|
||||||
value float64
|
nb.AssignNull()
|
||||||
}
|
return nb.Build()
|
||||||
|
|
||||||
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}
|
|
||||||
}
|
}
|
||||||
|
|||||||
170
match.go
170
match.go
@@ -5,8 +5,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/ipld/go-ipld-prime"
|
"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"
|
"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() {
|
switch statement.Kind() {
|
||||||
case Kind_Equal:
|
case Kind_Equal:
|
||||||
if s, ok := statement.(EqualityStatement); ok {
|
if s, ok := statement.(EqualityStatement); ok {
|
||||||
n, err := selectNode(s.Selector(), node)
|
one, _, err := selector.Select(s.Selector(), node)
|
||||||
if err != nil {
|
if err != nil || one == nil {
|
||||||
if _, ok := err.(datamodel.ErrNotExists); ok {
|
return false, nil
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
return false, fmt.Errorf("selecting node: %w", err)
|
|
||||||
}
|
}
|
||||||
return isDeepEqual(s.Value(), n)
|
return isDeepEqual(s.Value(), one)
|
||||||
}
|
}
|
||||||
case Kind_GreaterThan:
|
case Kind_GreaterThan:
|
||||||
if s, ok := statement.(InequalityStatement); ok {
|
if s, ok := statement.(InequalityStatement); ok {
|
||||||
n, err := selectNode(s.Selector(), node)
|
one, _, err := selector.Select(s.Selector(), node)
|
||||||
if err != nil {
|
if err != nil || one == nil {
|
||||||
if _, ok := err.(datamodel.ErrNotExists); ok {
|
return false, nil
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
return false, fmt.Errorf("selecting node: %w", err)
|
|
||||||
}
|
}
|
||||||
return isOrdered(s, n, gt)
|
return isOrdered(s.Value(), one, gt)
|
||||||
}
|
}
|
||||||
case Kind_GreaterThanOrEqual:
|
case Kind_GreaterThanOrEqual:
|
||||||
if s, ok := statement.(InequalityStatement); ok {
|
if s, ok := statement.(InequalityStatement); ok {
|
||||||
n, err := selectNode(s.Selector(), node)
|
one, _, err := selector.Select(s.Selector(), node)
|
||||||
if err != nil {
|
if err != nil || one == nil {
|
||||||
if _, ok := err.(datamodel.ErrNotExists); ok {
|
return false, nil
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
return false, fmt.Errorf("selecting node: %w", err)
|
|
||||||
}
|
}
|
||||||
return isOrdered(s, n, gte)
|
return isOrdered(s.Value(), one, gte)
|
||||||
}
|
}
|
||||||
case Kind_LessThan:
|
case Kind_LessThan:
|
||||||
if s, ok := statement.(InequalityStatement); ok {
|
if s, ok := statement.(InequalityStatement); ok {
|
||||||
n, err := selectNode(s.Selector(), node)
|
one, _, err := selector.Select(s.Selector(), node)
|
||||||
if err != nil {
|
if err != nil || one == nil {
|
||||||
if _, ok := err.(datamodel.ErrNotExists); ok {
|
return false, nil
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
return false, fmt.Errorf("selecting node: %w", err)
|
|
||||||
}
|
}
|
||||||
return isOrdered(s, n, lt)
|
return isOrdered(s.Value(), one, lt)
|
||||||
}
|
}
|
||||||
case Kind_LessThanOrEqual:
|
case Kind_LessThanOrEqual:
|
||||||
if s, ok := statement.(InequalityStatement); ok {
|
if s, ok := statement.(InequalityStatement); ok {
|
||||||
n, err := selectNode(s.Selector(), node)
|
one, _, err := selector.Select(s.Selector(), node)
|
||||||
if err != nil {
|
if err != nil || one == nil {
|
||||||
if _, ok := err.(datamodel.ErrNotExists); ok {
|
return false, nil
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
return false, fmt.Errorf("selecting node: %w", err)
|
|
||||||
}
|
}
|
||||||
return isOrdered(s, n, lte)
|
return isOrdered(s.Value(), one, lte)
|
||||||
}
|
}
|
||||||
case Kind_Negation:
|
case Kind_Negation:
|
||||||
if s, ok := statement.(NegationStatement); ok {
|
if s, ok := statement.(NegationStatement); ok {
|
||||||
@@ -101,6 +84,9 @@ func matchStatement(statement Statement, node ipld.Node) (bool, error) {
|
|||||||
}
|
}
|
||||||
case Kind_Disjunction:
|
case Kind_Disjunction:
|
||||||
if s, ok := statement.(DisjunctionStatement); ok {
|
if s, ok := statement.(DisjunctionStatement); ok {
|
||||||
|
if len(s.Value()) == 0 {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
for _, cs := range s.Value() {
|
for _, cs := range s.Value() {
|
||||||
r, err := matchStatement(cs, node)
|
r, err := matchStatement(cs, node)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -116,124 +102,102 @@ func matchStatement(statement Statement, node ipld.Node) (bool, error) {
|
|||||||
case Kind_Universal:
|
case Kind_Universal:
|
||||||
case Kind_Existential:
|
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) {
|
func isOrdered(expected ipld.Node, actual ipld.Node, satisfies func(order int) bool) (bool, error) {
|
||||||
if sel.Identity() {
|
if expected.Kind() == ipld.Kind_Int && actual.Kind() == ipld.Kind_Int {
|
||||||
child = node
|
a, err := actual.AsInt()
|
||||||
} 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()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("extracting node int: %w", err)
|
return false, fmt.Errorf("extracting node int: %w", err)
|
||||||
}
|
}
|
||||||
b, err := stmt.Value().AsInt()
|
b, err := expected.AsInt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("extracting selector int: %w", err)
|
return false, fmt.Errorf("extracting selector int: %w", err)
|
||||||
}
|
}
|
||||||
return satisfies(cmp.Compare(a, b)), nil
|
return satisfies(cmp.Compare(a, b)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if stmt.Value().Kind() == literal.Kind_Float && node.Kind() == ipld.Kind_Float {
|
if expected.Kind() == ipld.Kind_Float && actual.Kind() == ipld.Kind_Float {
|
||||||
a, err := node.AsFloat()
|
a, err := actual.AsFloat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("extracting node float: %w", err)
|
return false, fmt.Errorf("extracting node float: %w", err)
|
||||||
}
|
}
|
||||||
b, err := stmt.Value().AsFloat()
|
b, err := expected.AsFloat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("extracting selector float: %w", err)
|
return false, fmt.Errorf("extracting selector float: %w", err)
|
||||||
}
|
}
|
||||||
return satisfies(cmp.Compare(a, b)), nil
|
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) {
|
func isDeepEqual(expected ipld.Node, actual ipld.Node) (bool, error) {
|
||||||
switch value.Kind() {
|
if expected.Kind() != actual.Kind() {
|
||||||
case literal.Kind_String:
|
return false, nil
|
||||||
if node.Kind() != ipld.Kind_String {
|
}
|
||||||
return false, nil
|
// TODO: should be easy enough to do the basic types, map, struct and list
|
||||||
}
|
// might be harder.
|
||||||
a, err := node.AsString()
|
switch expected.Kind() {
|
||||||
|
case ipld.Kind_String:
|
||||||
|
a, err := actual.AsString()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("extracting node string: %w", err)
|
return false, fmt.Errorf("extracting node string: %w", err)
|
||||||
}
|
}
|
||||||
b, err := value.AsString()
|
b, err := expected.AsString()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("extracting selector string: %w", err)
|
return false, fmt.Errorf("extracting selector string: %w", err)
|
||||||
}
|
}
|
||||||
return a == b, nil
|
return a == b, nil
|
||||||
case literal.Kind_Int:
|
case ipld.Kind_Int:
|
||||||
if node.Kind() != ipld.Kind_Int {
|
if actual.Kind() != ipld.Kind_Int {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
a, err := node.AsInt()
|
a, err := actual.AsInt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("extracting node int: %w", err)
|
return false, fmt.Errorf("extracting node int: %w", err)
|
||||||
}
|
}
|
||||||
b, err := value.AsInt()
|
b, err := expected.AsInt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("extracting selector int: %w", err)
|
return false, fmt.Errorf("extracting selector int: %w", err)
|
||||||
}
|
}
|
||||||
return a == b, nil
|
return a == b, nil
|
||||||
case literal.Kind_Float:
|
case ipld.Kind_Float:
|
||||||
if node.Kind() != ipld.Kind_Float {
|
if actual.Kind() != ipld.Kind_Float {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
a, err := node.AsFloat()
|
a, err := actual.AsFloat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("extracting node float: %w", err)
|
return false, fmt.Errorf("extracting node float: %w", err)
|
||||||
}
|
}
|
||||||
b, err := value.AsFloat()
|
b, err := expected.AsFloat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("extracting selector float: %w", err)
|
return false, fmt.Errorf("extracting selector float: %w", err)
|
||||||
}
|
}
|
||||||
return a == b, nil
|
return a == b, nil
|
||||||
case literal.Kind_IPLD:
|
case ipld.Kind_Bool:
|
||||||
v, err := value.AsNode()
|
a, err := actual.AsBool()
|
||||||
if err != nil {
|
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() {
|
b, err := expected.AsBool()
|
||||||
return false, nil
|
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
|
return a == b, nil
|
||||||
// might be harder.
|
case ipld.Kind_Link:
|
||||||
switch v.Kind() {
|
a, err := actual.AsLink()
|
||||||
case ipld.Kind_Bool:
|
if err != nil {
|
||||||
a, err := node.AsBool()
|
return false, fmt.Errorf("extracting node link: %w", err)
|
||||||
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 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 }
|
func gt(order int) bool { return order == 1 }
|
||||||
|
|||||||
@@ -252,6 +252,11 @@ func TestMatch(t *testing.T) {
|
|||||||
ok, err = Match(pol, nd)
|
ok, err = Match(pol, nd)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.False(t, ok)
|
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) {
|
t.Run("disjunction", func(t *testing.T) {
|
||||||
@@ -279,5 +284,10 @@ func TestMatch(t *testing.T) {
|
|||||||
ok, err = Match(pol, nd)
|
ok, err = Match(pol, nd)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.False(t, ok)
|
require.False(t, ok)
|
||||||
|
|
||||||
|
pol = Policy{Or()}
|
||||||
|
ok, err = Match(pol, nd)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, ok)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,103 +2,232 @@ package selector
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ipld/go-ipld-prime"
|
||||||
|
"github.com/ipld/go-ipld-prime/datamodel"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Selector describes a UCAN policy selector, as specified here:
|
// Selector describes a UCAN policy selector, as specified here:
|
||||||
// https://github.com/ucan-wg/delegation/blob/4094d5878b58f5d35055a3b93fccda0b8329ebae/README.md#selectors
|
// 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 flags that this selector is the identity selector.
|
||||||
Identity() bool
|
Identity() bool
|
||||||
// Optional flags that this selector is optional.
|
// Optional flags that this selector is optional.
|
||||||
Optional() bool
|
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 is the name of a field in a struct/map.
|
||||||
Field() string
|
Field() string
|
||||||
// Index is an index of a slice.
|
// Index is an index of a slice.
|
||||||
Index() int
|
Index() int
|
||||||
// String returns the selector's string representation.
|
// String returns the segment's string representation.
|
||||||
String() string
|
String() string
|
||||||
}
|
}
|
||||||
|
|
||||||
type selector struct {
|
var Identity = Segment(segment{".", true, false, false, nil, "", 0})
|
||||||
|
|
||||||
|
type segment struct {
|
||||||
str string
|
str string
|
||||||
identity bool
|
identity bool
|
||||||
optional bool
|
optional bool
|
||||||
|
iterator bool
|
||||||
|
slice []int
|
||||||
field string
|
field string
|
||||||
index int
|
index int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s selector) Field() string {
|
func (s segment) String() 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 {
|
|
||||||
return s.str
|
return s.str
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: probably regex or better parser
|
func (s segment) Identity() bool {
|
||||||
func Parse(sel string) (Selector, error) {
|
return s.identity
|
||||||
s := sel
|
}
|
||||||
if s == "." {
|
|
||||||
return selector{sel, true, false, "", 0}, nil
|
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, "?")
|
col := 0
|
||||||
if optional {
|
var sel Selector
|
||||||
s = s[0 : len(s)-1]
|
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, ".")
|
func tokenize(str string) []string {
|
||||||
if dotted {
|
var toks []string
|
||||||
s = s[1:]
|
col := 0
|
||||||
}
|
ofs := 0
|
||||||
|
ctx := ""
|
||||||
|
|
||||||
// collection values
|
for col < len(str) {
|
||||||
if s == "[]" {
|
char := string(str[col])
|
||||||
return nil, fmt.Errorf("unsupported selector: %s", sel)
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasPrefix(s, "[") && strings.HasSuffix(s, "]") {
|
if char == "\"" && string(str[col-1]) != "\\" {
|
||||||
s = s[1 : len(s)-1]
|
col++
|
||||||
|
if ctx == "\"" {
|
||||||
// explicit field selector
|
ctx = ""
|
||||||
if strings.HasPrefix(s, "\"") && strings.HasSuffix(s, "\"") {
|
} else {
|
||||||
return selector{sel, false, optional, s[1 : len(s)-1], 0}, nil
|
ctx = "\""
|
||||||
|
}
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// collection range
|
if ctx == "\"" {
|
||||||
if strings.Contains(s, ":") {
|
col++
|
||||||
return nil, fmt.Errorf("unsupported selector: %s", sel)
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// index selector
|
if char == "." || char == "[" {
|
||||||
idx, err := strconv.Atoi(s)
|
if ofs < col {
|
||||||
if err != nil {
|
toks = append(toks, str[ofs:col])
|
||||||
return nil, fmt.Errorf("parsing index selector value: %s", err)
|
}
|
||||||
|
ofs = col
|
||||||
}
|
}
|
||||||
|
col++
|
||||||
return selector{sel, false, optional, "", idx}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !dotted {
|
if ofs < col && ctx != "\"" {
|
||||||
return nil, fmt.Errorf("invalid selector: %s", sel)
|
toks = append(toks, str[ofs:col])
|
||||||
}
|
}
|
||||||
|
|
||||||
// dotted field selector
|
return toks
|
||||||
return selector{sel, false, optional, s, 0}, nil
|
}
|
||||||
|
|
||||||
|
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 {
|
func MustParse(sel string) Selector {
|
||||||
@@ -108,3 +237,168 @@ func MustParse(sel string) Selector {
|
|||||||
}
|
}
|
||||||
return s
|
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}
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,124 +11,229 @@ func TestParse(t *testing.T) {
|
|||||||
t.Run("identity", func(t *testing.T) {
|
t.Run("identity", func(t *testing.T) {
|
||||||
sel, err := Parse(".")
|
sel, err := Parse(".")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.True(t, sel.Identity())
|
require.Equal(t, 1, len(sel))
|
||||||
require.False(t, sel.Optional())
|
require.True(t, sel[0].Identity())
|
||||||
require.Empty(t, sel.Field())
|
require.False(t, sel[0].Optional())
|
||||||
require.Empty(t, sel.Index())
|
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")
|
sel, err := Parse(".foo")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.False(t, sel.Identity())
|
require.Equal(t, 1, len(sel))
|
||||||
require.False(t, sel.Optional())
|
require.False(t, sel[0].Identity())
|
||||||
require.Equal(t, sel.Field(), "foo")
|
require.False(t, sel[0].Optional())
|
||||||
require.Empty(t, sel.Index())
|
require.False(t, sel[0].Iterator())
|
||||||
})
|
require.Empty(t, sel[0].Slice())
|
||||||
|
require.Equal(t, sel[0].Field(), "foo")
|
||||||
t.Run("dotted explicit field", func(t *testing.T) {
|
require.Empty(t, sel[0].Index())
|
||||||
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)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("explicit field", func(t *testing.T) {
|
t.Run("explicit field", func(t *testing.T) {
|
||||||
sel, err := Parse("[\"foo\"]")
|
sel, err := Parse(`.["foo"]`)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.False(t, sel.Identity())
|
require.Equal(t, 2, len(sel))
|
||||||
require.False(t, sel.Optional())
|
require.True(t, sel[0].Identity())
|
||||||
require.Equal(t, sel.Field(), "foo")
|
require.False(t, sel[0].Optional())
|
||||||
require.Empty(t, sel.Index())
|
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) {
|
t.Run("index", func(t *testing.T) {
|
||||||
sel, err := Parse("[138]")
|
sel, err := Parse(".[138]")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.False(t, sel.Identity())
|
require.Equal(t, 2, len(sel))
|
||||||
require.False(t, sel.Optional())
|
require.True(t, sel[0].Identity())
|
||||||
require.Empty(t, sel.Field())
|
require.False(t, sel[0].Optional())
|
||||||
require.Equal(t, sel.Index(), 138)
|
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) {
|
t.Run("negative index", func(t *testing.T) {
|
||||||
sel, err := Parse("[-138]")
|
sel, err := Parse(".[-138]")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.False(t, sel.Identity())
|
require.Equal(t, 2, len(sel))
|
||||||
require.False(t, sel.Optional())
|
require.True(t, sel[0].Identity())
|
||||||
require.Empty(t, sel.Field())
|
require.False(t, sel[0].Optional())
|
||||||
require.Equal(t, sel.Index(), -138)
|
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?")
|
sel, err := Parse(".foo?")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.False(t, sel.Identity())
|
require.Equal(t, 1, len(sel))
|
||||||
require.True(t, sel.Optional())
|
require.False(t, sel[0].Identity())
|
||||||
require.Equal(t, sel.Field(), "foo")
|
require.True(t, sel[0].Optional())
|
||||||
require.Empty(t, sel.Index())
|
require.False(t, sel[0].Iterator())
|
||||||
})
|
require.Empty(t, sel[0].Slice())
|
||||||
|
require.Equal(t, sel[0].Field(), "foo")
|
||||||
t.Run("optional dotted explicit field", func(t *testing.T) {
|
require.Empty(t, sel[0].Index())
|
||||||
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)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("optional explicit field", func(t *testing.T) {
|
t.Run("optional explicit field", func(t *testing.T) {
|
||||||
sel, err := Parse("[\"foo\"]?")
|
sel, err := Parse(`.["foo"]?`)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.False(t, sel.Identity())
|
require.Equal(t, 2, len(sel))
|
||||||
require.True(t, sel.Optional())
|
require.True(t, sel[0].Identity())
|
||||||
require.Equal(t, sel.Field(), "foo")
|
require.False(t, sel[0].Optional())
|
||||||
require.Empty(t, sel.Index())
|
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) {
|
t.Run("optional index", func(t *testing.T) {
|
||||||
sel, err := Parse("[138]?")
|
sel, err := Parse(".[138]?")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.False(t, sel.Identity())
|
require.Equal(t, 2, len(sel))
|
||||||
require.True(t, sel.Optional())
|
require.True(t, sel[0].Identity())
|
||||||
require.Empty(t, sel.Field())
|
require.False(t, sel[0].Optional())
|
||||||
require.Equal(t, sel.Index(), 138)
|
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) {
|
t.Run("non dotted", func(t *testing.T) {
|
||||||
_, err := Parse("foo")
|
_, err := Parse("foo")
|
||||||
if err == nil {
|
require.NotNil(t, err)
|
||||||
t.Fatalf("expected error parsing selector")
|
|
||||||
}
|
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("non quoted", func(t *testing.T) {
|
t.Run("non quoted", func(t *testing.T) {
|
||||||
_, err := Parse(".[foo]")
|
_, err := Parse(".[foo]")
|
||||||
if err == nil {
|
require.NotNil(t, err)
|
||||||
t.Fatalf("expected error parsing selector")
|
|
||||||
}
|
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func printSegments(s Selector) {
|
||||||
|
for i, seg := range s {
|
||||||
|
fmt.Printf("%d: %s\n", i, seg.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
20
statement.go
20
statement.go
@@ -3,7 +3,7 @@ package policy
|
|||||||
// https://github.com/ucan-wg/delegation/blob/4094d5878b58f5d35055a3b93fccda0b8329ebae/README.md#policy
|
// https://github.com/ucan-wg/delegation/blob/4094d5878b58f5d35055a3b93fccda0b8329ebae/README.md#policy
|
||||||
|
|
||||||
import (
|
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"
|
"github.com/storacha-network/go-ucanto/core/policy/selector"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,13 +30,13 @@ type Statement interface {
|
|||||||
type EqualityStatement interface {
|
type EqualityStatement interface {
|
||||||
Statement
|
Statement
|
||||||
Selector() selector.Selector
|
Selector() selector.Selector
|
||||||
Value() literal.Literal
|
Value() ipld.Node
|
||||||
}
|
}
|
||||||
|
|
||||||
type InequalityStatement interface {
|
type InequalityStatement interface {
|
||||||
Statement
|
Statement
|
||||||
Selector() selector.Selector
|
Selector() selector.Selector
|
||||||
Value() literal.Literal
|
Value() ipld.Node
|
||||||
}
|
}
|
||||||
|
|
||||||
type WildcardStatement interface {
|
type WildcardStatement interface {
|
||||||
@@ -73,14 +73,14 @@ type QuantifierStatement interface {
|
|||||||
type equality struct {
|
type equality struct {
|
||||||
kind string
|
kind string
|
||||||
selector selector.Selector
|
selector selector.Selector
|
||||||
value literal.Literal
|
value ipld.Node
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e equality) Kind() string {
|
func (e equality) Kind() string {
|
||||||
return e.kind
|
return e.kind
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e equality) Value() literal.Literal {
|
func (e equality) Value() ipld.Node {
|
||||||
return e.value
|
return e.value
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,23 +88,23 @@ func (e equality) Selector() selector.Selector {
|
|||||||
return e.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}
|
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}
|
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}
|
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}
|
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}
|
return equality{Kind_LessThanOrEqual, selector, value}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user