-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
5 changed files
with
377 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,252 @@ | ||
package policy | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/ipld/go-ipld-prime/datamodel" | ||
"github.com/ipld/go-ipld-prime/must" | ||
"github.com/ipld/go-ipld-prime/node/basicnode" | ||
|
||
"github.com/ucan-wg/go-ucan/v1/capability/policy/selector" | ||
) | ||
|
||
func PolicyFromIPLD(node datamodel.Node) (Policy, error) { | ||
return statementsFromIPLD("/", node) | ||
} | ||
|
||
func statementFromIPLD(path string, node datamodel.Node) (Statement, error) { | ||
// sanity checks | ||
if node.Kind() != datamodel.Kind_List { | ||
return nil, ErrNotATuple(path) | ||
} | ||
if node.Length() != 2 && node.Length() != 3 { | ||
return nil, ErrUnrecognizedShape(path) | ||
} | ||
|
||
// extract operator | ||
opNode, _ := node.LookupByIndex(0) | ||
if opNode.Kind() != datamodel.Kind_String { | ||
return nil, ErrNotAString(path) | ||
} | ||
op := must.String(opNode) | ||
|
||
arg2AsSelector := func() (selector.Selector, error) { | ||
nd, _ := node.LookupByIndex(1) | ||
if nd.Kind() != datamodel.Kind_String { | ||
return nil, ErrNotAString(path + "1/") | ||
} | ||
sel, err := selector.Parse(must.String(nd)) | ||
if err != nil { | ||
return nil, ErrInvalidSelector(path+"1/", err) | ||
} | ||
return sel, nil | ||
} | ||
|
||
switch node.Length() { | ||
case 2: | ||
switch op { | ||
case KindNot: | ||
arg2, _ := node.LookupByIndex(1) | ||
statement, err := statementFromIPLD(path+"1/", arg2) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return Not(statement), nil | ||
|
||
case KindAnd, KindOr: | ||
arg2, _ := node.LookupByIndex(1) | ||
statement, err := statementsFromIPLD(path+"1/", arg2) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return connective{kind: op, statements: statement}, nil | ||
|
||
default: | ||
return nil, ErrUnrecognizedOperator(path, op) | ||
} | ||
case 3: | ||
switch op { | ||
case KindEqual, KindLessThan, KindLessThanOrEqual, KindGreaterThan, KindGreaterThanOrEqual: | ||
sel, err := arg2AsSelector() | ||
if err != nil { | ||
return nil, err | ||
} | ||
arg3, _ := node.LookupByIndex(2) | ||
return equality{kind: op, selector: sel, value: arg3}, nil | ||
|
||
case KindLike: | ||
sel, err := arg2AsSelector() | ||
if err != nil { | ||
return nil, err | ||
} | ||
pattern, _ := node.LookupByIndex(2) | ||
if pattern.Kind() != datamodel.Kind_String { | ||
return nil, ErrNotAString(path + "2/") | ||
} | ||
res, err := Like(sel, must.String(pattern)) | ||
if err != nil { | ||
return nil, ErrInvalidPattern(path+"2/", err) | ||
} | ||
return res, nil | ||
|
||
case KindAll, KindAny: | ||
sel, err := arg2AsSelector() | ||
if err != nil { | ||
return nil, err | ||
} | ||
statementsNodes, _ := node.LookupByIndex(2) | ||
statements, err := statementsFromIPLD(path+"1/", statementsNodes) | ||
return quantifier{kind: op, selector: sel, statements: statements}, nil | ||
|
||
default: | ||
return nil, ErrUnrecognizedOperator(path, op) | ||
} | ||
|
||
default: | ||
return nil, ErrUnrecognizedShape(path) | ||
} | ||
} | ||
|
||
func statementsFromIPLD(path string, node datamodel.Node) ([]Statement, error) { | ||
// sanity checks | ||
if node.Kind() != datamodel.Kind_List { | ||
return nil, ErrNotATuple(path) | ||
} | ||
if node.Length() == 0 { | ||
return nil, ErrEmptyList(path) | ||
} | ||
|
||
res := make([]Statement, node.Length()) | ||
|
||
for i := int64(0); i < node.Length(); i++ { | ||
nd, _ := node.LookupByIndex(i) | ||
statement, err := statementFromIPLD(fmt.Sprintf("%s%d/", path, i), nd) | ||
if err != nil { | ||
return nil, err | ||
} | ||
res[i] = statement | ||
} | ||
|
||
return res, nil | ||
} | ||
|
||
func (p Policy) ToIPLD() (datamodel.Node, error) { | ||
return statementsToIPLD(p) | ||
} | ||
|
||
func statementsToIPLD(statements []Statement) (datamodel.Node, error) { | ||
list := basicnode.Prototype.List.NewBuilder() | ||
// can't error, we have the right builder. | ||
listBuilder, _ := list.BeginList(int64(len(statements))) | ||
for _, argStatement := range statements { | ||
node, err := statementToIPLD(argStatement) | ||
if err != nil { | ||
return nil, err | ||
} | ||
err = listBuilder.AssembleValue().AssignNode(node) | ||
if err != nil { | ||
return nil, err | ||
} | ||
} | ||
err := listBuilder.Finish() | ||
if err != nil { | ||
return nil, err | ||
} | ||
return list.Build(), nil | ||
} | ||
|
||
func statementToIPLD(statement Statement) (datamodel.Node, error) { | ||
list := basicnode.Prototype.List.NewBuilder() | ||
|
||
length := int64(3) | ||
switch statement.(type) { | ||
case negation, connective: | ||
length = 2 | ||
} | ||
|
||
// can't error, we have the right builder. | ||
listBuilder, _ := list.BeginList(length) | ||
|
||
switch statement := statement.(type) { | ||
case equality: | ||
err := listBuilder.AssembleValue().AssignString(statement.kind) | ||
if err != nil { | ||
return nil, err | ||
} | ||
err = listBuilder.AssembleValue().AssignString(statement.selector.String()) | ||
if err != nil { | ||
return nil, err | ||
} | ||
err = listBuilder.AssembleValue().AssignNode(statement.value) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
case negation: | ||
err := listBuilder.AssembleValue().AssignString(statement.Kind()) | ||
if err != nil { | ||
return nil, err | ||
} | ||
node, err := statementToIPLD(statement.statement) | ||
if err != nil { | ||
return nil, err | ||
} | ||
err = listBuilder.AssembleValue().AssignNode(node) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
case connective: | ||
err := listBuilder.AssembleValue().AssignString(statement.kind) | ||
if err != nil { | ||
return nil, err | ||
} | ||
args, err := statementsToIPLD(statement.statements) | ||
if err != nil { | ||
return nil, err | ||
} | ||
err = listBuilder.AssembleValue().AssignNode(args) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
case wildcard: | ||
err := listBuilder.AssembleValue().AssignString(statement.Kind()) | ||
if err != nil { | ||
return nil, err | ||
} | ||
err = listBuilder.AssembleValue().AssignString(statement.selector.String()) | ||
if err != nil { | ||
return nil, err | ||
} | ||
err = listBuilder.AssembleValue().AssignString(statement.pattern) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
case quantifier: | ||
err := listBuilder.AssembleValue().AssignString(statement.kind) | ||
if err != nil { | ||
return nil, err | ||
} | ||
err = listBuilder.AssembleValue().AssignString(statement.selector.String()) | ||
if err != nil { | ||
return nil, err | ||
} | ||
args, err := statementsToIPLD(statement.statements) | ||
if err != nil { | ||
return nil, err | ||
} | ||
err = listBuilder.AssembleValue().AssignNode(args) | ||
if err != nil { | ||
return nil, err | ||
} | ||
} | ||
|
||
err := listBuilder.Finish() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return list.Build(), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package policy | ||
|
||
import "fmt" | ||
|
||
type errWithPath struct { | ||
path string | ||
msg string | ||
} | ||
|
||
func (e errWithPath) Error() string { | ||
return fmt.Sprintf("IPLD path '%s': %s", e.path, e.msg) | ||
} | ||
|
||
func ErrInvalidSelector(path string, err error) error { | ||
return errWithPath{path: path, msg: fmt.Sprintf("invalid selector: %s", err)} | ||
} | ||
|
||
func ErrInvalidPattern(path string, err error) error { | ||
return errWithPath{path: path, msg: fmt.Sprintf("invalid pattern: %s", err)} | ||
} | ||
|
||
func ErrNotAString(path string) error { | ||
return errWithPath{path: path, msg: ""} | ||
} | ||
|
||
func ErrUnrecognizedOperator(path string, op string) error { | ||
return errWithPath{path: path, msg: fmt.Sprintf("unrecognized operator '%s'", safeStr(op))} | ||
} | ||
|
||
func ErrUnrecognizedShape(path string) error { | ||
return errWithPath{path: path, msg: "unrecognized shape"} | ||
} | ||
|
||
func ErrNotATuple(path string) error { | ||
return errWithPath{path: path, msg: "not a tuple"} | ||
} | ||
|
||
func ErrEmptyList(path string) error { | ||
return errWithPath{path: path, msg: "empty list"} | ||
} | ||
|
||
func safeStr(str string) string { | ||
if len(str) > 10 { | ||
return str[:10] | ||
} | ||
return str | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package policy | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/ipld/go-ipld-prime" | ||
"github.com/ipld/go-ipld-prime/codec/dagjson" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestIpldRoundTrip(t *testing.T) { | ||
const illustrativeExample = `[ | ||
["==", ".status", "draft"], | ||
["all", ".reviewer", [["like", ".email", "*@example.com"]]], | ||
["any", ".tags", | ||
["or", [ | ||
["==", ".", "news"], | ||
["==", ".", "press"]] | ||
]] | ||
]` | ||
|
||
for _, tc := range []struct { | ||
name, dagjson string | ||
}{ | ||
{"illustrativeExample", illustrativeExample}, | ||
} { | ||
// strip all spaces and carriage return | ||
asDagJson := strings.Join(strings.Fields(tc.dagjson), "") | ||
|
||
nodes, err := ipld.Decode([]byte(asDagJson), dagjson.Decode) | ||
require.NoError(t, err) | ||
|
||
pol, err := PolicyFromIPLD(nodes) | ||
require.NoError(t, err) | ||
|
||
fmt.Println(pol) | ||
|
||
wroteIpld, err := pol.ToIPLD() | ||
require.NoError(t, err) | ||
|
||
wroteAsDagJson, err := ipld.Encode(wroteIpld, dagjson.Encode) | ||
require.NoError(t, err) | ||
|
||
require.Equal(t, asDagJson, string(wroteAsDagJson)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.