feat: simplify
This commit is contained in:
143
match.go
143
match.go
@@ -5,35 +5,37 @@ import (
|
|||||||
"fmt"
|
"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/must"
|
||||||
"github.com/storacha-network/go-ucanto/core/policy/selector"
|
"github.com/storacha-network/go-ucanto/core/policy/selector"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Match determines if the IPLD node matches the policy document.
|
// Match determines if the IPLD node matches the policy document.
|
||||||
func Match(policy Policy, node ipld.Node) (bool, error) {
|
func Match(policy Policy, node ipld.Node) bool {
|
||||||
for _, stmt := range policy {
|
for _, stmt := range policy {
|
||||||
ok, err := matchStatement(stmt, node)
|
ok := matchStatement(stmt, node)
|
||||||
if err != nil || !ok {
|
if !ok {
|
||||||
return ok, err
|
return ok
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true, nil
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func matchStatement(statement Statement, node ipld.Node) (bool, error) {
|
func matchStatement(statement Statement, node ipld.Node) bool {
|
||||||
switch statement.Kind() {
|
switch statement.Kind() {
|
||||||
case Kind_Equal:
|
case Kind_Equal:
|
||||||
if s, ok := statement.(EqualityStatement); ok {
|
if s, ok := statement.(EqualityStatement); ok {
|
||||||
one, _, err := selector.Select(s.Selector(), node)
|
one, _, err := selector.Select(s.Selector(), node)
|
||||||
if err != nil || one == nil {
|
if err != nil || one == nil {
|
||||||
return false, nil
|
return false
|
||||||
}
|
}
|
||||||
return isDeepEqual(s.Value(), one)
|
return datamodel.DeepEqual(s.Value(), one)
|
||||||
}
|
}
|
||||||
case Kind_GreaterThan:
|
case Kind_GreaterThan:
|
||||||
if s, ok := statement.(InequalityStatement); ok {
|
if s, ok := statement.(InequalityStatement); ok {
|
||||||
one, _, err := selector.Select(s.Selector(), node)
|
one, _, err := selector.Select(s.Selector(), node)
|
||||||
if err != nil || one == nil {
|
if err != nil || one == nil {
|
||||||
return false, nil
|
return false
|
||||||
}
|
}
|
||||||
return isOrdered(s.Value(), one, gt)
|
return isOrdered(s.Value(), one, gt)
|
||||||
}
|
}
|
||||||
@@ -41,7 +43,7 @@ func matchStatement(statement Statement, node ipld.Node) (bool, error) {
|
|||||||
if s, ok := statement.(InequalityStatement); ok {
|
if s, ok := statement.(InequalityStatement); ok {
|
||||||
one, _, err := selector.Select(s.Selector(), node)
|
one, _, err := selector.Select(s.Selector(), node)
|
||||||
if err != nil || one == nil {
|
if err != nil || one == nil {
|
||||||
return false, nil
|
return false
|
||||||
}
|
}
|
||||||
return isOrdered(s.Value(), one, gte)
|
return isOrdered(s.Value(), one, gte)
|
||||||
}
|
}
|
||||||
@@ -49,7 +51,7 @@ func matchStatement(statement Statement, node ipld.Node) (bool, error) {
|
|||||||
if s, ok := statement.(InequalityStatement); ok {
|
if s, ok := statement.(InequalityStatement); ok {
|
||||||
one, _, err := selector.Select(s.Selector(), node)
|
one, _, err := selector.Select(s.Selector(), node)
|
||||||
if err != nil || one == nil {
|
if err != nil || one == nil {
|
||||||
return false, nil
|
return false
|
||||||
}
|
}
|
||||||
return isOrdered(s.Value(), one, lt)
|
return isOrdered(s.Value(), one, lt)
|
||||||
}
|
}
|
||||||
@@ -57,147 +59,64 @@ func matchStatement(statement Statement, node ipld.Node) (bool, error) {
|
|||||||
if s, ok := statement.(InequalityStatement); ok {
|
if s, ok := statement.(InequalityStatement); ok {
|
||||||
one, _, err := selector.Select(s.Selector(), node)
|
one, _, err := selector.Select(s.Selector(), node)
|
||||||
if err != nil || one == nil {
|
if err != nil || one == nil {
|
||||||
return false, nil
|
return false
|
||||||
}
|
}
|
||||||
return isOrdered(s.Value(), one, 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 {
|
||||||
r, err := matchStatement(s.Value(), node)
|
return !matchStatement(s.Value(), node)
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
return !r, err
|
|
||||||
}
|
}
|
||||||
case Kind_Conjunction:
|
case Kind_Conjunction:
|
||||||
if s, ok := statement.(ConjunctionStatement); ok {
|
if s, ok := statement.(ConjunctionStatement); ok {
|
||||||
for _, cs := range s.Value() {
|
for _, cs := range s.Value() {
|
||||||
r, err := matchStatement(cs, node)
|
r := matchStatement(cs, node)
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
if !r {
|
if !r {
|
||||||
return false, nil
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true, nil
|
return true
|
||||||
}
|
}
|
||||||
case Kind_Disjunction:
|
case Kind_Disjunction:
|
||||||
if s, ok := statement.(DisjunctionStatement); ok {
|
if s, ok := statement.(DisjunctionStatement); ok {
|
||||||
if len(s.Value()) == 0 {
|
if len(s.Value()) == 0 {
|
||||||
return true, nil
|
return true
|
||||||
}
|
}
|
||||||
for _, cs := range s.Value() {
|
for _, cs := range s.Value() {
|
||||||
r, err := matchStatement(cs, node)
|
r := matchStatement(cs, node)
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
if r {
|
if r {
|
||||||
return true, nil
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false, nil
|
return false
|
||||||
}
|
}
|
||||||
case Kind_Wildcard:
|
case Kind_Wildcard:
|
||||||
case Kind_Universal:
|
case Kind_Universal:
|
||||||
case Kind_Existential:
|
case Kind_Existential:
|
||||||
}
|
}
|
||||||
return false, fmt.Errorf("unimplemented statement kind: %s", statement.Kind())
|
panic(fmt.Errorf("unimplemented statement kind: %s", statement.Kind()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func isOrdered(expected ipld.Node, actual ipld.Node, satisfies func(order int) bool) (bool, error) {
|
func isOrdered(expected ipld.Node, actual ipld.Node, satisfies func(order int) bool) bool {
|
||||||
if expected.Kind() == ipld.Kind_Int && actual.Kind() == ipld.Kind_Int {
|
if expected.Kind() == ipld.Kind_Int && actual.Kind() == ipld.Kind_Int {
|
||||||
a, err := actual.AsInt()
|
a := must.Int(actual)
|
||||||
if err != nil {
|
b := must.Int(expected)
|
||||||
return false, fmt.Errorf("extracting node int: %w", err)
|
return satisfies(cmp.Compare(a, b))
|
||||||
}
|
|
||||||
b, err := expected.AsInt()
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("extracting selector int: %w", err)
|
|
||||||
}
|
|
||||||
return satisfies(cmp.Compare(a, b)), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if expected.Kind() == ipld.Kind_Float && actual.Kind() == ipld.Kind_Float {
|
if expected.Kind() == ipld.Kind_Float && actual.Kind() == ipld.Kind_Float {
|
||||||
a, err := actual.AsFloat()
|
a, err := actual.AsFloat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("extracting node float: %w", err)
|
panic(fmt.Errorf("extracting node float: %w", err))
|
||||||
}
|
}
|
||||||
b, err := expected.AsFloat()
|
b, err := expected.AsFloat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("extracting selector float: %w", err)
|
panic(fmt.Errorf("extracting selector float: %w", err))
|
||||||
}
|
}
|
||||||
return satisfies(cmp.Compare(a, b)), nil
|
return satisfies(cmp.Compare(a, b))
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, fmt.Errorf("unsupported IPLD kinds in ordered comparison: %s %s", expected.Kind(), actual.Kind())
|
return false
|
||||||
}
|
|
||||||
|
|
||||||
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 := expected.AsString()
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("extracting selector string: %w", err)
|
|
||||||
}
|
|
||||||
return a == b, nil
|
|
||||||
case ipld.Kind_Int:
|
|
||||||
if actual.Kind() != ipld.Kind_Int {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
a, err := actual.AsInt()
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("extracting node int: %w", err)
|
|
||||||
}
|
|
||||||
b, err := expected.AsInt()
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("extracting selector int: %w", err)
|
|
||||||
}
|
|
||||||
return a == b, nil
|
|
||||||
case ipld.Kind_Float:
|
|
||||||
if actual.Kind() != ipld.Kind_Float {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
a, err := actual.AsFloat()
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("extracting node float: %w", err)
|
|
||||||
}
|
|
||||||
b, err := expected.AsFloat()
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("extracting selector float: %w", err)
|
|
||||||
}
|
|
||||||
return a == b, nil
|
|
||||||
case ipld.Kind_Bool:
|
|
||||||
a, err := actual.AsBool()
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("extracting node boolean: %w", err)
|
|
||||||
}
|
|
||||||
b, err := expected.AsBool()
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("extracting selector node boolean: %w", err)
|
|
||||||
}
|
|
||||||
return a == b, nil
|
|
||||||
case ipld.Kind_Link:
|
|
||||||
a, err := actual.AsLink()
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("extracting node link: %w", err)
|
|
||||||
}
|
|
||||||
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("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 }
|
||||||
|
|||||||
122
match_test.go
122
match_test.go
@@ -19,18 +19,15 @@ func TestMatch(t *testing.T) {
|
|||||||
nd := nb.Build()
|
nd := nb.Build()
|
||||||
|
|
||||||
pol := Policy{Equal(selector.MustParse("."), literal.String("test"))}
|
pol := Policy{Equal(selector.MustParse("."), literal.String("test"))}
|
||||||
ok, err := Match(pol, nd)
|
ok := Match(pol, nd)
|
||||||
require.NoError(t, err)
|
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
||||||
pol = Policy{Equal(selector.MustParse("."), literal.String("test2"))}
|
pol = Policy{Equal(selector.MustParse("."), literal.String("test2"))}
|
||||||
ok, err = Match(pol, nd)
|
ok = Match(pol, nd)
|
||||||
require.NoError(t, err)
|
|
||||||
require.False(t, ok)
|
require.False(t, ok)
|
||||||
|
|
||||||
pol = Policy{Equal(selector.MustParse("."), literal.Int(138))}
|
pol = Policy{Equal(selector.MustParse("."), literal.Int(138))}
|
||||||
ok, err = Match(pol, nd)
|
ok = Match(pol, nd)
|
||||||
require.NoError(t, err)
|
|
||||||
require.False(t, ok)
|
require.False(t, ok)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -41,18 +38,15 @@ func TestMatch(t *testing.T) {
|
|||||||
nd := nb.Build()
|
nd := nb.Build()
|
||||||
|
|
||||||
pol := Policy{Equal(selector.MustParse("."), literal.Int(138))}
|
pol := Policy{Equal(selector.MustParse("."), literal.Int(138))}
|
||||||
ok, err := Match(pol, nd)
|
ok := Match(pol, nd)
|
||||||
require.NoError(t, err)
|
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
||||||
pol = Policy{Equal(selector.MustParse("."), literal.Int(1138))}
|
pol = Policy{Equal(selector.MustParse("."), literal.Int(1138))}
|
||||||
ok, err = Match(pol, nd)
|
ok = Match(pol, nd)
|
||||||
require.NoError(t, err)
|
|
||||||
require.False(t, ok)
|
require.False(t, ok)
|
||||||
|
|
||||||
pol = Policy{Equal(selector.MustParse("."), literal.String("138"))}
|
pol = Policy{Equal(selector.MustParse("."), literal.String("138"))}
|
||||||
ok, err = Match(pol, nd)
|
ok = Match(pol, nd)
|
||||||
require.NoError(t, err)
|
|
||||||
require.False(t, ok)
|
require.False(t, ok)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -63,18 +57,15 @@ func TestMatch(t *testing.T) {
|
|||||||
nd := nb.Build()
|
nd := nb.Build()
|
||||||
|
|
||||||
pol := Policy{Equal(selector.MustParse("."), literal.Float(1.138))}
|
pol := Policy{Equal(selector.MustParse("."), literal.Float(1.138))}
|
||||||
ok, err := Match(pol, nd)
|
ok := Match(pol, nd)
|
||||||
require.NoError(t, err)
|
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
||||||
pol = Policy{Equal(selector.MustParse("."), literal.Float(11.38))}
|
pol = Policy{Equal(selector.MustParse("."), literal.Float(11.38))}
|
||||||
ok, err = Match(pol, nd)
|
ok = Match(pol, nd)
|
||||||
require.NoError(t, err)
|
|
||||||
require.False(t, ok)
|
require.False(t, ok)
|
||||||
|
|
||||||
pol = Policy{Equal(selector.MustParse("."), literal.String("138"))}
|
pol = Policy{Equal(selector.MustParse("."), literal.String("138"))}
|
||||||
ok, err = Match(pol, nd)
|
ok = Match(pol, nd)
|
||||||
require.NoError(t, err)
|
|
||||||
require.False(t, ok)
|
require.False(t, ok)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -88,18 +79,15 @@ func TestMatch(t *testing.T) {
|
|||||||
nd := nb.Build()
|
nd := nb.Build()
|
||||||
|
|
||||||
pol := Policy{Equal(selector.MustParse("."), literal.Link(l0))}
|
pol := Policy{Equal(selector.MustParse("."), literal.Link(l0))}
|
||||||
ok, err := Match(pol, nd)
|
ok := Match(pol, nd)
|
||||||
require.NoError(t, err)
|
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
||||||
pol = Policy{Equal(selector.MustParse("."), literal.Link(l1))}
|
pol = Policy{Equal(selector.MustParse("."), literal.Link(l1))}
|
||||||
ok, err = Match(pol, nd)
|
ok = Match(pol, nd)
|
||||||
require.NoError(t, err)
|
|
||||||
require.False(t, ok)
|
require.False(t, ok)
|
||||||
|
|
||||||
pol = Policy{Equal(selector.MustParse("."), literal.String("bafybeif4owy5gno5lwnixqm52rwqfodklf76hsetxdhffuxnplvijskzqq"))}
|
pol = Policy{Equal(selector.MustParse("."), literal.String("bafybeif4owy5gno5lwnixqm52rwqfodklf76hsetxdhffuxnplvijskzqq"))}
|
||||||
ok, err = Match(pol, nd)
|
ok = Match(pol, nd)
|
||||||
require.NoError(t, err)
|
|
||||||
require.False(t, ok)
|
require.False(t, ok)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -113,23 +101,19 @@ func TestMatch(t *testing.T) {
|
|||||||
nd := nb.Build()
|
nd := nb.Build()
|
||||||
|
|
||||||
pol := Policy{Equal(selector.MustParse(".foo"), literal.String("bar"))}
|
pol := Policy{Equal(selector.MustParse(".foo"), literal.String("bar"))}
|
||||||
ok, err := Match(pol, nd)
|
ok := Match(pol, nd)
|
||||||
require.NoError(t, err)
|
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
||||||
pol = Policy{Equal(selector.MustParse(".[\"foo\"]"), literal.String("bar"))}
|
pol = Policy{Equal(selector.MustParse(".[\"foo\"]"), literal.String("bar"))}
|
||||||
ok, err = Match(pol, nd)
|
ok = Match(pol, nd)
|
||||||
require.NoError(t, err)
|
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
||||||
pol = Policy{Equal(selector.MustParse(".foo"), literal.String("baz"))}
|
pol = Policy{Equal(selector.MustParse(".foo"), literal.String("baz"))}
|
||||||
ok, err = Match(pol, nd)
|
ok = Match(pol, nd)
|
||||||
require.NoError(t, err)
|
|
||||||
require.False(t, ok)
|
require.False(t, ok)
|
||||||
|
|
||||||
pol = Policy{Equal(selector.MustParse(".foobar"), literal.String("bar"))}
|
pol = Policy{Equal(selector.MustParse(".foobar"), literal.String("bar"))}
|
||||||
ok, err = Match(pol, nd)
|
ok = Match(pol, nd)
|
||||||
require.NoError(t, err)
|
|
||||||
require.False(t, ok)
|
require.False(t, ok)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -142,13 +126,11 @@ func TestMatch(t *testing.T) {
|
|||||||
nd := nb.Build()
|
nd := nb.Build()
|
||||||
|
|
||||||
pol := Policy{Equal(selector.MustParse(".[0]"), literal.String("foo"))}
|
pol := Policy{Equal(selector.MustParse(".[0]"), literal.String("foo"))}
|
||||||
ok, err := Match(pol, nd)
|
ok := Match(pol, nd)
|
||||||
require.NoError(t, err)
|
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
||||||
pol = Policy{Equal(selector.MustParse(".[1]"), literal.String("foo"))}
|
pol = Policy{Equal(selector.MustParse(".[1]"), literal.String("foo"))}
|
||||||
ok, err = Match(pol, nd)
|
ok = Match(pol, nd)
|
||||||
require.NoError(t, err)
|
|
||||||
require.False(t, ok)
|
require.False(t, ok)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -159,8 +141,7 @@ func TestMatch(t *testing.T) {
|
|||||||
nd := nb.Build()
|
nd := nb.Build()
|
||||||
|
|
||||||
pol := Policy{GreaterThan(selector.MustParse("."), literal.Int(1))}
|
pol := Policy{GreaterThan(selector.MustParse("."), literal.Int(1))}
|
||||||
ok, err := Match(pol, nd)
|
ok := Match(pol, nd)
|
||||||
require.NoError(t, err)
|
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -171,13 +152,11 @@ func TestMatch(t *testing.T) {
|
|||||||
nd := nb.Build()
|
nd := nb.Build()
|
||||||
|
|
||||||
pol := Policy{GreaterThanOrEqual(selector.MustParse("."), literal.Int(1))}
|
pol := Policy{GreaterThanOrEqual(selector.MustParse("."), literal.Int(1))}
|
||||||
ok, err := Match(pol, nd)
|
ok := Match(pol, nd)
|
||||||
require.NoError(t, err)
|
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
||||||
pol = Policy{GreaterThanOrEqual(selector.MustParse("."), literal.Int(138))}
|
pol = Policy{GreaterThanOrEqual(selector.MustParse("."), literal.Int(138))}
|
||||||
ok, err = Match(pol, nd)
|
ok = Match(pol, nd)
|
||||||
require.NoError(t, err)
|
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -188,8 +167,7 @@ func TestMatch(t *testing.T) {
|
|||||||
nd := nb.Build()
|
nd := nb.Build()
|
||||||
|
|
||||||
pol := Policy{GreaterThan(selector.MustParse("."), literal.Float(1))}
|
pol := Policy{GreaterThan(selector.MustParse("."), literal.Float(1))}
|
||||||
ok, err := Match(pol, nd)
|
ok := Match(pol, nd)
|
||||||
require.NoError(t, err)
|
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -200,13 +178,37 @@ func TestMatch(t *testing.T) {
|
|||||||
nd := nb.Build()
|
nd := nb.Build()
|
||||||
|
|
||||||
pol := Policy{GreaterThanOrEqual(selector.MustParse("."), literal.Float(1))}
|
pol := Policy{GreaterThanOrEqual(selector.MustParse("."), literal.Float(1))}
|
||||||
ok, err := Match(pol, nd)
|
ok := Match(pol, nd)
|
||||||
require.NoError(t, err)
|
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
||||||
pol = Policy{GreaterThanOrEqual(selector.MustParse("."), literal.Float(1.38))}
|
pol = Policy{GreaterThanOrEqual(selector.MustParse("."), literal.Float(1.38))}
|
||||||
ok, err = Match(pol, nd)
|
ok = Match(pol, nd)
|
||||||
require.NoError(t, err)
|
require.True(t, ok)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("inequality lt int", func(t *testing.T) {
|
||||||
|
np := basicnode.Prototype.Int
|
||||||
|
nb := np.NewBuilder()
|
||||||
|
nb.AssignInt(138)
|
||||||
|
nd := nb.Build()
|
||||||
|
|
||||||
|
pol := Policy{LessThan(selector.MustParse("."), literal.Int(1138))}
|
||||||
|
ok := Match(pol, nd)
|
||||||
|
require.True(t, ok)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("inequality lte int", func(t *testing.T) {
|
||||||
|
np := basicnode.Prototype.Int
|
||||||
|
nb := np.NewBuilder()
|
||||||
|
nb.AssignInt(138)
|
||||||
|
nd := nb.Build()
|
||||||
|
|
||||||
|
pol := Policy{LessThanOrEqual(selector.MustParse("."), literal.Int(1138))}
|
||||||
|
ok := Match(pol, nd)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
pol = Policy{LessThanOrEqual(selector.MustParse("."), literal.Int(138))}
|
||||||
|
ok = Match(pol, nd)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -217,13 +219,11 @@ func TestMatch(t *testing.T) {
|
|||||||
nd := nb.Build()
|
nd := nb.Build()
|
||||||
|
|
||||||
pol := Policy{Not(Equal(selector.MustParse("."), literal.Bool(true)))}
|
pol := Policy{Not(Equal(selector.MustParse("."), literal.Bool(true)))}
|
||||||
ok, err := Match(pol, nd)
|
ok := Match(pol, nd)
|
||||||
require.NoError(t, err)
|
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
||||||
pol = Policy{Not(Equal(selector.MustParse("."), literal.Bool(false)))}
|
pol = Policy{Not(Equal(selector.MustParse("."), literal.Bool(false)))}
|
||||||
ok, err = Match(pol, nd)
|
ok = Match(pol, nd)
|
||||||
require.NoError(t, err)
|
|
||||||
require.False(t, ok)
|
require.False(t, ok)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -239,8 +239,7 @@ func TestMatch(t *testing.T) {
|
|||||||
LessThan(selector.MustParse("."), literal.Int(1138)),
|
LessThan(selector.MustParse("."), literal.Int(1138)),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
ok, err := Match(pol, nd)
|
ok := Match(pol, nd)
|
||||||
require.NoError(t, err)
|
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
||||||
pol = Policy{
|
pol = Policy{
|
||||||
@@ -249,13 +248,11 @@ func TestMatch(t *testing.T) {
|
|||||||
Equal(selector.MustParse("."), literal.Int(1138)),
|
Equal(selector.MustParse("."), literal.Int(1138)),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
ok, err = Match(pol, nd)
|
ok = Match(pol, nd)
|
||||||
require.NoError(t, err)
|
|
||||||
require.False(t, ok)
|
require.False(t, ok)
|
||||||
|
|
||||||
pol = Policy{And()}
|
pol = Policy{And()}
|
||||||
ok, err = Match(pol, nd)
|
ok = Match(pol, nd)
|
||||||
require.NoError(t, err)
|
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -271,8 +268,7 @@ func TestMatch(t *testing.T) {
|
|||||||
LessThan(selector.MustParse("."), literal.Int(1138)),
|
LessThan(selector.MustParse("."), literal.Int(1138)),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
ok, err := Match(pol, nd)
|
ok := Match(pol, nd)
|
||||||
require.NoError(t, err)
|
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
||||||
pol = Policy{
|
pol = Policy{
|
||||||
@@ -281,13 +277,11 @@ func TestMatch(t *testing.T) {
|
|||||||
Equal(selector.MustParse("."), literal.Int(1138)),
|
Equal(selector.MustParse("."), literal.Int(1138)),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
ok, err = Match(pol, nd)
|
ok = Match(pol, nd)
|
||||||
require.NoError(t, err)
|
|
||||||
require.False(t, ok)
|
require.False(t, ok)
|
||||||
|
|
||||||
pol = Policy{Or()}
|
pol = Policy{Or()}
|
||||||
ok, err = Match(pol, nd)
|
ok = Match(pol, nd)
|
||||||
require.NoError(t, err)
|
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/ipld/go-ipld-prime"
|
"github.com/ipld/go-ipld-prime"
|
||||||
"github.com/ipld/go-ipld-prime/datamodel"
|
"github.com/ipld/go-ipld-prime/datamodel"
|
||||||
|
"github.com/ipld/go-ipld-prime/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Selector describes a UCAN policy selector, as specified here:
|
// Selector describes a UCAN policy selector, as specified here:
|
||||||
@@ -238,6 +239,8 @@ func MustParse(sel string) Selector {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Select uses a selector to extract an IPLD node or set of nodes from the
|
||||||
|
// passed subject node.
|
||||||
func Select(sel Selector, subject ipld.Node) (ipld.Node, []ipld.Node, error) {
|
func Select(sel Selector, subject ipld.Node) (ipld.Node, []ipld.Node, error) {
|
||||||
return resolve(sel, subject, nil)
|
return resolve(sel, subject, nil)
|
||||||
}
|
}
|
||||||
@@ -256,12 +259,12 @@ func resolve(sel Selector, subject ipld.Node, at []string) (ipld.Node, []ipld.No
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
i, v, err := it.Next()
|
k, v, err := it.Next()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
key := fmt.Sprintf("%d", i)
|
key := fmt.Sprintf("%d", k)
|
||||||
o, m, err := resolve(sel[i+1:], v, append(at[:], key))
|
o, m, err := resolve(sel[i+1:], v, append(at[:], key))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
@@ -311,7 +314,7 @@ func resolve(sel Selector, subject ipld.Node, at []string) (ipld.Node, []ipld.No
|
|||||||
if cur != nil && cur.Kind() == datamodel.Kind_Map {
|
if cur != nil && cur.Kind() == datamodel.Kind_Map {
|
||||||
n, err := cur.LookupByString(seg.Field())
|
n, err := cur.LookupByString(seg.Field())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if _, ok := err.(datamodel.ErrNotExists); ok {
|
if isMissing(err) {
|
||||||
if seg.Optional() {
|
if seg.Optional() {
|
||||||
cur = nil
|
cur = nil
|
||||||
} else {
|
} else {
|
||||||
@@ -342,7 +345,7 @@ func resolve(sel Selector, subject ipld.Node, at []string) (ipld.Node, []ipld.No
|
|||||||
if cur != nil && cur.Kind() == datamodel.Kind_List {
|
if cur != nil && cur.Kind() == datamodel.Kind_List {
|
||||||
n, err := cur.LookupByIndex(int64(seg.Index()))
|
n, err := cur.LookupByIndex(int64(seg.Index()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if _, ok := err.(datamodel.ErrNotExists); ok {
|
if isMissing(err) {
|
||||||
if seg.Optional() {
|
if seg.Optional() {
|
||||||
cur = nil
|
cur = nil
|
||||||
} else {
|
} else {
|
||||||
@@ -371,6 +374,19 @@ func kindString(n datamodel.Node) string {
|
|||||||
return n.Kind().String()
|
return n.Kind().String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isMissing(err error) bool {
|
||||||
|
if _, ok := err.(datamodel.ErrNotExists); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := err.(schema.ErrNoSuchField); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := err.(schema.ErrInvalidKey); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
type ResolutionError interface {
|
type ResolutionError interface {
|
||||||
error
|
error
|
||||||
Name() string
|
Name() string
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ipld/go-ipld-prime"
|
||||||
|
"github.com/ipld/go-ipld-prime/must"
|
||||||
|
"github.com/ipld/go-ipld-prime/node/bindnode"
|
||||||
|
"github.com/ipld/go-ipld-prime/printer"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -171,9 +175,11 @@ func TestParse(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("nesting", func(t *testing.T) {
|
t.Run("nesting", func(t *testing.T) {
|
||||||
sel, err := Parse(`.foo.["bar"].[138]?.baz[1:]`)
|
str := `.foo.["bar"].[138]?.baz[1:]`
|
||||||
|
sel, err := Parse(str)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
printSegments(sel)
|
printSegments(sel)
|
||||||
|
require.Equal(t, str, sel.String())
|
||||||
require.Equal(t, 7, len(sel))
|
require.Equal(t, 7, len(sel))
|
||||||
require.False(t, sel[0].Identity())
|
require.False(t, sel[0].Identity())
|
||||||
require.False(t, sel[0].Optional())
|
require.False(t, sel[0].Optional())
|
||||||
@@ -237,3 +243,189 @@ func printSegments(s Selector) {
|
|||||||
fmt.Printf("%d: %s\n", i, seg.String())
|
fmt.Printf("%d: %s\n", i, seg.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSelect(t *testing.T) {
|
||||||
|
type name struct {
|
||||||
|
First string
|
||||||
|
Middle *string
|
||||||
|
Last string
|
||||||
|
}
|
||||||
|
type interest struct {
|
||||||
|
Name string
|
||||||
|
Outdoor bool
|
||||||
|
Experience int
|
||||||
|
}
|
||||||
|
type user struct {
|
||||||
|
Name name
|
||||||
|
Age int
|
||||||
|
Nationalities []string
|
||||||
|
Interests []interest
|
||||||
|
}
|
||||||
|
|
||||||
|
ts, err := ipld.LoadSchemaBytes([]byte(`
|
||||||
|
type User struct {
|
||||||
|
name Name
|
||||||
|
age Int
|
||||||
|
nationalities [String]
|
||||||
|
interests [Interest]
|
||||||
|
}
|
||||||
|
type Name struct {
|
||||||
|
first String
|
||||||
|
middle optional String
|
||||||
|
last String
|
||||||
|
}
|
||||||
|
type Interest struct {
|
||||||
|
name String
|
||||||
|
outdoor Bool
|
||||||
|
experience Int
|
||||||
|
}
|
||||||
|
`))
|
||||||
|
require.NoError(t, err)
|
||||||
|
typ := ts.TypeByName("User")
|
||||||
|
|
||||||
|
am := "Joan"
|
||||||
|
alice := user{
|
||||||
|
Name: name{First: "Alice", Middle: &am, Last: "Wonderland"},
|
||||||
|
Age: 24,
|
||||||
|
Nationalities: []string{"British"},
|
||||||
|
Interests: []interest{
|
||||||
|
{Name: "Cycling", Outdoor: true, Experience: 4},
|
||||||
|
{Name: "Chess", Outdoor: false, Experience: 2},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
bob := user{
|
||||||
|
Name: name{First: "Bob", Last: "Builder"},
|
||||||
|
Age: 35,
|
||||||
|
Nationalities: []string{"Canadian", "South African"},
|
||||||
|
Interests: []interest{
|
||||||
|
{Name: "Snowboarding", Outdoor: true, Experience: 8},
|
||||||
|
{Name: "Reading", Outdoor: false, Experience: 25},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
anode := bindnode.Wrap(&alice, typ)
|
||||||
|
bnode := bindnode.Wrap(&bob, typ)
|
||||||
|
|
||||||
|
t.Run("identity", func(t *testing.T) {
|
||||||
|
sel, err := Parse(".")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
one, many, err := Select(sel, anode)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, one)
|
||||||
|
require.Empty(t, many)
|
||||||
|
|
||||||
|
fmt.Println(printer.Sprint(one))
|
||||||
|
|
||||||
|
age := must.Int(must.Node(one.LookupByString("age")))
|
||||||
|
require.Equal(t, int64(alice.Age), age)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("nested property", func(t *testing.T) {
|
||||||
|
sel, err := Parse(".name.first")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
one, many, err := Select(sel, anode)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, one)
|
||||||
|
require.Empty(t, many)
|
||||||
|
|
||||||
|
fmt.Println(printer.Sprint(one))
|
||||||
|
|
||||||
|
name := must.String(one)
|
||||||
|
require.Equal(t, alice.Name.First, name)
|
||||||
|
|
||||||
|
one, many, err = Select(sel, bnode)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, one)
|
||||||
|
require.Empty(t, many)
|
||||||
|
|
||||||
|
fmt.Println(printer.Sprint(one))
|
||||||
|
|
||||||
|
name = must.String(one)
|
||||||
|
require.Equal(t, bob.Name.First, name)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("optional nested property", func(t *testing.T) {
|
||||||
|
sel, err := Parse(".name.middle?")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
one, many, err := Select(sel, anode)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, one)
|
||||||
|
require.Empty(t, many)
|
||||||
|
|
||||||
|
fmt.Println(printer.Sprint(one))
|
||||||
|
|
||||||
|
name := must.String(one)
|
||||||
|
require.Equal(t, *alice.Name.Middle, name)
|
||||||
|
|
||||||
|
one, many, err = Select(sel, bnode)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Empty(t, one)
|
||||||
|
require.Empty(t, many)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("not exists", func(t *testing.T) {
|
||||||
|
sel, err := Parse(".name.foo")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
one, many, err := Select(sel, anode)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Empty(t, one)
|
||||||
|
require.Empty(t, many)
|
||||||
|
|
||||||
|
fmt.Println(err)
|
||||||
|
|
||||||
|
if _, ok := err.(ResolutionError); !ok {
|
||||||
|
t.Fatalf("error was not a resolution error")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("optional not exists", func(t *testing.T) {
|
||||||
|
sel, err := Parse(".name.foo?")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
one, many, err := Select(sel, anode)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Empty(t, one)
|
||||||
|
require.Empty(t, many)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("iterator", func(t *testing.T) {
|
||||||
|
sel, err := Parse(".interests[]")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
one, many, err := Select(sel, anode)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Empty(t, one)
|
||||||
|
require.NotEmpty(t, many)
|
||||||
|
|
||||||
|
for _, n := range many {
|
||||||
|
fmt.Println(printer.Sprint(n))
|
||||||
|
}
|
||||||
|
|
||||||
|
iname := must.String(must.Node(many[0].LookupByString("name")))
|
||||||
|
require.Equal(t, alice.Interests[0].Name, iname)
|
||||||
|
|
||||||
|
iname = must.String(must.Node(many[1].LookupByString("name")))
|
||||||
|
require.Equal(t, alice.Interests[1].Name, iname)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("map iterator", func(t *testing.T) {
|
||||||
|
sel, err := Parse(".interests[0][]")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
one, many, err := Select(sel, anode)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Empty(t, one)
|
||||||
|
require.NotEmpty(t, many)
|
||||||
|
|
||||||
|
for _, n := range many {
|
||||||
|
fmt.Println(printer.Sprint(n))
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(t, alice.Interests[0].Name, must.String(many[0]))
|
||||||
|
require.Equal(t, alice.Interests[0].Experience, int(must.Int(many[2])))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user