Add 'delegation/policy/' from commit '9997e95b385f0618cee68baec1fb3ca41fb296fe'
git-subtree-dir: delegation/policy git-subtree-mainline:c676c2bdf6git-subtree-split:9997e95b38
This commit is contained in:
52
delegation/policy/literal/literal.go
Normal file
52
delegation/policy/literal/literal.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package literal
|
||||
|
||||
import (
|
||||
"github.com/ipld/go-ipld-prime"
|
||||
"github.com/ipld/go-ipld-prime/node/basicnode"
|
||||
)
|
||||
|
||||
func Node(n ipld.Node) ipld.Node {
|
||||
return n
|
||||
}
|
||||
|
||||
func Link(cid ipld.Link) ipld.Node {
|
||||
nb := basicnode.Prototype.Link.NewBuilder()
|
||||
nb.AssignLink(cid)
|
||||
return nb.Build()
|
||||
}
|
||||
|
||||
func Bool(val bool) ipld.Node {
|
||||
nb := basicnode.Prototype.Bool.NewBuilder()
|
||||
nb.AssignBool(val)
|
||||
return nb.Build()
|
||||
}
|
||||
|
||||
func Int(val int64) ipld.Node {
|
||||
nb := basicnode.Prototype.Int.NewBuilder()
|
||||
nb.AssignInt(val)
|
||||
return nb.Build()
|
||||
}
|
||||
|
||||
func Float(val float64) ipld.Node {
|
||||
nb := basicnode.Prototype.Float.NewBuilder()
|
||||
nb.AssignFloat(val)
|
||||
return nb.Build()
|
||||
}
|
||||
|
||||
func String(val string) ipld.Node {
|
||||
nb := basicnode.Prototype.String.NewBuilder()
|
||||
nb.AssignString(val)
|
||||
return nb.Build()
|
||||
}
|
||||
|
||||
func Bytes(val []byte) ipld.Node {
|
||||
nb := basicnode.Prototype.Bytes.NewBuilder()
|
||||
nb.AssignBytes(val)
|
||||
return nb.Build()
|
||||
}
|
||||
|
||||
func Null() ipld.Node {
|
||||
nb := basicnode.Prototype.Any.NewBuilder()
|
||||
nb.AssignNull()
|
||||
return nb.Build()
|
||||
}
|
||||
162
delegation/policy/match.go
Normal file
162
delegation/policy/match.go
Normal file
@@ -0,0 +1,162 @@
|
||||
package policy
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"fmt"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// Match determines if the IPLD node matches the policy document.
|
||||
func Match(policy Policy, node ipld.Node) bool {
|
||||
for _, stmt := range policy {
|
||||
ok := matchStatement(stmt, node)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func matchStatement(statement Statement, node ipld.Node) bool {
|
||||
switch statement.Kind() {
|
||||
case Kind_Equal:
|
||||
if s, ok := statement.(EqualityStatement); ok {
|
||||
one, _, err := selector.Select(s.Selector(), node)
|
||||
if err != nil || one == nil {
|
||||
return false
|
||||
}
|
||||
return datamodel.DeepEqual(s.Value(), one)
|
||||
}
|
||||
case Kind_GreaterThan:
|
||||
if s, ok := statement.(InequalityStatement); ok {
|
||||
one, _, err := selector.Select(s.Selector(), node)
|
||||
if err != nil || one == nil {
|
||||
return false
|
||||
}
|
||||
return isOrdered(s.Value(), one, gt)
|
||||
}
|
||||
case Kind_GreaterThanOrEqual:
|
||||
if s, ok := statement.(InequalityStatement); ok {
|
||||
one, _, err := selector.Select(s.Selector(), node)
|
||||
if err != nil || one == nil {
|
||||
return false
|
||||
}
|
||||
return isOrdered(s.Value(), one, gte)
|
||||
}
|
||||
case Kind_LessThan:
|
||||
if s, ok := statement.(InequalityStatement); ok {
|
||||
one, _, err := selector.Select(s.Selector(), node)
|
||||
if err != nil || one == nil {
|
||||
return false
|
||||
}
|
||||
return isOrdered(s.Value(), one, lt)
|
||||
}
|
||||
case Kind_LessThanOrEqual:
|
||||
if s, ok := statement.(InequalityStatement); ok {
|
||||
one, _, err := selector.Select(s.Selector(), node)
|
||||
if err != nil || one == nil {
|
||||
return false
|
||||
}
|
||||
return isOrdered(s.Value(), one, lte)
|
||||
}
|
||||
case Kind_Not:
|
||||
if s, ok := statement.(NegationStatement); ok {
|
||||
return !matchStatement(s.Value(), node)
|
||||
}
|
||||
case Kind_And:
|
||||
if s, ok := statement.(ConjunctionStatement); ok {
|
||||
for _, cs := range s.Value() {
|
||||
r := matchStatement(cs, node)
|
||||
if !r {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
case Kind_Or:
|
||||
if s, ok := statement.(DisjunctionStatement); ok {
|
||||
if len(s.Value()) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, cs := range s.Value() {
|
||||
r := matchStatement(cs, node)
|
||||
if r {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
case Kind_Like:
|
||||
if s, ok := statement.(WildcardStatement); ok {
|
||||
one, _, err := selector.Select(s.Selector(), node)
|
||||
if err != nil || one == nil {
|
||||
return false
|
||||
}
|
||||
v, err := one.AsString()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return s.Value().Match(v)
|
||||
}
|
||||
case Kind_All:
|
||||
if s, ok := statement.(QuantifierStatement); ok {
|
||||
_, many, err := selector.Select(s.Selector(), node)
|
||||
if err != nil || many == nil {
|
||||
return false
|
||||
}
|
||||
for _, n := range many {
|
||||
ok := Match(s.Value(), n)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
case Kind_Any:
|
||||
if s, ok := statement.(QuantifierStatement); ok {
|
||||
_, many, err := selector.Select(s.Selector(), node)
|
||||
if err != nil || many == nil {
|
||||
return false
|
||||
}
|
||||
for _, n := range many {
|
||||
ok := Match(s.Value(), n)
|
||||
if ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
panic(fmt.Errorf("unimplemented statement kind: %s", statement.Kind()))
|
||||
}
|
||||
|
||||
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 {
|
||||
a := must.Int(actual)
|
||||
b := must.Int(expected)
|
||||
return satisfies(cmp.Compare(a, b))
|
||||
}
|
||||
|
||||
if expected.Kind() == ipld.Kind_Float && actual.Kind() == ipld.Kind_Float {
|
||||
a, err := actual.AsFloat()
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("extracting node float: %w", err))
|
||||
}
|
||||
b, err := expected.AsFloat()
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("extracting selector float: %w", err))
|
||||
}
|
||||
return satisfies(cmp.Compare(a, b))
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func gt(order int) bool { return order == 1 }
|
||||
func gte(order int) bool { return order == 0 || order == 1 }
|
||||
func lt(order int) bool { return order == -1 }
|
||||
func lte(order int) bool { return order == 0 || order == -1 }
|
||||
414
delegation/policy/match_test.go
Normal file
414
delegation/policy/match_test.go
Normal file
@@ -0,0 +1,414 @@
|
||||
package policy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/gobwas/glob"
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/ipld/go-ipld-prime"
|
||||
cidlink "github.com/ipld/go-ipld-prime/linking/cid"
|
||||
"github.com/ipld/go-ipld-prime/node/basicnode"
|
||||
"github.com/storacha-network/go-ucanto/core/policy/literal"
|
||||
"github.com/storacha-network/go-ucanto/core/policy/selector"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMatch(t *testing.T) {
|
||||
t.Run("equality", func(t *testing.T) {
|
||||
t.Run("string", func(t *testing.T) {
|
||||
np := basicnode.Prototype.String
|
||||
nb := np.NewBuilder()
|
||||
nb.AssignString("test")
|
||||
nd := nb.Build()
|
||||
|
||||
pol := Policy{Equal(selector.MustParse("."), literal.String("test"))}
|
||||
ok := Match(pol, nd)
|
||||
require.True(t, ok)
|
||||
|
||||
pol = Policy{Equal(selector.MustParse("."), literal.String("test2"))}
|
||||
ok = Match(pol, nd)
|
||||
require.False(t, ok)
|
||||
|
||||
pol = Policy{Equal(selector.MustParse("."), literal.Int(138))}
|
||||
ok = Match(pol, nd)
|
||||
require.False(t, ok)
|
||||
})
|
||||
|
||||
t.Run("int", func(t *testing.T) {
|
||||
np := basicnode.Prototype.Int
|
||||
nb := np.NewBuilder()
|
||||
nb.AssignInt(138)
|
||||
nd := nb.Build()
|
||||
|
||||
pol := Policy{Equal(selector.MustParse("."), literal.Int(138))}
|
||||
ok := Match(pol, nd)
|
||||
require.True(t, ok)
|
||||
|
||||
pol = Policy{Equal(selector.MustParse("."), literal.Int(1138))}
|
||||
ok = Match(pol, nd)
|
||||
require.False(t, ok)
|
||||
|
||||
pol = Policy{Equal(selector.MustParse("."), literal.String("138"))}
|
||||
ok = Match(pol, nd)
|
||||
require.False(t, ok)
|
||||
})
|
||||
|
||||
t.Run("float", func(t *testing.T) {
|
||||
np := basicnode.Prototype.Float
|
||||
nb := np.NewBuilder()
|
||||
nb.AssignFloat(1.138)
|
||||
nd := nb.Build()
|
||||
|
||||
pol := Policy{Equal(selector.MustParse("."), literal.Float(1.138))}
|
||||
ok := Match(pol, nd)
|
||||
require.True(t, ok)
|
||||
|
||||
pol = Policy{Equal(selector.MustParse("."), literal.Float(11.38))}
|
||||
ok = Match(pol, nd)
|
||||
require.False(t, ok)
|
||||
|
||||
pol = Policy{Equal(selector.MustParse("."), literal.String("138"))}
|
||||
ok = Match(pol, nd)
|
||||
require.False(t, ok)
|
||||
})
|
||||
|
||||
t.Run("IPLD Link", func(t *testing.T) {
|
||||
l0 := cidlink.Link{Cid: cid.MustParse("bafybeif4owy5gno5lwnixqm52rwqfodklf76hsetxdhffuxnplvijskzqq")}
|
||||
l1 := cidlink.Link{Cid: cid.MustParse("bafkreifau35r7vi37tvbvfy3hdwvgb4tlflqf7zcdzeujqcjk3rsphiwte")}
|
||||
|
||||
np := basicnode.Prototype.Link
|
||||
nb := np.NewBuilder()
|
||||
nb.AssignLink(l0)
|
||||
nd := nb.Build()
|
||||
|
||||
pol := Policy{Equal(selector.MustParse("."), literal.Link(l0))}
|
||||
ok := Match(pol, nd)
|
||||
require.True(t, ok)
|
||||
|
||||
pol = Policy{Equal(selector.MustParse("."), literal.Link(l1))}
|
||||
ok = Match(pol, nd)
|
||||
require.False(t, ok)
|
||||
|
||||
pol = Policy{Equal(selector.MustParse("."), literal.String("bafybeif4owy5gno5lwnixqm52rwqfodklf76hsetxdhffuxnplvijskzqq"))}
|
||||
ok = Match(pol, nd)
|
||||
require.False(t, ok)
|
||||
})
|
||||
|
||||
t.Run("string in map", func(t *testing.T) {
|
||||
np := basicnode.Prototype.Map
|
||||
nb := np.NewBuilder()
|
||||
ma, _ := nb.BeginMap(1)
|
||||
ma.AssembleKey().AssignString("foo")
|
||||
ma.AssembleValue().AssignString("bar")
|
||||
ma.Finish()
|
||||
nd := nb.Build()
|
||||
|
||||
pol := Policy{Equal(selector.MustParse(".foo"), literal.String("bar"))}
|
||||
ok := Match(pol, nd)
|
||||
require.True(t, ok)
|
||||
|
||||
pol = Policy{Equal(selector.MustParse(".[\"foo\"]"), literal.String("bar"))}
|
||||
ok = Match(pol, nd)
|
||||
require.True(t, ok)
|
||||
|
||||
pol = Policy{Equal(selector.MustParse(".foo"), literal.String("baz"))}
|
||||
ok = Match(pol, nd)
|
||||
require.False(t, ok)
|
||||
|
||||
pol = Policy{Equal(selector.MustParse(".foobar"), literal.String("bar"))}
|
||||
ok = Match(pol, nd)
|
||||
require.False(t, ok)
|
||||
})
|
||||
|
||||
t.Run("string in list", func(t *testing.T) {
|
||||
np := basicnode.Prototype.List
|
||||
nb := np.NewBuilder()
|
||||
la, _ := nb.BeginList(1)
|
||||
la.AssembleValue().AssignString("foo")
|
||||
la.Finish()
|
||||
nd := nb.Build()
|
||||
|
||||
pol := Policy{Equal(selector.MustParse(".[0]"), literal.String("foo"))}
|
||||
ok := Match(pol, nd)
|
||||
require.True(t, ok)
|
||||
|
||||
pol = Policy{Equal(selector.MustParse(".[1]"), literal.String("foo"))}
|
||||
ok = Match(pol, nd)
|
||||
require.False(t, ok)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("inequality", func(t *testing.T) {
|
||||
t.Run("gt int", func(t *testing.T) {
|
||||
np := basicnode.Prototype.Int
|
||||
nb := np.NewBuilder()
|
||||
nb.AssignInt(138)
|
||||
nd := nb.Build()
|
||||
|
||||
pol := Policy{GreaterThan(selector.MustParse("."), literal.Int(1))}
|
||||
ok := Match(pol, nd)
|
||||
require.True(t, ok)
|
||||
})
|
||||
|
||||
t.Run("gte int", func(t *testing.T) {
|
||||
np := basicnode.Prototype.Int
|
||||
nb := np.NewBuilder()
|
||||
nb.AssignInt(138)
|
||||
nd := nb.Build()
|
||||
|
||||
pol := Policy{GreaterThanOrEqual(selector.MustParse("."), literal.Int(1))}
|
||||
ok := Match(pol, nd)
|
||||
require.True(t, ok)
|
||||
|
||||
pol = Policy{GreaterThanOrEqual(selector.MustParse("."), literal.Int(138))}
|
||||
ok = Match(pol, nd)
|
||||
require.True(t, ok)
|
||||
})
|
||||
|
||||
t.Run("gt float", func(t *testing.T) {
|
||||
np := basicnode.Prototype.Float
|
||||
nb := np.NewBuilder()
|
||||
nb.AssignFloat(1.38)
|
||||
nd := nb.Build()
|
||||
|
||||
pol := Policy{GreaterThan(selector.MustParse("."), literal.Float(1))}
|
||||
ok := Match(pol, nd)
|
||||
require.True(t, ok)
|
||||
})
|
||||
|
||||
t.Run("gte float", func(t *testing.T) {
|
||||
np := basicnode.Prototype.Float
|
||||
nb := np.NewBuilder()
|
||||
nb.AssignFloat(1.38)
|
||||
nd := nb.Build()
|
||||
|
||||
pol := Policy{GreaterThanOrEqual(selector.MustParse("."), literal.Float(1))}
|
||||
ok := Match(pol, nd)
|
||||
require.True(t, ok)
|
||||
|
||||
pol = Policy{GreaterThanOrEqual(selector.MustParse("."), literal.Float(1.38))}
|
||||
ok = Match(pol, nd)
|
||||
require.True(t, ok)
|
||||
})
|
||||
|
||||
t.Run("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("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)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("negation", func(t *testing.T) {
|
||||
np := basicnode.Prototype.Bool
|
||||
nb := np.NewBuilder()
|
||||
nb.AssignBool(false)
|
||||
nd := nb.Build()
|
||||
|
||||
pol := Policy{Not(Equal(selector.MustParse("."), literal.Bool(true)))}
|
||||
ok := Match(pol, nd)
|
||||
require.True(t, ok)
|
||||
|
||||
pol = Policy{Not(Equal(selector.MustParse("."), literal.Bool(false)))}
|
||||
ok = Match(pol, nd)
|
||||
require.False(t, ok)
|
||||
})
|
||||
|
||||
t.Run("conjunction", func(t *testing.T) {
|
||||
np := basicnode.Prototype.Int
|
||||
nb := np.NewBuilder()
|
||||
nb.AssignInt(138)
|
||||
nd := nb.Build()
|
||||
|
||||
pol := Policy{
|
||||
And(
|
||||
GreaterThan(selector.MustParse("."), literal.Int(1)),
|
||||
LessThan(selector.MustParse("."), literal.Int(1138)),
|
||||
),
|
||||
}
|
||||
ok := Match(pol, nd)
|
||||
require.True(t, ok)
|
||||
|
||||
pol = Policy{
|
||||
And(
|
||||
GreaterThan(selector.MustParse("."), literal.Int(1)),
|
||||
Equal(selector.MustParse("."), literal.Int(1138)),
|
||||
),
|
||||
}
|
||||
ok = Match(pol, nd)
|
||||
require.False(t, ok)
|
||||
|
||||
pol = Policy{And()}
|
||||
ok = Match(pol, nd)
|
||||
require.True(t, ok)
|
||||
})
|
||||
|
||||
t.Run("disjunction", func(t *testing.T) {
|
||||
np := basicnode.Prototype.Int
|
||||
nb := np.NewBuilder()
|
||||
nb.AssignInt(138)
|
||||
nd := nb.Build()
|
||||
|
||||
pol := Policy{
|
||||
Or(
|
||||
GreaterThan(selector.MustParse("."), literal.Int(138)),
|
||||
LessThan(selector.MustParse("."), literal.Int(1138)),
|
||||
),
|
||||
}
|
||||
ok := Match(pol, nd)
|
||||
require.True(t, ok)
|
||||
|
||||
pol = Policy{
|
||||
Or(
|
||||
GreaterThan(selector.MustParse("."), literal.Int(138)),
|
||||
Equal(selector.MustParse("."), literal.Int(1138)),
|
||||
),
|
||||
}
|
||||
ok = Match(pol, nd)
|
||||
require.False(t, ok)
|
||||
|
||||
pol = Policy{Or()}
|
||||
ok = Match(pol, nd)
|
||||
require.True(t, ok)
|
||||
})
|
||||
|
||||
t.Run("wildcard", func(t *testing.T) {
|
||||
glb, err := glob.Compile(`Alice\*, Bob*, Carol.`)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, s := range []string{
|
||||
"Alice*, Bob, Carol.",
|
||||
"Alice*, Bob, Dan, Erin, Carol.",
|
||||
"Alice*, Bob , Carol.",
|
||||
"Alice*, Bob*, Carol.",
|
||||
} {
|
||||
func(s string) {
|
||||
t.Run(fmt.Sprintf("pass %s", s), func(t *testing.T) {
|
||||
np := basicnode.Prototype.String
|
||||
nb := np.NewBuilder()
|
||||
nb.AssignString(s)
|
||||
nd := nb.Build()
|
||||
|
||||
pol := Policy{Like(selector.MustParse("."), glb)}
|
||||
ok := Match(pol, nd)
|
||||
require.True(t, ok)
|
||||
})
|
||||
}(s)
|
||||
}
|
||||
|
||||
for _, s := range []string{
|
||||
"Alice*, Bob, Carol",
|
||||
"Alice*, Bob*, Carol!",
|
||||
"Alice, Bob, Carol.",
|
||||
" Alice*, Bob, Carol. ",
|
||||
} {
|
||||
func(s string) {
|
||||
t.Run(fmt.Sprintf("fail %s", s), func(t *testing.T) {
|
||||
np := basicnode.Prototype.String
|
||||
nb := np.NewBuilder()
|
||||
nb.AssignString(s)
|
||||
nd := nb.Build()
|
||||
|
||||
pol := Policy{Like(selector.MustParse("."), glb)}
|
||||
ok := Match(pol, nd)
|
||||
require.False(t, ok)
|
||||
})
|
||||
}(s)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("quantification", func(t *testing.T) {
|
||||
buildValueNode := func(v int64) ipld.Node {
|
||||
np := basicnode.Prototype.Map
|
||||
nb := np.NewBuilder()
|
||||
ma, _ := nb.BeginMap(1)
|
||||
ma.AssembleKey().AssignString("value")
|
||||
ma.AssembleValue().AssignInt(v)
|
||||
ma.Finish()
|
||||
return nb.Build()
|
||||
}
|
||||
|
||||
t.Run("all", func(t *testing.T) {
|
||||
np := basicnode.Prototype.List
|
||||
nb := np.NewBuilder()
|
||||
la, _ := nb.BeginList(5)
|
||||
la.AssembleValue().AssignNode(buildValueNode(5))
|
||||
la.AssembleValue().AssignNode(buildValueNode(10))
|
||||
la.AssembleValue().AssignNode(buildValueNode(20))
|
||||
la.AssembleValue().AssignNode(buildValueNode(50))
|
||||
la.AssembleValue().AssignNode(buildValueNode(100))
|
||||
la.Finish()
|
||||
nd := nb.Build()
|
||||
|
||||
pol := Policy{
|
||||
All(
|
||||
selector.MustParse(".[]"),
|
||||
GreaterThan(selector.MustParse(".value"), literal.Int(2)),
|
||||
),
|
||||
}
|
||||
ok := Match(pol, nd)
|
||||
require.True(t, ok)
|
||||
|
||||
pol = Policy{
|
||||
All(
|
||||
selector.MustParse(".[]"),
|
||||
GreaterThan(selector.MustParse(".value"), literal.Int(20)),
|
||||
),
|
||||
}
|
||||
ok = Match(pol, nd)
|
||||
require.False(t, ok)
|
||||
})
|
||||
|
||||
t.Run("any", func(t *testing.T) {
|
||||
np := basicnode.Prototype.List
|
||||
nb := np.NewBuilder()
|
||||
la, _ := nb.BeginList(5)
|
||||
la.AssembleValue().AssignNode(buildValueNode(5))
|
||||
la.AssembleValue().AssignNode(buildValueNode(10))
|
||||
la.AssembleValue().AssignNode(buildValueNode(20))
|
||||
la.AssembleValue().AssignNode(buildValueNode(50))
|
||||
la.AssembleValue().AssignNode(buildValueNode(100))
|
||||
la.Finish()
|
||||
nd := nb.Build()
|
||||
|
||||
pol := Policy{
|
||||
Any(
|
||||
selector.MustParse(".[]"),
|
||||
GreaterThan(selector.MustParse(".value"), literal.Int(10)),
|
||||
LessThan(selector.MustParse(".value"), literal.Int(50)),
|
||||
),
|
||||
}
|
||||
ok := Match(pol, nd)
|
||||
require.True(t, ok)
|
||||
|
||||
pol = Policy{
|
||||
Any(
|
||||
selector.MustParse(".[]"),
|
||||
GreaterThan(selector.MustParse(".value"), literal.Int(100)),
|
||||
),
|
||||
}
|
||||
ok = Match(pol, nd)
|
||||
require.False(t, ok)
|
||||
})
|
||||
})
|
||||
}
|
||||
205
delegation/policy/policy.go
Normal file
205
delegation/policy/policy.go
Normal file
@@ -0,0 +1,205 @@
|
||||
package policy
|
||||
|
||||
// https://github.com/ucan-wg/delegation/blob/4094d5878b58f5d35055a3b93fccda0b8329ebae/README.md#policy
|
||||
|
||||
import (
|
||||
"github.com/gobwas/glob"
|
||||
"github.com/ipld/go-ipld-prime"
|
||||
"github.com/storacha-network/go-ucanto/core/policy/selector"
|
||||
)
|
||||
|
||||
const (
|
||||
Kind_Equal = "=="
|
||||
Kind_GreaterThan = ">"
|
||||
Kind_GreaterThanOrEqual = ">="
|
||||
Kind_LessThan = "<"
|
||||
Kind_LessThanOrEqual = "<="
|
||||
Kind_Not = "not"
|
||||
Kind_And = "and"
|
||||
Kind_Or = "or"
|
||||
Kind_Like = "like"
|
||||
Kind_All = "all"
|
||||
Kind_Any = "any"
|
||||
)
|
||||
|
||||
type Policy = []Statement
|
||||
|
||||
type Statement interface {
|
||||
Kind() string
|
||||
}
|
||||
|
||||
type EqualityStatement interface {
|
||||
Statement
|
||||
Selector() selector.Selector
|
||||
Value() ipld.Node
|
||||
}
|
||||
|
||||
type InequalityStatement interface {
|
||||
Statement
|
||||
Selector() selector.Selector
|
||||
Value() ipld.Node
|
||||
}
|
||||
|
||||
type WildcardStatement interface {
|
||||
Statement
|
||||
Selector() selector.Selector
|
||||
Value() glob.Glob
|
||||
}
|
||||
|
||||
type ConnectiveStatement interface {
|
||||
Statement
|
||||
}
|
||||
|
||||
type NegationStatement interface {
|
||||
ConnectiveStatement
|
||||
Value() Statement
|
||||
}
|
||||
|
||||
type ConjunctionStatement interface {
|
||||
ConnectiveStatement
|
||||
Value() []Statement
|
||||
}
|
||||
|
||||
type DisjunctionStatement interface {
|
||||
ConnectiveStatement
|
||||
Value() []Statement
|
||||
}
|
||||
|
||||
type QuantifierStatement interface {
|
||||
Statement
|
||||
Selector() selector.Selector
|
||||
Value() Policy
|
||||
}
|
||||
|
||||
type equality struct {
|
||||
kind string
|
||||
selector selector.Selector
|
||||
value ipld.Node
|
||||
}
|
||||
|
||||
func (e equality) Kind() string {
|
||||
return e.kind
|
||||
}
|
||||
|
||||
func (e equality) Value() ipld.Node {
|
||||
return e.value
|
||||
}
|
||||
|
||||
func (e equality) Selector() selector.Selector {
|
||||
return e.selector
|
||||
}
|
||||
|
||||
func Equal(selector selector.Selector, value ipld.Node) EqualityStatement {
|
||||
return equality{Kind_Equal, selector, value}
|
||||
}
|
||||
|
||||
func GreaterThan(selector selector.Selector, value ipld.Node) InequalityStatement {
|
||||
return equality{Kind_GreaterThan, selector, value}
|
||||
}
|
||||
|
||||
func GreaterThanOrEqual(selector selector.Selector, value ipld.Node) InequalityStatement {
|
||||
return equality{Kind_GreaterThanOrEqual, selector, value}
|
||||
}
|
||||
|
||||
func LessThan(selector selector.Selector, value ipld.Node) InequalityStatement {
|
||||
return equality{Kind_LessThan, selector, value}
|
||||
}
|
||||
|
||||
func LessThanOrEqual(selector selector.Selector, value ipld.Node) InequalityStatement {
|
||||
return equality{Kind_LessThanOrEqual, selector, value}
|
||||
}
|
||||
|
||||
type negation struct {
|
||||
statement Statement
|
||||
}
|
||||
|
||||
func (n negation) Kind() string {
|
||||
return Kind_Not
|
||||
}
|
||||
|
||||
func (n negation) Value() Statement {
|
||||
return n.statement
|
||||
}
|
||||
|
||||
func Not(stmt Statement) NegationStatement {
|
||||
return negation{stmt}
|
||||
}
|
||||
|
||||
type conjunction struct {
|
||||
statements []Statement
|
||||
}
|
||||
|
||||
func (n conjunction) Kind() string {
|
||||
return Kind_And
|
||||
}
|
||||
|
||||
func (n conjunction) Value() []Statement {
|
||||
return n.statements
|
||||
}
|
||||
|
||||
func And(stmts ...Statement) ConjunctionStatement {
|
||||
return conjunction{stmts}
|
||||
}
|
||||
|
||||
type disjunction struct {
|
||||
statements []Statement
|
||||
}
|
||||
|
||||
func (n disjunction) Kind() string {
|
||||
return Kind_Or
|
||||
}
|
||||
|
||||
func (n disjunction) Value() []Statement {
|
||||
return n.statements
|
||||
}
|
||||
|
||||
func Or(stmts ...Statement) DisjunctionStatement {
|
||||
return disjunction{stmts}
|
||||
}
|
||||
|
||||
type wildcard struct {
|
||||
selector selector.Selector
|
||||
glob glob.Glob
|
||||
}
|
||||
|
||||
func (n wildcard) Kind() string {
|
||||
return Kind_Like
|
||||
}
|
||||
|
||||
func (n wildcard) Selector() selector.Selector {
|
||||
return n.selector
|
||||
}
|
||||
|
||||
func (n wildcard) Value() glob.Glob {
|
||||
return n.glob
|
||||
}
|
||||
|
||||
func Like(selector selector.Selector, glob glob.Glob) WildcardStatement {
|
||||
return wildcard{selector, glob}
|
||||
}
|
||||
|
||||
type quantifier struct {
|
||||
kind string
|
||||
selector selector.Selector
|
||||
policy Policy
|
||||
}
|
||||
|
||||
func (n quantifier) Kind() string {
|
||||
return n.kind
|
||||
}
|
||||
|
||||
func (n quantifier) Selector() selector.Selector {
|
||||
return n.selector
|
||||
}
|
||||
|
||||
func (n quantifier) Value() Policy {
|
||||
return n.policy
|
||||
}
|
||||
|
||||
func All(selector selector.Selector, policy ...Statement) QuantifierStatement {
|
||||
return quantifier{Kind_All, selector, policy}
|
||||
}
|
||||
|
||||
func Any(selector selector.Selector, policy ...Statement) QuantifierStatement {
|
||||
return quantifier{Kind_Any, selector, policy}
|
||||
}
|
||||
426
delegation/policy/selector/selector.go
Normal file
426
delegation/policy/selector/selector.go
Normal file
@@ -0,0 +1,426 @@
|
||||
package selector
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/ipld/go-ipld-prime"
|
||||
"github.com/ipld/go-ipld-prime/datamodel"
|
||||
"github.com/ipld/go-ipld-prime/schema"
|
||||
)
|
||||
|
||||
// Selector describes a UCAN policy selector, as specified here:
|
||||
// https://github.com/ucan-wg/delegation/blob/4094d5878b58f5d35055a3b93fccda0b8329ebae/README.md#selectors
|
||||
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 segment's string representation.
|
||||
String() string
|
||||
}
|
||||
|
||||
var Identity = segment{".", true, false, false, nil, "", 0}
|
||||
|
||||
var (
|
||||
indexRegex = regexp.MustCompile(`^-?\d+$`)
|
||||
sliceRegex = regexp.MustCompile(`^((\-?\d+:\-?\d*)|(\-?\d*:\-?\d+))$`)
|
||||
fieldRegex = regexp.MustCompile(`^\.[a-zA-Z_]*?$`)
|
||||
)
|
||||
|
||||
type segment struct {
|
||||
str string
|
||||
identity bool
|
||||
optional bool
|
||||
iterator bool
|
||||
slice []int
|
||||
field string
|
||||
index int
|
||||
}
|
||||
|
||||
func (s segment) String() string {
|
||||
return s.str
|
||||
}
|
||||
|
||||
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]))
|
||||
}
|
||||
|
||||
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 indexRegex.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 sliceRegex.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 fieldRegex.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
|
||||
}
|
||||
|
||||
func tokenize(str string) []string {
|
||||
var toks []string
|
||||
col := 0
|
||||
ofs := 0
|
||||
ctx := ""
|
||||
|
||||
for col < len(str) {
|
||||
char := string(str[col])
|
||||
|
||||
if char == "\"" && string(str[col-1]) != "\\" {
|
||||
col++
|
||||
if ctx == "\"" {
|
||||
ctx = ""
|
||||
} else {
|
||||
ctx = "\""
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if ctx == "\"" {
|
||||
col++
|
||||
continue
|
||||
}
|
||||
|
||||
if char == "." || char == "[" {
|
||||
if ofs < col {
|
||||
toks = append(toks, str[ofs:col])
|
||||
}
|
||||
ofs = col
|
||||
}
|
||||
col++
|
||||
}
|
||||
|
||||
if ofs < col && ctx != "\"" {
|
||||
toks = append(toks, str[ofs:col])
|
||||
}
|
||||
|
||||
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 {
|
||||
s, err := Parse(sel)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
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) {
|
||||
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
|
||||
}
|
||||
|
||||
k, v, err := it.Next()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
key := fmt.Sprintf("%d", k)
|
||||
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 isMissing(err) {
|
||||
if seg.Optional() {
|
||||
cur = nil
|
||||
} else {
|
||||
return nil, nil, NewResolutionError(fmt.Sprintf("object has no field named: %s", seg.Field()), at)
|
||||
}
|
||||
} else {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
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 isMissing(err) {
|
||||
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()
|
||||
}
|
||||
|
||||
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 {
|
||||
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}
|
||||
}
|
||||
431
delegation/policy/selector/selector_test.go
Normal file
431
delegation/policy/selector/selector_test.go
Normal file
@@ -0,0 +1,431 @@
|
||||
package selector
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"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"
|
||||
)
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
t.Run("identity", func(t *testing.T) {
|
||||
sel, err := Parse(".")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(sel))
|
||||
require.True(t, sel[0].Identity())
|
||||
require.False(t, sel[0].Optional())
|
||||
require.False(t, sel[0].Iterator())
|
||||
require.Empty(t, sel[0].Slice())
|
||||
require.Empty(t, sel[0].Field())
|
||||
require.Empty(t, sel[0].Index())
|
||||
})
|
||||
|
||||
t.Run("field", func(t *testing.T) {
|
||||
sel, err := Parse(".foo")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(sel))
|
||||
require.False(t, sel[0].Identity())
|
||||
require.False(t, sel[0].Optional())
|
||||
require.False(t, sel[0].Iterator())
|
||||
require.Empty(t, sel[0].Slice())
|
||||
require.Equal(t, sel[0].Field(), "foo")
|
||||
require.Empty(t, sel[0].Index())
|
||||
})
|
||||
|
||||
t.Run("explicit field", func(t *testing.T) {
|
||||
sel, err := Parse(`.["foo"]`)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(sel))
|
||||
require.True(t, sel[0].Identity())
|
||||
require.False(t, sel[0].Optional())
|
||||
require.False(t, sel[0].Iterator())
|
||||
require.Empty(t, sel[0].Slice())
|
||||
require.Empty(t, sel[0].Field())
|
||||
require.Empty(t, sel[0].Index())
|
||||
require.False(t, sel[1].Identity())
|
||||
require.False(t, sel[1].Optional())
|
||||
require.False(t, sel[1].Iterator())
|
||||
require.Empty(t, sel[1].Slice())
|
||||
require.Equal(t, sel[1].Field(), "foo")
|
||||
require.Empty(t, sel[1].Index())
|
||||
})
|
||||
|
||||
t.Run("index", func(t *testing.T) {
|
||||
sel, err := Parse(".[138]")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(sel))
|
||||
require.True(t, sel[0].Identity())
|
||||
require.False(t, sel[0].Optional())
|
||||
require.False(t, sel[0].Iterator())
|
||||
require.Empty(t, sel[0].Slice())
|
||||
require.Empty(t, sel[0].Field())
|
||||
require.Empty(t, sel[0].Index())
|
||||
require.False(t, sel[1].Identity())
|
||||
require.False(t, sel[1].Optional())
|
||||
require.False(t, sel[1].Iterator())
|
||||
require.Empty(t, sel[1].Slice())
|
||||
require.Empty(t, sel[1].Field())
|
||||
require.Equal(t, sel[1].Index(), 138)
|
||||
})
|
||||
|
||||
t.Run("negative index", func(t *testing.T) {
|
||||
sel, err := Parse(".[-138]")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(sel))
|
||||
require.True(t, sel[0].Identity())
|
||||
require.False(t, sel[0].Optional())
|
||||
require.False(t, sel[0].Iterator())
|
||||
require.Empty(t, sel[0].Slice())
|
||||
require.Empty(t, sel[0].Field())
|
||||
require.Empty(t, sel[0].Index())
|
||||
require.False(t, sel[1].Identity())
|
||||
require.False(t, sel[1].Optional())
|
||||
require.False(t, sel[1].Iterator())
|
||||
require.Empty(t, sel[1].Slice())
|
||||
require.Empty(t, sel[1].Field())
|
||||
require.Equal(t, sel[1].Index(), -138)
|
||||
})
|
||||
|
||||
t.Run("iterator", func(t *testing.T) {
|
||||
sel, err := Parse(".[]")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(sel))
|
||||
require.True(t, sel[0].Identity())
|
||||
require.False(t, sel[0].Optional())
|
||||
require.False(t, sel[0].Iterator())
|
||||
require.Empty(t, sel[0].Slice())
|
||||
require.Empty(t, sel[0].Field())
|
||||
require.Empty(t, sel[0].Index())
|
||||
require.False(t, sel[1].Identity())
|
||||
require.False(t, sel[1].Optional())
|
||||
require.True(t, sel[1].Iterator())
|
||||
require.Empty(t, sel[1].Slice())
|
||||
require.Empty(t, sel[1].Field())
|
||||
require.Empty(t, sel[1].Index())
|
||||
})
|
||||
|
||||
t.Run("optional field", func(t *testing.T) {
|
||||
sel, err := Parse(".foo?")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(sel))
|
||||
require.False(t, sel[0].Identity())
|
||||
require.True(t, sel[0].Optional())
|
||||
require.False(t, sel[0].Iterator())
|
||||
require.Empty(t, sel[0].Slice())
|
||||
require.Equal(t, sel[0].Field(), "foo")
|
||||
require.Empty(t, sel[0].Index())
|
||||
})
|
||||
|
||||
t.Run("optional explicit field", func(t *testing.T) {
|
||||
sel, err := Parse(`.["foo"]?`)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(sel))
|
||||
require.True(t, sel[0].Identity())
|
||||
require.False(t, sel[0].Optional())
|
||||
require.False(t, sel[0].Iterator())
|
||||
require.Empty(t, sel[0].Slice())
|
||||
require.Empty(t, sel[0].Field())
|
||||
require.Empty(t, sel[0].Index())
|
||||
require.False(t, sel[1].Identity())
|
||||
require.True(t, sel[1].Optional())
|
||||
require.False(t, sel[1].Iterator())
|
||||
require.Empty(t, sel[1].Slice())
|
||||
require.Equal(t, sel[1].Field(), "foo")
|
||||
require.Empty(t, sel[1].Index())
|
||||
})
|
||||
|
||||
t.Run("optional index", func(t *testing.T) {
|
||||
sel, err := Parse(".[138]?")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(sel))
|
||||
require.True(t, sel[0].Identity())
|
||||
require.False(t, sel[0].Optional())
|
||||
require.False(t, sel[0].Iterator())
|
||||
require.Empty(t, sel[0].Slice())
|
||||
require.Empty(t, sel[0].Field())
|
||||
require.Empty(t, sel[0].Index())
|
||||
require.False(t, sel[1].Identity())
|
||||
require.True(t, sel[1].Optional())
|
||||
require.False(t, sel[1].Iterator())
|
||||
require.Empty(t, sel[1].Slice())
|
||||
require.Empty(t, sel[1].Field())
|
||||
require.Equal(t, sel[1].Index(), 138)
|
||||
})
|
||||
|
||||
t.Run("optional iterator", func(t *testing.T) {
|
||||
sel, err := Parse(".[]?")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(sel))
|
||||
require.True(t, sel[0].Identity())
|
||||
require.False(t, sel[0].Optional())
|
||||
require.False(t, sel[0].Iterator())
|
||||
require.Empty(t, sel[0].Slice())
|
||||
require.Empty(t, sel[0].Field())
|
||||
require.Empty(t, sel[0].Index())
|
||||
require.False(t, sel[1].Identity())
|
||||
require.True(t, sel[1].Optional())
|
||||
require.True(t, sel[1].Iterator())
|
||||
require.Empty(t, sel[1].Slice())
|
||||
require.Empty(t, sel[1].Field())
|
||||
require.Empty(t, sel[1].Index())
|
||||
})
|
||||
|
||||
t.Run("nesting", func(t *testing.T) {
|
||||
str := `.foo.["bar"].[138]?.baz[1:]`
|
||||
sel, err := Parse(str)
|
||||
require.NoError(t, err)
|
||||
printSegments(sel)
|
||||
require.Equal(t, str, sel.String())
|
||||
require.Equal(t, 7, len(sel))
|
||||
require.False(t, sel[0].Identity())
|
||||
require.False(t, sel[0].Optional())
|
||||
require.False(t, sel[0].Iterator())
|
||||
require.Empty(t, sel[0].Slice())
|
||||
require.Equal(t, sel[0].Field(), "foo")
|
||||
require.Empty(t, sel[0].Index())
|
||||
require.True(t, sel[1].Identity())
|
||||
require.False(t, sel[1].Optional())
|
||||
require.False(t, sel[1].Iterator())
|
||||
require.Empty(t, sel[1].Slice())
|
||||
require.Empty(t, sel[1].Field())
|
||||
require.Empty(t, sel[1].Index())
|
||||
require.False(t, sel[2].Identity())
|
||||
require.False(t, sel[2].Optional())
|
||||
require.False(t, sel[2].Iterator())
|
||||
require.Empty(t, sel[2].Slice())
|
||||
require.Equal(t, sel[2].Field(), "bar")
|
||||
require.Empty(t, sel[2].Index())
|
||||
require.True(t, sel[3].Identity())
|
||||
require.False(t, sel[3].Optional())
|
||||
require.False(t, sel[3].Iterator())
|
||||
require.Empty(t, sel[3].Slice())
|
||||
require.Empty(t, sel[3].Field())
|
||||
require.Empty(t, sel[3].Index())
|
||||
require.False(t, sel[4].Identity())
|
||||
require.True(t, sel[4].Optional())
|
||||
require.False(t, sel[4].Iterator())
|
||||
require.Empty(t, sel[4].Slice())
|
||||
require.Empty(t, sel[4].Field())
|
||||
require.Equal(t, sel[4].Index(), 138)
|
||||
require.False(t, sel[5].Identity())
|
||||
require.False(t, sel[5].Optional())
|
||||
require.False(t, sel[5].Iterator())
|
||||
require.Empty(t, sel[5].Slice())
|
||||
require.Equal(t, sel[5].Field(), "baz")
|
||||
require.Empty(t, sel[5].Index())
|
||||
require.False(t, sel[6].Identity())
|
||||
require.False(t, sel[6].Optional())
|
||||
require.False(t, sel[6].Iterator())
|
||||
require.Equal(t, sel[6].Slice(), []int{1})
|
||||
require.Empty(t, sel[6].Field())
|
||||
require.Empty(t, sel[6].Index())
|
||||
})
|
||||
|
||||
t.Run("non dotted", func(t *testing.T) {
|
||||
_, err := Parse("foo")
|
||||
require.NotNil(t, err)
|
||||
fmt.Println(err)
|
||||
})
|
||||
|
||||
t.Run("non quoted", func(t *testing.T) {
|
||||
_, err := Parse(".[foo]")
|
||||
require.NotNil(t, err)
|
||||
fmt.Println(err)
|
||||
})
|
||||
}
|
||||
|
||||
func printSegments(s Selector) {
|
||||
for i, seg := range s {
|
||||
fmt.Printf("%d: %s\n", i, seg.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelect(t *testing.T) {
|
||||
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])))
|
||||
})
|
||||
}
|
||||
163
delegation/policy/selector/supported.json
Normal file
163
delegation/policy/selector/supported.json
Normal file
@@ -0,0 +1,163 @@
|
||||
{
|
||||
"pass": [
|
||||
{
|
||||
"name": "Identity",
|
||||
"selector": ".",
|
||||
"input": "{\"x\":1}",
|
||||
"output": "{\"x\":1}"
|
||||
},
|
||||
{
|
||||
"name": "Iterator",
|
||||
"selector": ".[]",
|
||||
"input": "[1, 2]",
|
||||
"output": "[1, 2]"
|
||||
},
|
||||
{
|
||||
"name": "Optional Null Iterator",
|
||||
"selector": ".[]?",
|
||||
"input": "null",
|
||||
"output": "()"
|
||||
},
|
||||
{
|
||||
"name": "Optional Iterator",
|
||||
"selector": ".[][]?",
|
||||
"input": "[[1], 2, [3]]",
|
||||
"output": "[1, 3]"
|
||||
},
|
||||
{
|
||||
"name": "Object Key",
|
||||
"selector": ".x",
|
||||
"input": "{\"x\": 1 }",
|
||||
"output": "1"
|
||||
},
|
||||
{
|
||||
"name": "Quoted Key",
|
||||
"selector": ".[\"x\"]",
|
||||
"input": "{\"x\": 1}",
|
||||
"output": "1"
|
||||
},
|
||||
{
|
||||
"name": "Index",
|
||||
"selector": ".[0]",
|
||||
"input": "[1, 2]",
|
||||
"output": "1"
|
||||
},
|
||||
{
|
||||
"name": "Negative Index",
|
||||
"selector": ".[-1]",
|
||||
"input": "[1, 2]",
|
||||
"output": "2"
|
||||
},
|
||||
{
|
||||
"name": "String Index",
|
||||
"selector": ".[0]",
|
||||
"input": "\"Hi\"",
|
||||
"output": "\"H\""
|
||||
},
|
||||
{
|
||||
"name": "Bytes Index",
|
||||
"selector": ".[0]",
|
||||
"input": "{\"/\":{\"bytes\":\"AAE\"}",
|
||||
"output": "0"
|
||||
},
|
||||
{
|
||||
"name": "Array Slice",
|
||||
"selector": ".[0:2]",
|
||||
"input": "[0, 1, 2]",
|
||||
"output": "[0, 1]"
|
||||
},
|
||||
{
|
||||
"name": "Array Slice",
|
||||
"selector": ".[1:]",
|
||||
"input": "[0, 1, 2]",
|
||||
"output": "[1, 2]"
|
||||
},
|
||||
{
|
||||
"name": "Array Slice",
|
||||
"selector": ".[:2]",
|
||||
"input": "[0, 1, 2]",
|
||||
"output": "[0, 1]"
|
||||
},
|
||||
{
|
||||
"name": "String Slice",
|
||||
"selector": ".[0:2]",
|
||||
"input": "\"hello\"",
|
||||
"output": "\"he\""
|
||||
},
|
||||
{
|
||||
"name": "Bytes Index",
|
||||
"selector": ".[1:]",
|
||||
"input": "{\"/\":{\"bytes\":\"AAEC\"}}",
|
||||
"output": "{\"/\":{\"bytes\":\"AQI\"}}"
|
||||
}
|
||||
],
|
||||
"null": [
|
||||
{
|
||||
"name": "Optional Missing Key",
|
||||
"selector": ".x?",
|
||||
"input": "{}"
|
||||
},
|
||||
{
|
||||
"name": "Optional Null Key",
|
||||
"selector": ".x?",
|
||||
"input": "null"
|
||||
},
|
||||
{
|
||||
"name": "Optional Array Key",
|
||||
"selector": ".x?",
|
||||
"input": "[]"
|
||||
},
|
||||
{
|
||||
"name": "Optional Quoted Key",
|
||||
"selector": ".[\"x\"]?",
|
||||
"input": "{}"
|
||||
},
|
||||
{
|
||||
"name": ".length?",
|
||||
"selector": ".length?",
|
||||
"input": "[1, 2]"
|
||||
},
|
||||
{
|
||||
"name": "Optional Index",
|
||||
"selector": ".[4]?",
|
||||
"input": "[0, 1]"
|
||||
}
|
||||
],
|
||||
"fail": [
|
||||
{
|
||||
"name": "Null Iterator",
|
||||
"selector": ".[]",
|
||||
"input": "null"
|
||||
},
|
||||
{
|
||||
"name": "Nested Iterator",
|
||||
"selector": ".[][]",
|
||||
"input": "[[1], 2, [3]]"
|
||||
},
|
||||
{
|
||||
"name": "Missing Key",
|
||||
"selector": ".x",
|
||||
"input": "{}"
|
||||
},
|
||||
{
|
||||
"name": "Null Key",
|
||||
"selector": ".x",
|
||||
"input": "null"
|
||||
},
|
||||
{
|
||||
"name": "Array Key",
|
||||
"selector": ".x",
|
||||
"input": "[]"
|
||||
},
|
||||
{
|
||||
"name": ".length",
|
||||
"selector": ".length",
|
||||
"input": "[1, 2]"
|
||||
},
|
||||
{
|
||||
"name": "Out of bound Index",
|
||||
"selector": ".[4]",
|
||||
"input": "[0, 1]"
|
||||
}
|
||||
]
|
||||
}
|
||||
187
delegation/policy/selector/supported_test.go
Normal file
187
delegation/policy/selector/supported_test.go
Normal file
@@ -0,0 +1,187 @@
|
||||
package selector_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ipld/go-ipld-prime"
|
||||
"github.com/ipld/go-ipld-prime/codec/dagjson"
|
||||
"github.com/ipld/go-ipld-prime/datamodel"
|
||||
basicnode "github.com/ipld/go-ipld-prime/node/basic"
|
||||
"github.com/storacha-network/go-ucanto/core/policy/selector"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/wI2L/jsondiff"
|
||||
)
|
||||
|
||||
//go:embed supported.json
|
||||
var supported []byte
|
||||
|
||||
type Testcase struct {
|
||||
Name string `json:"name"`
|
||||
Selector string `json:"selector"`
|
||||
Input string `json:"input"`
|
||||
}
|
||||
|
||||
func (tc Testcase) Select(t *testing.T) (datamodel.Node, []datamodel.Node, error) {
|
||||
t.Helper()
|
||||
|
||||
sel, err := selector.Parse(tc.Selector)
|
||||
require.NoError(t, err)
|
||||
|
||||
return selector.Select(sel, node(t, tc.Input))
|
||||
}
|
||||
|
||||
type SuccessTestcase struct {
|
||||
Testcase
|
||||
Output *string `json:"output"`
|
||||
}
|
||||
|
||||
func (tc SuccessTestcase) SelectAndCompare(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
exp := node(t, *tc.Output)
|
||||
|
||||
node, nodes, err := tc.Select(t)
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, node != nil, len(nodes) > 0) // XOR (only one of node or nodes should be set)
|
||||
|
||||
if node == nil {
|
||||
nb := basicnode.Prototype.List.NewBuilder()
|
||||
la, err := nb.BeginList(int64(len(nodes)))
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, n := range nodes {
|
||||
// TODO: This code is probably not needed if the Select operation properly prunes nil values - e.g.: Optional Iterator
|
||||
if n == nil {
|
||||
n = datamodel.Null
|
||||
}
|
||||
|
||||
require.NoError(t, la.AssembleValue().AssignNode(n))
|
||||
}
|
||||
|
||||
require.NoError(t, la.Finish())
|
||||
|
||||
node = nb.Build()
|
||||
}
|
||||
|
||||
equalIPLD(t, exp, node)
|
||||
}
|
||||
|
||||
type Testcases struct {
|
||||
SuccessTestcases []SuccessTestcase `json:"pass"`
|
||||
NullTestcases []Testcase `json:"null"`
|
||||
ErrorTestcases []Testcase `json:"fail"`
|
||||
}
|
||||
|
||||
// TestSupported Forms runs tests against the Selector according to the
|
||||
// proposed "Supported Forms" presented in this GitHub issue:
|
||||
// https://github.com/ucan-wg/delegation/issues/5#issue-2154766496
|
||||
func TestSupportedForms(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var testcases Testcases
|
||||
|
||||
require.NoError(t, json.Unmarshal(supported, &testcases))
|
||||
|
||||
t.Run("node(s)", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, testcase := range testcases.SuccessTestcases {
|
||||
testcase := testcase
|
||||
|
||||
t.Run(testcase.Name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// TODO: This test case panics during Select, though Parse works - reports
|
||||
// "index out of range [-1]" so a bit of subtraction and some bounds checking
|
||||
// should fix this testcase.
|
||||
if testcase.Name == "Negative Index" {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
testcase.SelectAndCompare(t)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("null", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, testcase := range testcases.NullTestcases {
|
||||
testcase := testcase
|
||||
|
||||
t.Run(testcase.Name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
node, nodes, err := testcase.Select(t)
|
||||
require.NoError(t, err)
|
||||
// TODO: should Select return a single node which is sometimes a list or null?
|
||||
// require.Equal(t, datamodel.Null, node)
|
||||
assert.Nil(t, node)
|
||||
assert.Empty(t, nodes)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("error", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, testcase := range testcases.ErrorTestcases {
|
||||
testcase := testcase
|
||||
|
||||
t.Run(testcase.Name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
node, nodes, err := testcase.Select(t)
|
||||
require.Error(t, err)
|
||||
assert.Nil(t, node)
|
||||
assert.Empty(t, nodes)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func equalIPLD(t *testing.T, expected datamodel.Node, actual datamodel.Node, msgAndArgs ...interface{}) bool {
|
||||
t.Helper()
|
||||
|
||||
if !assert.ObjectsAreEqual(expected, actual) {
|
||||
exp, act := &bytes.Buffer{}, &bytes.Buffer{}
|
||||
if err := dagjson.Encode(expected, exp); err != nil {
|
||||
return assert.Fail(t, "Failed to encode json for expected IPLD node")
|
||||
}
|
||||
|
||||
if err := dagjson.Encode(actual, act); err != nil {
|
||||
return assert.Fail(t, "Failed to encode JSON for actual IPLD node")
|
||||
}
|
||||
|
||||
diff, err := jsondiff.CompareJSON(act.Bytes(), exp.Bytes())
|
||||
if err != nil {
|
||||
return assert.Fail(t, "Failed to create diff of expected and actual IPLD nodes")
|
||||
}
|
||||
|
||||
return assert.Fail(t, fmt.Sprintf("Not equal: \n"+
|
||||
"expected: %s\n"+
|
||||
"actual: %s\n"+
|
||||
"diff: %s", exp, act, diff), msgAndArgs)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func node(t *testing.T, json string) ipld.Node {
|
||||
t.Helper()
|
||||
|
||||
np := basicnode.Prototype.Any
|
||||
nb := np.NewBuilder()
|
||||
require.NoError(t, dagjson.Decode(nb, strings.NewReader(json)))
|
||||
|
||||
node := nb.Build()
|
||||
require.NotNil(t, node)
|
||||
|
||||
return node
|
||||
}
|
||||
Reference in New Issue
Block a user