diff --git a/capability/policy/selector/selector.go b/capability/policy/selector/selector.go index 1dba97e..1bd85f2 100644 --- a/capability/policy/selector/selector.go +++ b/capability/policy/selector/selector.go @@ -7,6 +7,7 @@ import ( "github.com/ipld/go-ipld-prime" "github.com/ipld/go-ipld-prime/datamodel" + "github.com/ipld/go-ipld-prime/node/basicnode" "github.com/ipld/go-ipld-prime/schema" ) @@ -86,68 +87,125 @@ func resolve(sel Selector, subject ipld.Node, at []string) (ipld.Node, []ipld.No 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() + // 1st level: handle the different segment types (iterator, field, slice, index) + // 2nd level: handle different node kinds (list, map, string, bytes) + switch { + case seg.Iterator(): + if cur == nil || cur.Kind() == datamodel.Kind_Null { + if seg.Optional() { + // build empty list + nb := basicnode.Prototype.List.NewBuilder() + assembler, err := nb.BeginList(0) 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 { + if err = assembler.Finish(); err != nil { return nil, nil, err } - if m != nil { - many = append(many, m...) - } else { - many = append(many, o) - } + return nb.Build(), nil, nil + } else { + return nil, nil, newResolutionError(fmt.Sprintf("can not iterate over kind: %s", kindString(cur)), at) } - 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) + var many []ipld.Node + switch cur.Kind() { + case datamodel.Kind_List: + it := cur.ListIterator() + for !it.Done() { + _, v, err := it.Next() + if err != nil { + return nil, nil, err + } + + // check if there are more iterator segments + if len(sel) > i+1 && sel[i+1].Iterator() { + if v.Kind() == datamodel.Kind_List { + // recursively resolve the remaining selector segments + var o ipld.Node + var m []ipld.Node + o, m, err = resolve(sel[i+1:], v, at) + if err != nil { + // if the segment is optional and an error occurs, skip the current iteration. + if seg.Optional() { + continue + } else { + return nil, nil, err + } + } + if m != nil { + many = append(many, m...) + } else if o != nil { + many = append(many, o) + } + } else { + // if the current value is not a list and the next segment is optional, skip the current iteration + if sel[i+1].Optional() { + continue + } else { + return nil, nil, newResolutionError(fmt.Sprintf("can not iterate over kind: %s", kindString(v)), at) + } + } + } else { + // if there are no more iterator segments, append the current value to the result + many = append(many, v) + } + } + case datamodel.Kind_Map: + it := cur.MapIterator() + for !it.Done() { + _, v, err := it.Next() + if err != nil { + return nil, nil, err + } + + if len(sel) > i+1 && sel[i+1].Iterator() { + if v.Kind() == datamodel.Kind_List { + var o ipld.Node + var m []ipld.Node + o, m, err = resolve(sel[i+1:], v, at) + if err != nil { + if seg.Optional() { + continue + } else { + return nil, nil, err + } + } + if m != nil { + many = append(many, m...) + } else if o != nil { + many = append(many, o) + } + } else { + if sel[i+1].Optional() { + continue + } else { + return nil, nil, newResolutionError(fmt.Sprintf("can not iterate over kind: %s", kindString(v)), at) + } + } + } else { + many = append(many, v) + } + } + default: + return nil, nil, newResolutionError(fmt.Sprintf("can not iterate over kind: %s", kindString(cur)), at) + } + + return nil, many, nil } - } else if seg.Field() != "" { + case seg.Field() != "": at = append(at, seg.Field()) - if cur != nil && cur.Kind() == datamodel.Kind_Map { + if cur == nil || cur.Kind() != datamodel.Kind_Map { + 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 { n, err := cur.LookupByString(seg.Field()) if err != nil { if isMissing(err) { @@ -159,53 +217,127 @@ func resolve(sel Selector, subject ipld.Node, at []string) (ipld.Node, []ipld.No } else { return nil, nil, err } + } else { + cur = n } - 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 + + case seg.Slice() != nil: + if cur == nil { + if seg.Optional() { + cur = nil + } else { + return nil, nil, newResolutionError(fmt.Sprintf("can not slice on kind: %s", kindString(cur)), at) + } } else { - return nil, nil, newResolutionError(fmt.Sprintf("can not index: %s on kind: %s", seg.Field(), kindString(cur)), at) + slice := seg.Slice() + var start, end int64 + switch cur.Kind() { + case datamodel.Kind_List: + start, end = resolveSliceIndices(slice, cur.Length()) + case datamodel.Kind_Bytes: + b, _ := cur.AsBytes() + start, end = resolveSliceIndices(slice, int64(len(b))) + case datamodel.Kind_String: + str, _ := cur.AsString() + start, end = resolveSliceIndices(slice, int64(len(str))) + default: + return nil, nil, newResolutionError(fmt.Sprintf("can not slice on kind: %s", kindString(cur)), at) + } + + if start < 0 || end < start { + if seg.Optional() { + cur = nil + } else { + return nil, nil, newResolutionError(fmt.Sprintf("slice out of bounds: [%d:%d]", start, end), at) + } + } else { + switch cur.Kind() { + case datamodel.Kind_List: + if end > cur.Length() { + end = cur.Length() + } + nb := basicnode.Prototype.List.NewBuilder() + assembler, _ := nb.BeginList(int64(end - start)) + for i := start; i < end; i++ { + item, _ := cur.LookupByIndex(int64(i)) + assembler.AssembleValue().AssignNode(item) + } + assembler.Finish() + cur = nb.Build() + case datamodel.Kind_Bytes: + b, _ := cur.AsBytes() + l := int64(len(b)) + if end > l { + end = l + } + cur = basicnode.NewBytes(b[start:end]) + case datamodel.Kind_String: + str, _ := cur.AsString() + l := int64(len(str)) + if end > l { + end = l + } + cur = basicnode.NewString(str[start:end]) + } + } } - } else { + + default: 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 + if cur == nil { + if seg.Optional() { + cur = nil + } else { + return nil, nil, newResolutionError(fmt.Sprintf("can not access index: %d on kind: %s", seg.Index(), kindString(cur)), at) } - if idx < 0 { - // necessary until https://github.com/ipld/go-ipld-prime/pull/571 - // after, isMissing() below will work - // TODO: remove - return nil, nil, newResolutionError(fmt.Sprintf("index out of bounds: %d", seg.Index()), at) - } - n, err := cur.LookupByIndex(idx) - if err != nil { - if isMissing(err) { + } else { + idx := seg.Index() + switch cur.Kind() { + case datamodel.Kind_List: + if idx < 0 { + idx = int(cur.Length()) + idx + } + if idx < 0 || idx >= int(cur.Length()) { 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, _ = cur.LookupByIndex(int64(idx)) } + case datamodel.Kind_String: + str, _ := cur.AsString() + if idx < 0 { + idx = len(str) + idx + } + if idx < 0 || idx >= len(str) { + if seg.Optional() { + cur = nil + } else { + return nil, nil, newResolutionError(fmt.Sprintf("index out of bounds: %d", seg.Index()), at) + } + } else { + cur = basicnode.NewString(string(str[idx])) + } + case datamodel.Kind_Bytes: + b, _ := cur.AsBytes() + if idx < 0 { + idx = len(b) + idx + } + if idx < 0 || idx >= len(b) { + if seg.Optional() { + cur = nil + } else { + return nil, nil, newResolutionError(fmt.Sprintf("index out of bounds: %d", seg.Index()), at) + } + } else { + cur = basicnode.NewInt(int64(b[idx])) + } + default: + return nil, nil, newResolutionError(fmt.Sprintf("can not access index: %d on kind: %s", seg.Index(), kindString(cur)), at) } - 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) } } } @@ -213,6 +345,42 @@ func resolve(sel Selector, subject ipld.Node, at []string) (ipld.Node, []ipld.No return cur, nil, nil } +// 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. +// - end: The resolved end index for slicing. +func resolveSliceIndices(slice []int, length int64) (int64, int64) { + start, end := int64(0), length + if len(slice) > 0 { + start = int64(slice[0]) + if start < 0 { + start = length + start + if start < 0 { + start = 0 + } + } + } + if len(slice) > 1 { + end = int64(slice[1]) + if end <= 0 { + end = length + end + if end < start { + end = start + } + } + } + + return start, end +} + func kindString(n datamodel.Node) string { if n == nil { return "null" diff --git a/capability/policy/selector/supported_test.go b/capability/policy/selector/supported_test.go index 6a6013a..de8f83c 100644 --- a/capability/policy/selector/supported_test.go +++ b/capability/policy/selector/supported_test.go @@ -30,14 +30,14 @@ func TestSupportedForms(t *testing.T) { for _, testcase := range []Testcase{ {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 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: "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]`},