2024-08-19 23:16:36 +02:00
|
|
|
package selector
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
2024-10-24 12:44:25 +02:00
|
|
|
"math"
|
2024-10-22 12:42:15 +02:00
|
|
|
"strconv"
|
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-10-22 12:42:15 +02:00
|
|
|
"github.com/ipld/go-ipld-prime/fluent/qp"
|
2024-09-12 18:19:50 +02:00
|
|
|
"github.com/ipld/go-ipld-prime/node/basicnode"
|
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
|
2024-08-31 15:48:24 +02:00
|
|
|
type Selector []segment
|
2024-08-20 15:55:04 +02:00
|
|
|
|
2024-10-15 17:26:49 +02:00
|
|
|
// Select perform the selection described by the selector on the input IPLD DAG.
|
2024-10-22 12:42:15 +02:00
|
|
|
// Select can return:
|
|
|
|
|
// - exactly one matched IPLD node
|
2024-10-22 16:27:01 +02:00
|
|
|
// - a resolutionerr error if not being able to resolve to a node
|
|
|
|
|
// - nil and no errors, if the selector couldn't match on an optional segment (with ?).
|
2024-10-22 12:42:15 +02:00
|
|
|
func (s Selector) Select(subject ipld.Node) (ipld.Node, error) {
|
2024-10-15 17:26:49 +02:00
|
|
|
return resolve(s, subject, nil)
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-20 15:55:04 +02:00
|
|
|
func (s Selector) String() string {
|
2024-09-01 17:22:21 +02:00
|
|
|
var res strings.Builder
|
2024-08-20 15:55:04 +02:00
|
|
|
for _, seg := range s {
|
2024-09-01 17:22:21 +02:00
|
|
|
res.WriteString(seg.String())
|
2024-08-20 15:55:04 +02:00
|
|
|
}
|
2024-09-01 17:22:21 +02:00
|
|
|
return res.String()
|
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
|
2024-10-22 12:42:15 +02:00
|
|
|
slice []int64 // either 0-length or 2-length
|
2024-08-19 23:16:36 +02:00
|
|
|
field string
|
|
|
|
|
index int
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-31 15:48:24 +02:00
|
|
|
// 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
|
|
|
}
|
|
|
|
|
|
2024-08-31 15:48:24 +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
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-31 15:48:24 +02:00
|
|
|
// 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
|
|
|
}
|
|
|
|
|
|
2024-08-31 15:48:24 +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
|
|
|
}
|
|
|
|
|
|
2024-08-31 15:48:24 +02:00
|
|
|
// Slice flags that this segment targets a range of a slice.
|
2024-10-22 12:42:15 +02:00
|
|
|
func (s segment) Slice() []int64 {
|
2024-08-20 15:55:04 +02:00
|
|
|
return s.slice
|
2024-08-19 23:16:36 +02:00
|
|
|
}
|
|
|
|
|
|
2024-08-31 15:48:24 +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
|
|
|
|
2024-08-31 15:48:24 +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-10-22 12:42:15 +02:00
|
|
|
func resolve(sel Selector, subject ipld.Node, at []string) (ipld.Node, error) {
|
|
|
|
|
errIfNotOptional := func(s segment, err error) error {
|
|
|
|
|
if !s.Optional() {
|
|
|
|
|
return err
|
2024-09-13 14:44:26 +02:00
|
|
|
}
|
2024-10-22 12:42:15 +02:00
|
|
|
return nil
|
|
|
|
|
}
|
2024-08-20 15:55:04 +02:00
|
|
|
|
2024-10-22 12:42:15 +02:00
|
|
|
cur := subject
|
|
|
|
|
for _, seg := range sel {
|
2024-09-13 14:44:26 +02:00
|
|
|
// 1st level: handle the different segment types (iterator, field, slice, index)
|
|
|
|
|
// 2nd level: handle different node kinds (list, map, string, bytes)
|
|
|
|
|
switch {
|
2024-10-22 12:42:15 +02:00
|
|
|
case seg.Identity():
|
|
|
|
|
continue
|
|
|
|
|
|
2024-09-13 14:44:26 +02:00
|
|
|
case seg.Iterator():
|
2024-10-22 12:42:15 +02:00
|
|
|
switch {
|
|
|
|
|
case cur == nil || cur.Kind() == datamodel.Kind_Null:
|
2024-09-13 14:44:26 +02:00
|
|
|
if seg.Optional() {
|
2024-10-22 12:42:15 +02:00
|
|
|
// build an empty list
|
2024-10-22 16:27:01 +02:00
|
|
|
n, _ := qp.BuildList(basicnode.Prototype.Any, 0, func(_ datamodel.ListAssembler) {})
|
|
|
|
|
return n, nil
|
2024-08-20 15:55:04 +02:00
|
|
|
}
|
2024-10-22 12:42:15 +02:00
|
|
|
return nil, newResolutionError(fmt.Sprintf("can not iterate over kind: %s", kindString(cur)), at)
|
2024-08-20 15:55:04 +02:00
|
|
|
|
2024-10-22 12:42:15 +02:00
|
|
|
case cur.Kind() == datamodel.Kind_List:
|
|
|
|
|
// iterators are no-op on list
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
case cur.Kind() == datamodel.Kind_Map:
|
|
|
|
|
// iterators on maps collect the values
|
2024-10-22 16:27:01 +02:00
|
|
|
nd, err := qp.BuildList(basicnode.Prototype.Any, cur.Length(), func(l datamodel.ListAssembler) {
|
2024-09-13 14:44:26 +02:00
|
|
|
it := cur.MapIterator()
|
|
|
|
|
for !it.Done() {
|
2024-09-13 16:26:46 +02:00
|
|
|
_, v, err := it.Next()
|
2024-09-13 14:44:26 +02:00
|
|
|
if err != nil {
|
2024-10-22 16:27:01 +02:00
|
|
|
// recovered by BuildList
|
|
|
|
|
// Error is bubbled up, but should never occur as we already checked the type,
|
|
|
|
|
// and are using the iterator correctly.
|
|
|
|
|
// This is verified with fuzzing.
|
|
|
|
|
panic(err)
|
2024-09-13 14:44:26 +02:00
|
|
|
}
|
2024-10-22 12:42:15 +02:00
|
|
|
qp.ListEntry(l, qp.Node(v))
|
2024-08-20 15:55:04 +02:00
|
|
|
}
|
2024-10-22 12:42:15 +02:00
|
|
|
})
|
2024-10-22 16:27:01 +02:00
|
|
|
if err != nil {
|
|
|
|
|
panic("should never happen")
|
|
|
|
|
}
|
|
|
|
|
return nd, nil
|
2024-09-13 14:44:26 +02:00
|
|
|
|
2024-10-22 12:42:15 +02:00
|
|
|
default:
|
|
|
|
|
return nil, newResolutionError(fmt.Sprintf("can not iterate over kind: %s", kindString(cur)), at)
|
2024-08-20 15:55:04 +02:00
|
|
|
}
|
|
|
|
|
|
2024-09-13 14:44:26 +02:00
|
|
|
case seg.Field() != "":
|
2024-08-20 15:55:04 +02:00
|
|
|
at = append(at, seg.Field())
|
2024-10-22 12:42:15 +02:00
|
|
|
switch {
|
|
|
|
|
case cur == nil:
|
|
|
|
|
err := newResolutionError(fmt.Sprintf("can not access field: %s on kind: %s", seg.Field(), kindString(cur)), at)
|
|
|
|
|
return nil, errIfNotOptional(seg, err)
|
|
|
|
|
|
|
|
|
|
case cur.Kind() == datamodel.Kind_Map:
|
|
|
|
|
n, err := cur.LookupByString(seg.Field())
|
|
|
|
|
if err != nil {
|
2024-10-22 16:27:01 +02:00
|
|
|
// the only possible error is missing field as we already check the type
|
2024-09-16 13:00:13 +02:00
|
|
|
if seg.Optional() {
|
|
|
|
|
cur = nil
|
|
|
|
|
} else {
|
2024-10-22 12:42:15 +02:00
|
|
|
return nil, newResolutionError(fmt.Sprintf("object has no field named: %s", seg.Field()), at)
|
2024-08-20 15:55:04 +02:00
|
|
|
}
|
2024-10-22 12:42:15 +02:00
|
|
|
} else {
|
|
|
|
|
cur = n
|
2024-08-20 15:55:04 +02:00
|
|
|
}
|
2024-10-22 12:42:15 +02:00
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
err := newResolutionError(fmt.Sprintf("can not access field: %s on kind: %s", seg.Field(), kindString(cur)), at)
|
|
|
|
|
return nil, errIfNotOptional(seg, err)
|
2024-08-20 15:55:04 +02:00
|
|
|
}
|
2024-09-13 14:18:59 +02:00
|
|
|
|
2024-10-22 12:42:15 +02:00
|
|
|
case len(seg.Slice()) > 0:
|
2024-09-13 14:44:26 +02:00
|
|
|
if cur == nil {
|
2024-10-22 12:42:15 +02:00
|
|
|
err := newResolutionError(fmt.Sprintf("can not slice on kind: %s", kindString(cur)), at)
|
|
|
|
|
return nil, errIfNotOptional(seg, err)
|
|
|
|
|
}
|
2024-09-13 14:18:59 +02:00
|
|
|
|
2024-10-22 12:42:15 +02:00
|
|
|
slice := seg.Slice()
|
|
|
|
|
|
|
|
|
|
switch cur.Kind() {
|
|
|
|
|
case datamodel.Kind_List:
|
2024-10-23 12:18:03 +02:00
|
|
|
start, end := resolveSliceIndices(slice, cur.Length())
|
2024-10-22 12:42:15 +02:00
|
|
|
sliced, err := qp.BuildList(basicnode.Prototype.Any, end-start, func(l datamodel.ListAssembler) {
|
2024-10-23 12:18:03 +02:00
|
|
|
for i := start; i < end; i++ {
|
2024-10-22 12:42:15 +02:00
|
|
|
item, err := cur.LookupByIndex(i)
|
|
|
|
|
if err != nil {
|
2024-10-22 16:27:01 +02:00
|
|
|
// recovered by BuildList
|
|
|
|
|
// Error is bubbled up, but should never occur as we already checked the type and boundaries
|
|
|
|
|
// This is verified with fuzzing.
|
|
|
|
|
panic(err)
|
2024-09-13 14:49:52 +02:00
|
|
|
}
|
2024-10-22 12:42:15 +02:00
|
|
|
qp.ListEntry(l, qp.Node(item))
|
2024-09-13 14:18:59 +02:00
|
|
|
}
|
2024-10-22 12:42:15 +02:00
|
|
|
})
|
|
|
|
|
if err != nil {
|
2024-10-22 16:27:01 +02:00
|
|
|
panic("should never happen")
|
2024-09-13 14:18:59 +02:00
|
|
|
}
|
2024-10-22 12:42:15 +02:00
|
|
|
cur = sliced
|
2024-10-23 12:18:03 +02:00
|
|
|
|
2024-10-22 12:42:15 +02:00
|
|
|
case datamodel.Kind_Bytes:
|
|
|
|
|
b, _ := cur.AsBytes()
|
2024-10-23 12:18:03 +02:00
|
|
|
start, end := resolveSliceIndices(slice, int64(len(b)))
|
2024-10-22 12:42:15 +02:00
|
|
|
cur = basicnode.NewBytes(b[start:end])
|
2024-10-23 12:18:03 +02:00
|
|
|
|
|
|
|
|
case datamodel.Kind_String:
|
|
|
|
|
str, _ := cur.AsString()
|
|
|
|
|
runes := []rune(str)
|
|
|
|
|
start, end := resolveSliceIndices(slice, int64(len(runes)))
|
|
|
|
|
cur = basicnode.NewString(string(runes[start:end]))
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
return nil, newResolutionError(fmt.Sprintf("can not slice on kind: %s", kindString(cur)), at)
|
2024-08-20 15:55:04 +02:00
|
|
|
}
|
2024-09-13 14:44:26 +02:00
|
|
|
|
2024-10-16 11:18:02 +02:00
|
|
|
default: // Index()
|
2024-10-22 12:42:15 +02:00
|
|
|
at = append(at, strconv.Itoa(seg.Index()))
|
|
|
|
|
|
2024-09-13 14:44:26 +02:00
|
|
|
if cur == nil {
|
2024-10-22 12:42:15 +02:00
|
|
|
err := newResolutionError(fmt.Sprintf("can not access index: %d on kind: %s", seg.Index(), kindString(cur)), at)
|
|
|
|
|
return nil, errIfNotOptional(seg, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
idx := seg.Index()
|
|
|
|
|
switch cur.Kind() {
|
|
|
|
|
case datamodel.Kind_List:
|
|
|
|
|
if idx < 0 {
|
|
|
|
|
idx = int(cur.Length()) + idx
|
2024-09-02 18:08:49 +02:00
|
|
|
}
|
2024-10-22 12:42:15 +02:00
|
|
|
if idx < 0 || idx >= int(cur.Length()) {
|
|
|
|
|
err := newResolutionError(fmt.Sprintf("index out of bounds: %d", seg.Index()), at)
|
|
|
|
|
return nil, errIfNotOptional(seg, err)
|
|
|
|
|
}
|
|
|
|
|
cur, _ = cur.LookupByIndex(int64(idx))
|
|
|
|
|
|
|
|
|
|
case datamodel.Kind_Bytes:
|
|
|
|
|
b, _ := cur.AsBytes()
|
|
|
|
|
if idx < 0 {
|
|
|
|
|
idx = len(b) + idx
|
2024-09-13 13:06:49 +02:00
|
|
|
}
|
2024-10-22 12:42:15 +02:00
|
|
|
if idx < 0 || idx >= len(b) {
|
2024-10-24 12:27:01 +02:00
|
|
|
err := newResolutionError(fmt.Sprintf("index %d out of bounds for bytes of length %d", seg.Index(), len(b)), at)
|
2024-10-22 12:42:15 +02:00
|
|
|
return nil, errIfNotOptional(seg, err)
|
|
|
|
|
}
|
|
|
|
|
cur = basicnode.NewInt(int64(b[idx]))
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
return nil, newResolutionError(fmt.Sprintf("can not access index: %d on kind: %s", seg.Index(), kindString(cur)), at)
|
2024-08-20 15:55:04 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-22 12:42:15 +02:00
|
|
|
// segment exhausted, we return where we are
|
|
|
|
|
return cur, nil
|
2024-08-20 15:55:04 +02:00
|
|
|
}
|
|
|
|
|
|
2024-09-13 14:44:26 +02:00
|
|
|
// resolveSliceIndices resolves the start and end indices for slicing a list or byte array.
|
|
|
|
|
//
|
|
|
|
|
// It takes the slice indices from the selector segment and the length of the list or byte array,
|
|
|
|
|
// and returns the resolved start and end indices. Negative indices are supported.
|
|
|
|
|
//
|
|
|
|
|
// Parameters:
|
|
|
|
|
// - slice: The slice indices from the selector segment.
|
|
|
|
|
// - length: The length of the list or byte array being sliced.
|
|
|
|
|
//
|
|
|
|
|
// Returns:
|
|
|
|
|
// - start: The resolved start index for slicing.
|
2024-10-23 12:18:03 +02:00
|
|
|
// - end: The resolved **excluded** end index for slicing.
|
2024-10-22 12:42:15 +02:00
|
|
|
func resolveSliceIndices(slice []int64, length int64) (start int64, end int64) {
|
|
|
|
|
if len(slice) != 2 {
|
|
|
|
|
panic("should always be 2-length")
|
2024-09-13 14:44:26 +02:00
|
|
|
}
|
2024-10-22 12:42:15 +02:00
|
|
|
|
|
|
|
|
start, end = slice[0], slice[1]
|
|
|
|
|
|
2024-10-24 12:44:25 +02:00
|
|
|
// adjust boundaries
|
|
|
|
|
switch {
|
|
|
|
|
case slice[0] == math.MinInt:
|
|
|
|
|
start = 0
|
|
|
|
|
case slice[0] < 0:
|
2024-11-29 13:00:00 +01:00
|
|
|
// Check for potential overflow before adding
|
|
|
|
|
if -slice[0] > length {
|
|
|
|
|
start = 0
|
|
|
|
|
} else {
|
|
|
|
|
start = length + slice[0]
|
|
|
|
|
}
|
2024-10-24 12:44:25 +02:00
|
|
|
}
|
2024-11-29 13:00:00 +01:00
|
|
|
|
2024-10-24 12:44:25 +02:00
|
|
|
switch {
|
|
|
|
|
case slice[1] == math.MaxInt:
|
|
|
|
|
end = length
|
|
|
|
|
case slice[1] < 0:
|
2024-11-29 13:00:00 +01:00
|
|
|
// Check for potential overflow before adding
|
|
|
|
|
if -slice[1] > length {
|
|
|
|
|
end = 0
|
|
|
|
|
} else {
|
|
|
|
|
end = length + slice[1]
|
|
|
|
|
}
|
2024-10-24 12:44:25 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// backward iteration is not allowed, shortcut to an empty result
|
|
|
|
|
if start >= end {
|
|
|
|
|
start, end = 0, 0
|
2024-11-29 13:00:00 +01:00
|
|
|
return
|
2024-10-24 12:44:25 +02:00
|
|
|
}
|
2024-11-29 13:00:00 +01:00
|
|
|
|
2024-10-24 12:44:25 +02:00
|
|
|
// clamp out of bound
|
2024-10-24 12:27:01 +02:00
|
|
|
if start < 0 {
|
2024-10-24 12:44:25 +02:00
|
|
|
start = 0
|
2024-10-22 12:42:15 +02:00
|
|
|
}
|
2024-10-24 12:27:01 +02:00
|
|
|
if start > length {
|
|
|
|
|
start = length
|
2024-09-13 14:44:26 +02:00
|
|
|
}
|
2024-11-29 13:00:00 +01:00
|
|
|
if end < 0 {
|
|
|
|
|
end = 0
|
|
|
|
|
}
|
2024-10-23 12:18:03 +02:00
|
|
|
if end > length {
|
|
|
|
|
end = length
|
|
|
|
|
}
|
2024-10-22 12:42:15 +02:00
|
|
|
|
2024-11-29 13:00:00 +01:00
|
|
|
return
|
2024-09-13 14:44:26 +02:00
|
|
|
}
|
|
|
|
|
|
2024-08-20 15:55:04 +02:00
|
|
|
func kindString(n datamodel.Node) string {
|
|
|
|
|
if n == nil {
|
|
|
|
|
return "null"
|
|
|
|
|
}
|
|
|
|
|
return n.Kind().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()
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-31 15:48:24 +02:00
|
|
|
func newResolutionError(message string, at []string) error {
|
2024-08-20 15:55:04 +02:00
|
|
|
return resolutionerr{message, at}
|
|
|
|
|
}
|