Files
ucan/capability/policy/selector/selector.go

254 lines
5.8 KiB
Go
Raw Normal View History

2024-08-19 23:16:36 +02:00
package selector
import (
"fmt"
2024-08-20 15:55:04 +02:00
"regexp"
2024-08-19 23:16:36 +02:00
"strings"
2024-08-20 15:55:04 +02:00
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/datamodel"
2024-08-20 22:27:56 +02:00
"github.com/ipld/go-ipld-prime/schema"
2024-08-19 23:16:36 +02:00
)
// Selector describes a UCAN policy selector, as specified here:
// https://github.com/ucan-wg/delegation/blob/4094d5878b58f5d35055a3b93fccda0b8329ebae/README.md#selectors
type Selector []segment
2024-08-20 15:55:04 +02:00
func (s Selector) String() string {
var res strings.Builder
2024-08-20 15:55:04 +02:00
for _, seg := range s {
res.WriteString(seg.String())
2024-08-20 15:55:04 +02:00
}
return res.String()
2024-08-20 15:55:04 +02:00
}
2024-08-20 22:34:25 +02:00
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_]*?$`)
)
2024-08-20 15:55:04 +02:00
type segment struct {
2024-08-19 23:16:36 +02:00
str string
identity bool
optional bool
2024-08-20 15:55:04 +02:00
iterator bool
slice []int
2024-08-19 23:16:36 +02:00
field string
index int
}
// String returns the segment's string representation.
2024-08-20 15:55:04 +02:00
func (s segment) String() string {
return s.str
2024-08-19 23:16:36 +02:00
}
// Identity flags that this selector is the identity selector.
2024-08-20 15:55:04 +02:00
func (s segment) Identity() bool {
2024-08-19 23:16:36 +02:00
return s.identity
}
// Optional flags that this selector is optional.
2024-08-20 15:55:04 +02:00
func (s segment) Optional() bool {
return s.optional
2024-08-19 23:16:36 +02:00
}
// Iterator flags that this selector is an iterator segment.
2024-08-20 15:55:04 +02:00
func (s segment) Iterator() bool {
return s.iterator
2024-08-19 23:16:36 +02:00
}
// Slice flags that this segment targets a range of a slice.
2024-08-20 15:55:04 +02:00
func (s segment) Slice() []int {
return s.slice
2024-08-19 23:16:36 +02:00
}
// Field is the name of a field in a struct/map.
2024-08-20 15:55:04 +02:00
func (s segment) Field() string {
return s.field
}
2024-08-19 23:16:36 +02:00
// Index is an index of a slice.
2024-08-20 15:55:04 +02:00
func (s segment) Index() int {
return s.index
}
2024-08-19 23:16:36 +02:00
2024-08-20 22:27:56 +02:00
// Select uses a selector to extract an IPLD node or set of nodes from the
// passed subject node.
2024-08-20 15:55:04 +02:00
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
}
2024-08-20 22:27:56 +02:00
k, v, err := it.Next()
2024-08-20 15:55:04 +02:00
if err != nil {
return nil, nil, err
}
2024-08-20 22:27:56 +02:00
key := fmt.Sprintf("%d", k)
2024-08-20 15:55:04 +02:00
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)
2024-08-20 15:55:04 +02:00
}
} 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 {
2024-08-20 22:27:56 +02:00
if isMissing(err) {
2024-08-20 15:55:04 +02:00
if seg.Optional() {
cur = nil
} else {
return nil, nil, newResolutionError(fmt.Sprintf("object has no field named: %s", seg.Field()), at)
2024-08-20 15:55:04 +02:00
}
} 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)
2024-08-20 15:55:04 +02:00
}
} else if seg.Slice() != nil {
if cur != nil && cur.Kind() == datamodel.Kind_List {
return nil, nil, newResolutionError("list slice selection not yet implemented", at)
2024-08-20 15:55:04 +02:00
} else if cur != nil && cur.Kind() == datamodel.Kind_Bytes {
return nil, nil, newResolutionError("bytes slice selection not yet implemented", at)
2024-08-20 15:55:04 +02:00
} 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)
2024-08-20 15:55:04 +02:00
}
} else {
at = append(at, fmt.Sprintf("%d", seg.Index()))
if cur != nil && cur.Kind() == datamodel.Kind_List {
idx := int64(seg.Index())
if idx < 0 {
idx = cur.Length() + idx
}
n, err := cur.LookupByIndex(idx)
2024-08-20 15:55:04 +02:00
if err != nil {
2024-08-20 22:27:56 +02:00
if isMissing(err) {
2024-08-20 15:55:04 +02:00
if seg.Optional() {
cur = nil
} else {
return nil, nil, newResolutionError(fmt.Sprintf("index out of bounds: %d", seg.Index()), at)
2024-08-20 15:55:04 +02:00
}
} 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)
2024-08-20 15:55:04 +02:00
}
}
}
return cur, nil, nil
}
func kindString(n datamodel.Node) string {
if n == nil {
return "null"
}
return n.Kind().String()
}
2024-08-20 22:27:56 +02:00
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
}
2024-08-20 15:55:04 +02:00
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 {
2024-08-20 15:55:04 +02:00
return resolutionerr{message, at}
}