Merge pull request #56 from ucan-wg/match-return

policy: make Match also return the failing statement
This commit is contained in:
Michael Muré
2024-11-07 15:40:57 +01:00
committed by GitHub
2 changed files with 166 additions and 153 deletions

View File

@@ -10,17 +10,18 @@ import (
) )
// Match determines if the IPLD node satisfies the policy. // Match determines if the IPLD node satisfies the policy.
func (p Policy) Match(node datamodel.Node) bool { // The first Statement failing to match is returned as well.
func (p Policy) Match(node datamodel.Node) (bool, Statement) {
for _, stmt := range p { for _, stmt := range p {
res, _ := matchStatement(stmt, node) res, leaf := matchStatement(stmt, node)
switch res { switch res {
case matchResultNoData, matchResultFalse: case matchResultNoData, matchResultFalse:
return false return false, leaf
case matchResultOptionalNoData, matchResultTrue: case matchResultOptionalNoData, matchResultTrue:
// continue // continue
} }
} }
return true return true, nil
} }
// PartialMatch returns false IIF one non-optional Statement has the corresponding data and doesn't match. // PartialMatch returns false IIF one non-optional Statement has the corresponding data and doesn't match.
@@ -131,9 +132,9 @@ func matchStatement(cur Statement, node ipld.Node) (_ matchResult, leafMost Stat
case matchResultNoData, matchResultOptionalNoData: case matchResultNoData, matchResultOptionalNoData:
return res, leaf return res, leaf
case matchResultTrue: case matchResultTrue:
return matchResultFalse, leaf return matchResultFalse, cur
case matchResultFalse: case matchResultFalse:
return matchResultTrue, leaf return matchResultTrue, nil
} }
} }
case KindAnd: case KindAnd:

View File

@@ -17,228 +17,252 @@ import (
func TestMatch(t *testing.T) { func TestMatch(t *testing.T) {
t.Run("equality", func(t *testing.T) { t.Run("equality", func(t *testing.T) {
t.Run("string", func(t *testing.T) { t.Run("string", func(t *testing.T) {
np := basicnode.Prototype.String nd := literal.String("test")
nb := np.NewBuilder()
nb.AssignString("test")
nd := nb.Build()
pol := MustConstruct(Equal(".", literal.String("test"))) pol := MustConstruct(Equal(".", literal.String("test")))
ok := pol.Match(nd) ok, leaf := pol.Match(nd)
require.True(t, ok) require.True(t, ok)
require.Nil(t, leaf)
pol = MustConstruct(Equal(".", literal.String("test2"))) pol = MustConstruct(Equal(".", literal.String("test2")))
ok = pol.Match(nd) ok, leaf = pol.Match(nd)
require.False(t, ok) require.False(t, ok)
require.Equal(t, pol[0], leaf)
pol = MustConstruct(Equal(".", literal.Int(138))) pol = MustConstruct(Equal(".", literal.Int(138)))
ok = pol.Match(nd) ok, leaf = pol.Match(nd)
require.False(t, ok) require.False(t, ok)
require.Equal(t, pol[0], leaf)
}) })
t.Run("int", func(t *testing.T) { t.Run("int", func(t *testing.T) {
np := basicnode.Prototype.Int nd := literal.Int(138)
nb := np.NewBuilder()
nb.AssignInt(138)
nd := nb.Build()
pol := MustConstruct(Equal(".", literal.Int(138))) pol := MustConstruct(Equal(".", literal.Int(138)))
ok := pol.Match(nd) ok, leaf := pol.Match(nd)
require.True(t, ok) require.True(t, ok)
require.Nil(t, leaf)
pol = MustConstruct(Equal(".", literal.Int(1138))) pol = MustConstruct(Equal(".", literal.Int(1138)))
ok = pol.Match(nd) ok, leaf = pol.Match(nd)
require.False(t, ok) require.False(t, ok)
require.Equal(t, pol[0], leaf)
pol = MustConstruct(Equal(".", literal.String("138"))) pol = MustConstruct(Equal(".", literal.String("138")))
ok = pol.Match(nd) ok, leaf = pol.Match(nd)
require.False(t, ok) require.False(t, ok)
require.Equal(t, pol[0], leaf)
}) })
t.Run("float", func(t *testing.T) { t.Run("float", func(t *testing.T) {
np := basicnode.Prototype.Float nd := literal.Float(1.138)
nb := np.NewBuilder()
nb.AssignFloat(1.138)
nd := nb.Build()
pol := MustConstruct(Equal(".", literal.Float(1.138))) pol := MustConstruct(Equal(".", literal.Float(1.138)))
ok := pol.Match(nd) ok, leaf := pol.Match(nd)
require.True(t, ok) require.True(t, ok)
require.Nil(t, leaf)
pol = MustConstruct(Equal(".", literal.Float(11.38))) pol = MustConstruct(Equal(".", literal.Float(11.38)))
ok = pol.Match(nd) ok, leaf = pol.Match(nd)
require.False(t, ok) require.False(t, ok)
require.Equal(t, pol[0], leaf)
pol = MustConstruct(Equal(".", literal.String("138"))) pol = MustConstruct(Equal(".", literal.String("138")))
ok = pol.Match(nd) ok, leaf = pol.Match(nd)
require.False(t, ok) require.False(t, ok)
require.Equal(t, pol[0], leaf)
}) })
t.Run("IPLD Link", func(t *testing.T) { t.Run("IPLD Link", func(t *testing.T) {
l0 := cidlink.Link{Cid: cid.MustParse("bafybeif4owy5gno5lwnixqm52rwqfodklf76hsetxdhffuxnplvijskzqq")} l0 := cidlink.Link{Cid: cid.MustParse("bafybeif4owy5gno5lwnixqm52rwqfodklf76hsetxdhffuxnplvijskzqq")}
l1 := cidlink.Link{Cid: cid.MustParse("bafkreifau35r7vi37tvbvfy3hdwvgb4tlflqf7zcdzeujqcjk3rsphiwte")} l1 := cidlink.Link{Cid: cid.MustParse("bafkreifau35r7vi37tvbvfy3hdwvgb4tlflqf7zcdzeujqcjk3rsphiwte")}
np := basicnode.Prototype.Link nd := literal.Link(l0)
nb := np.NewBuilder()
nb.AssignLink(l0)
nd := nb.Build()
pol := MustConstruct(Equal(".", literal.Link(l0))) pol := MustConstruct(Equal(".", literal.Link(l0)))
ok := pol.Match(nd) ok, leaf := pol.Match(nd)
require.True(t, ok) require.True(t, ok)
require.Nil(t, leaf)
pol = MustConstruct(Equal(".", literal.Link(l1))) pol = MustConstruct(Equal(".", literal.Link(l1)))
ok = pol.Match(nd) ok, leaf = pol.Match(nd)
require.False(t, ok) require.False(t, ok)
require.Equal(t, pol[0], leaf)
pol = MustConstruct(Equal(".", literal.String("bafybeif4owy5gno5lwnixqm52rwqfodklf76hsetxdhffuxnplvijskzqq"))) pol = MustConstruct(Equal(".", literal.String("bafybeif4owy5gno5lwnixqm52rwqfodklf76hsetxdhffuxnplvijskzqq")))
ok = pol.Match(nd) ok, leaf = pol.Match(nd)
require.False(t, ok) require.False(t, ok)
require.Equal(t, pol[0], leaf)
}) })
t.Run("string in map", func(t *testing.T) { t.Run("string in map", func(t *testing.T) {
np := basicnode.Prototype.Map nd, _ := literal.Map(map[string]any{
nb := np.NewBuilder() "foo": "bar",
ma, _ := nb.BeginMap(1) })
ma.AssembleKey().AssignString("foo")
ma.AssembleValue().AssignString("bar")
ma.Finish()
nd := nb.Build()
pol := MustConstruct(Equal(".foo", literal.String("bar"))) pol := MustConstruct(Equal(".foo", literal.String("bar")))
ok := pol.Match(nd) ok, leaf := pol.Match(nd)
require.True(t, ok) require.True(t, ok)
require.Nil(t, leaf)
pol = MustConstruct(Equal(".[\"foo\"]", literal.String("bar"))) pol = MustConstruct(Equal(".[\"foo\"]", literal.String("bar")))
ok = pol.Match(nd) ok, leaf = pol.Match(nd)
require.True(t, ok) require.True(t, ok)
require.Nil(t, leaf)
pol = MustConstruct(Equal(".foo", literal.String("baz"))) pol = MustConstruct(Equal(".foo", literal.String("baz")))
ok = pol.Match(nd) ok, leaf = pol.Match(nd)
require.False(t, ok) require.False(t, ok)
require.Equal(t, pol[0], leaf)
pol = MustConstruct(Equal(".foobar", literal.String("bar"))) pol = MustConstruct(Equal(".foobar", literal.String("bar")))
ok = pol.Match(nd) ok, leaf = pol.Match(nd)
require.False(t, ok) require.False(t, ok)
require.Equal(t, pol[0], leaf)
}) })
t.Run("string in list", func(t *testing.T) { t.Run("string in list", func(t *testing.T) {
np := basicnode.Prototype.List nd, _ := literal.List([]any{"foo"})
nb := np.NewBuilder()
la, _ := nb.BeginList(1)
la.AssembleValue().AssignString("foo")
la.Finish()
nd := nb.Build()
pol := MustConstruct(Equal(".[0]", literal.String("foo"))) pol := MustConstruct(Equal(".[0]", literal.String("foo")))
ok := pol.Match(nd) ok, leaf := pol.Match(nd)
require.True(t, ok) require.True(t, ok)
require.Nil(t, leaf)
pol = MustConstruct(Equal(".[1]", literal.String("foo"))) pol = MustConstruct(Equal(".[1]", literal.String("foo")))
ok = pol.Match(nd) ok, leaf = pol.Match(nd)
require.False(t, ok) require.False(t, ok)
require.Equal(t, pol[0], leaf)
}) })
}) })
t.Run("inequality", func(t *testing.T) { t.Run("inequality", func(t *testing.T) {
t.Run("gt int", func(t *testing.T) { t.Run("gt int", func(t *testing.T) {
np := basicnode.Prototype.Int nd := literal.Int(138)
nb := np.NewBuilder()
nb.AssignInt(138)
nd := nb.Build()
pol := MustConstruct(GreaterThan(".", literal.Int(1))) pol := MustConstruct(GreaterThan(".", literal.Int(1)))
ok := pol.Match(nd) ok, leaf := pol.Match(nd)
require.True(t, ok) require.True(t, ok)
require.Nil(t, leaf)
pol = MustConstruct(GreaterThan(".", literal.Int(138)))
ok, leaf = pol.Match(nd)
require.False(t, ok)
require.Equal(t, pol[0], leaf)
pol = MustConstruct(GreaterThan(".", literal.Int(140)))
ok, leaf = pol.Match(nd)
require.False(t, ok)
require.Equal(t, pol[0], leaf)
}) })
t.Run("gte int", func(t *testing.T) { t.Run("gte int", func(t *testing.T) {
np := basicnode.Prototype.Int nd := literal.Int(138)
nb := np.NewBuilder()
nb.AssignInt(138)
nd := nb.Build()
pol := MustConstruct(GreaterThanOrEqual(".", literal.Int(1))) pol := MustConstruct(GreaterThanOrEqual(".", literal.Int(1)))
ok := pol.Match(nd) ok, leaf := pol.Match(nd)
require.True(t, ok) require.True(t, ok)
require.Nil(t, leaf)
pol = MustConstruct(GreaterThanOrEqual(".", literal.Int(138))) pol = MustConstruct(GreaterThanOrEqual(".", literal.Int(138)))
ok = pol.Match(nd) ok, leaf = pol.Match(nd)
require.True(t, ok) require.True(t, ok)
require.Nil(t, leaf)
pol = MustConstruct(GreaterThanOrEqual(".", literal.Int(140)))
ok, leaf = pol.Match(nd)
require.False(t, ok)
require.Equal(t, pol[0], leaf)
}) })
t.Run("gt float", func(t *testing.T) { t.Run("gt float", func(t *testing.T) {
np := basicnode.Prototype.Float nd := literal.Float(1.38)
nb := np.NewBuilder()
nb.AssignFloat(1.38)
nd := nb.Build()
pol := MustConstruct(GreaterThan(".", literal.Float(1))) pol := MustConstruct(GreaterThan(".", literal.Float(1)))
ok := pol.Match(nd) ok, leaf := pol.Match(nd)
require.True(t, ok) require.True(t, ok)
require.Nil(t, leaf)
pol = MustConstruct(GreaterThan(".", literal.Float(2)))
ok, leaf = pol.Match(nd)
require.False(t, ok)
require.Equal(t, pol[0], leaf)
}) })
t.Run("gte float", func(t *testing.T) { t.Run("gte float", func(t *testing.T) {
np := basicnode.Prototype.Float nd := literal.Float(1.38)
nb := np.NewBuilder()
nb.AssignFloat(1.38)
nd := nb.Build()
pol := MustConstruct(GreaterThanOrEqual(".", literal.Float(1))) pol := MustConstruct(GreaterThanOrEqual(".", literal.Float(1)))
ok := pol.Match(nd) ok, leaf := pol.Match(nd)
require.True(t, ok) require.True(t, ok)
require.Nil(t, leaf)
pol = MustConstruct(GreaterThanOrEqual(".", literal.Float(1.38))) pol = MustConstruct(GreaterThanOrEqual(".", literal.Float(1.38)))
ok = pol.Match(nd) ok, leaf = pol.Match(nd)
require.True(t, ok) require.True(t, ok)
require.Nil(t, leaf)
pol = MustConstruct(GreaterThanOrEqual(".", literal.Float(2)))
ok, leaf = pol.Match(nd)
require.False(t, ok)
require.Equal(t, pol[0], leaf)
}) })
t.Run("lt int", func(t *testing.T) { t.Run("lt int", func(t *testing.T) {
np := basicnode.Prototype.Int nd := literal.Int(138)
nb := np.NewBuilder()
nb.AssignInt(138)
nd := nb.Build()
pol := MustConstruct(LessThan(".", literal.Int(1138))) pol := MustConstruct(LessThan(".", literal.Int(1138)))
ok := pol.Match(nd) ok, leaf := pol.Match(nd)
require.True(t, ok) require.True(t, ok)
require.Nil(t, leaf)
pol = MustConstruct(LessThan(".", literal.Int(138)))
ok, leaf = pol.Match(nd)
require.False(t, ok)
require.Equal(t, pol[0], leaf)
pol = MustConstruct(LessThan(".", literal.Int(100)))
ok, leaf = pol.Match(nd)
require.False(t, ok)
require.Equal(t, pol[0], leaf)
}) })
t.Run("lte int", func(t *testing.T) { t.Run("lte int", func(t *testing.T) {
np := basicnode.Prototype.Int nd := literal.Int(138)
nb := np.NewBuilder()
nb.AssignInt(138)
nd := nb.Build()
pol := MustConstruct(LessThanOrEqual(".", literal.Int(1138))) pol := MustConstruct(LessThanOrEqual(".", literal.Int(1138)))
ok := pol.Match(nd) ok, leaf := pol.Match(nd)
require.True(t, ok) require.True(t, ok)
require.Nil(t, leaf)
pol = MustConstruct(LessThanOrEqual(".", literal.Int(138))) pol = MustConstruct(LessThanOrEqual(".", literal.Int(138)))
ok = pol.Match(nd) ok, leaf = pol.Match(nd)
require.True(t, ok) require.True(t, ok)
require.Nil(t, leaf)
pol = MustConstruct(LessThanOrEqual(".", literal.Int(100)))
ok, leaf = pol.Match(nd)
require.False(t, ok)
require.Equal(t, pol[0], leaf)
}) })
}) })
t.Run("negation", func(t *testing.T) { t.Run("negation", func(t *testing.T) {
np := basicnode.Prototype.Bool nd := literal.Bool(false)
nb := np.NewBuilder()
nb.AssignBool(false)
nd := nb.Build()
pol := MustConstruct(Not(Equal(".", literal.Bool(true)))) pol := MustConstruct(Not(Equal(".", literal.Bool(true))))
ok := pol.Match(nd) ok, leaf := pol.Match(nd)
require.True(t, ok) require.True(t, ok)
require.Nil(t, leaf)
pol = MustConstruct(Not(Equal(".", literal.Bool(false)))) pol = MustConstruct(Not(Equal(".", literal.Bool(false))))
ok = pol.Match(nd) ok, leaf = pol.Match(nd)
require.False(t, ok) require.False(t, ok)
require.Equal(t, pol[0], leaf)
}) })
t.Run("conjunction", func(t *testing.T) { t.Run("conjunction", func(t *testing.T) {
np := basicnode.Prototype.Int nd := literal.Int(138)
nb := np.NewBuilder()
nb.AssignInt(138)
nd := nb.Build()
pol := MustConstruct( pol := MustConstruct(
And( And(
@@ -246,8 +270,9 @@ func TestMatch(t *testing.T) {
LessThan(".", literal.Int(1138)), LessThan(".", literal.Int(1138)),
), ),
) )
ok := pol.Match(nd) ok, leaf := pol.Match(nd)
require.True(t, ok) require.True(t, ok)
require.Nil(t, leaf)
pol = MustConstruct( pol = MustConstruct(
And( And(
@@ -255,19 +280,18 @@ func TestMatch(t *testing.T) {
Equal(".", literal.Int(1138)), Equal(".", literal.Int(1138)),
), ),
) )
ok = pol.Match(nd) ok, leaf = pol.Match(nd)
require.False(t, ok) require.False(t, ok)
require.Equal(t, MustConstruct(Equal(".", literal.Int(1138)))[0], leaf)
pol = MustConstruct(And()) pol = MustConstruct(And())
ok = pol.Match(nd) ok, leaf = pol.Match(nd)
require.True(t, ok) require.True(t, ok)
require.Nil(t, leaf)
}) })
t.Run("disjunction", func(t *testing.T) { t.Run("disjunction", func(t *testing.T) {
np := basicnode.Prototype.Int nd := literal.Int(138)
nb := np.NewBuilder()
nb.AssignInt(138)
nd := nb.Build()
pol := MustConstruct( pol := MustConstruct(
Or( Or(
@@ -275,8 +299,9 @@ func TestMatch(t *testing.T) {
LessThan(".", literal.Int(1138)), LessThan(".", literal.Int(1138)),
), ),
) )
ok := pol.Match(nd) ok, leaf := pol.Match(nd)
require.True(t, ok) require.True(t, ok)
require.Nil(t, leaf)
pol = MustConstruct( pol = MustConstruct(
Or( Or(
@@ -284,12 +309,14 @@ func TestMatch(t *testing.T) {
Equal(".", literal.Int(1138)), Equal(".", literal.Int(1138)),
), ),
) )
ok = pol.Match(nd) ok, leaf = pol.Match(nd)
require.False(t, ok) require.False(t, ok)
require.Equal(t, pol[0], leaf)
pol = MustConstruct(Or()) pol = MustConstruct(Or())
ok = pol.Match(nd) ok, leaf = pol.Match(nd)
require.True(t, ok) require.True(t, ok)
require.Nil(t, leaf)
}) })
t.Run("wildcard", func(t *testing.T) { t.Run("wildcard", func(t *testing.T) {
@@ -303,14 +330,12 @@ func TestMatch(t *testing.T) {
} { } {
func(s string) { func(s string) {
t.Run(fmt.Sprintf("pass %s", s), func(t *testing.T) { t.Run(fmt.Sprintf("pass %s", s), func(t *testing.T) {
np := basicnode.Prototype.String nd := literal.String(s)
nb := np.NewBuilder()
nb.AssignString(s)
nd := nb.Build()
pol := MustConstruct(Like(".", pattern)) pol := MustConstruct(Like(".", pattern))
ok := pol.Match(nd) ok, leaf := pol.Match(nd)
require.True(t, ok) require.True(t, ok)
require.Nil(t, leaf)
}) })
}(s) }(s)
} }
@@ -324,70 +349,56 @@ func TestMatch(t *testing.T) {
} { } {
func(s string) { func(s string) {
t.Run(fmt.Sprintf("fail %s", s), func(t *testing.T) { t.Run(fmt.Sprintf("fail %s", s), func(t *testing.T) {
np := basicnode.Prototype.String nd := literal.String(s)
nb := np.NewBuilder()
nb.AssignString(s)
nd := nb.Build()
pol := MustConstruct(Like(".", pattern)) pol := MustConstruct(Like(".", pattern))
ok := pol.Match(nd) ok, leaf := pol.Match(nd)
require.False(t, ok) require.False(t, ok)
require.Equal(t, pol[0], leaf)
}) })
}(s) }(s)
} }
}) })
t.Run("quantification", func(t *testing.T) { 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) { t.Run("all", func(t *testing.T) {
np := basicnode.Prototype.List nd, _ := literal.List([]any{
nb := np.NewBuilder() map[string]int{"value": 5},
la, _ := nb.BeginList(5) map[string]int{"value": 10},
la.AssembleValue().AssignNode(buildValueNode(5)) map[string]int{"value": 20},
la.AssembleValue().AssignNode(buildValueNode(10)) map[string]int{"value": 50},
la.AssembleValue().AssignNode(buildValueNode(20)) map[string]int{"value": 100},
la.AssembleValue().AssignNode(buildValueNode(50)) })
la.AssembleValue().AssignNode(buildValueNode(100))
la.Finish()
nd := nb.Build()
pol := MustConstruct(All(".[]", GreaterThan(".value", literal.Int(2)))) pol := MustConstruct(All(".[]", GreaterThan(".value", literal.Int(2))))
ok := pol.Match(nd) ok, leaf := pol.Match(nd)
require.True(t, ok) require.True(t, ok)
require.Nil(t, leaf)
pol = MustConstruct(All(".[]", GreaterThan(".value", literal.Int(20)))) pol = MustConstruct(All(".[]", GreaterThan(".value", literal.Int(20))))
ok = pol.Match(nd) ok, leaf = pol.Match(nd)
require.False(t, ok) require.False(t, ok)
require.Equal(t, MustConstruct(GreaterThan(".value", literal.Int(20)))[0], leaf)
}) })
t.Run("any", func(t *testing.T) { t.Run("any", func(t *testing.T) {
np := basicnode.Prototype.List nd, _ := literal.List([]any{
nb := np.NewBuilder() map[string]int{"value": 5},
la, _ := nb.BeginList(5) map[string]int{"value": 10},
la.AssembleValue().AssignNode(buildValueNode(5)) map[string]int{"value": 20},
la.AssembleValue().AssignNode(buildValueNode(10)) map[string]int{"value": 50},
la.AssembleValue().AssignNode(buildValueNode(20)) map[string]int{"value": 100},
la.AssembleValue().AssignNode(buildValueNode(50)) })
la.AssembleValue().AssignNode(buildValueNode(100))
la.Finish()
nd := nb.Build()
pol := MustConstruct(Any(".[]", GreaterThan(".value", literal.Int(60)))) pol := MustConstruct(Any(".[]", GreaterThan(".value", literal.Int(60))))
ok := pol.Match(nd) ok, leaf := pol.Match(nd)
require.True(t, ok) require.True(t, ok)
require.Nil(t, leaf)
pol = MustConstruct(Any(".[]", GreaterThan(".value", literal.Int(100)))) pol = MustConstruct(Any(".[]", GreaterThan(".value", literal.Int(100))))
ok = pol.Match(nd) ok, leaf = pol.Match(nd)
require.False(t, ok) require.False(t, ok)
require.Equal(t, pol[0], leaf)
}) })
}) })
} }
@@ -405,7 +416,8 @@ func TestPolicyExamples(t *testing.T) {
pol, err := FromDagJson(policy) pol, err := FromDagJson(policy)
require.NoError(t, err) require.NoError(t, err)
return pol.Match(data) res, _ := pol.Match(data)
return res
} }
t.Run("And", func(t *testing.T) { t.Run("And", func(t *testing.T) {
@@ -509,7 +521,7 @@ func FuzzMatch(f *testing.F) {
t.Skip() t.Skip()
} }
policy.Match(dataNode) _, _ = policy.Match(dataNode)
}) })
} }
@@ -584,7 +596,7 @@ func TestOptionalSelectors(t *testing.T) {
err = nb.AssignNode(n) err = nb.AssignNode(n)
require.NoError(t, err) require.NoError(t, err)
result := tt.policy.Match(nb.Build()) result, _ := tt.policy.Match(nb.Build())
require.Equal(t, tt.expected, result) require.Equal(t, tt.expected, result)
}) })
} }