From e86e45be73e7c1a59255fe09210e6ad5d7aa21ba Mon Sep 17 00:00:00 2001 From: Fabio Bozzo Date: Thu, 12 Sep 2024 18:19:50 +0200 Subject: [PATCH 1/7] handle String Index selector --- capability/policy/selector/selector.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/capability/policy/selector/selector.go b/capability/policy/selector/selector.go index 1dba97e..1733fcb 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" ) @@ -202,6 +203,25 @@ func resolve(sel Selector, subject ipld.Node, at []string) (ipld.Node, []ipld.No } } cur = n + } else if cur != nil && cur.Kind() == datamodel.Kind_String { + str, err := cur.AsString() + if err != nil { + return nil, nil, err + } + idx := seg.Index() + // handle negative indices by adjusting them to count from the end of the string + 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])) + } } else if seg.Optional() { cur = nil } else { From 2e17ff855056d02af3d1b87f917b55b6e6ffe856 Mon Sep 17 00:00:00 2001 From: Fabio Bozzo Date: Fri, 13 Sep 2024 13:06:49 +0200 Subject: [PATCH 2/7] handle Bytes Index [0] selector and fix case input --- capability/policy/selector/selector.go | 18 ++++++++++++++++++ capability/policy/selector/supported_test.go | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/capability/policy/selector/selector.go b/capability/policy/selector/selector.go index 1733fcb..a46388c 100644 --- a/capability/policy/selector/selector.go +++ b/capability/policy/selector/selector.go @@ -222,6 +222,24 @@ func resolve(sel Selector, subject ipld.Node, at []string) (ipld.Node, []ipld.No } else { cur = basicnode.NewString(string(str[idx])) } + } else if cur != nil && cur.Kind() == datamodel.Kind_Bytes { + b, err := cur.AsBytes() + if err != nil { + return nil, nil, err + } + idx := seg.Index() + 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])) + } } else if seg.Optional() { cur = nil } else { diff --git a/capability/policy/selector/supported_test.go b/capability/policy/selector/supported_test.go index 6a6013a..6524950 100644 --- a/capability/policy/selector/supported_test.go +++ b/capability/policy/selector/supported_test.go @@ -37,7 +37,7 @@ func TestSupportedForms(t *testing.T) { {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]`}, From cb45d9019b4dfc16a5b189d9ec498864e2162b24 Mon Sep 17 00:00:00 2001 From: Fabio Bozzo Date: Fri, 13 Sep 2024 14:18:59 +0200 Subject: [PATCH 3/7] handle Array Slice selector --- capability/policy/selector/selector.go | 50 +++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/capability/policy/selector/selector.go b/capability/policy/selector/selector.go index a46388c..2a72554 100644 --- a/capability/policy/selector/selector.go +++ b/capability/policy/selector/selector.go @@ -169,7 +169,55 @@ func resolve(sel Selector, subject ipld.Node, at []string) (ipld.Node, []ipld.No } } else if seg.Slice() != nil { if cur != nil && cur.Kind() == datamodel.Kind_List { - return nil, nil, newResolutionError("list slice selection not yet implemented", at) + slice := seg.Slice() + start, end := int64(0), cur.Length() + + if len(slice) > 0 { + start = int64(slice[0]) + if start < 0 { + start = cur.Length() + start + if start < 0 { + start = 0 + } + } + } + + if len(slice) > 1 { + end = int64(slice[1]) + if end <= 0 { + end = cur.Length() + end + if end < start { + end = start + } + } + } + + if start < 0 || start >= cur.Length() || end < start || end > cur.Length() { + if seg.Optional() { + cur = nil + } else { + return nil, nil, newResolutionError(fmt.Sprintf("slice out of bounds: [%d:%d]", start, end), at) + } + } else { + nb := basicnode.Prototype.List.NewBuilder() + assembler, err := nb.BeginList(int64(end - start)) + if err != nil { + return nil, nil, err + } + for i := start; i < end; i++ { + item, err := cur.LookupByIndex(int64(i)) + if err != nil { + return nil, nil, err + } + if err := assembler.AssembleValue().AssignNode(item); err != nil { + return nil, nil, err + } + } + if err := assembler.Finish(); err != nil { + return nil, nil, err + } + cur = nb.Build() + } } else if cur != nil && cur.Kind() == datamodel.Kind_Bytes { return nil, nil, newResolutionError("bytes slice selection not yet implemented", at) } else if seg.Optional() { From dbfff3f70cf5a76880240a4c94652236ab83420b Mon Sep 17 00:00:00 2001 From: Fabio Bozzo Date: Fri, 13 Sep 2024 14:44:26 +0200 Subject: [PATCH 4/7] refactoring resolve func --- capability/policy/selector/selector.go | 337 +++++++++++++------------ 1 file changed, 179 insertions(+), 158 deletions(-) diff --git a/capability/policy/selector/selector.go b/capability/policy/selector/selector.go index 2a72554..5cf12be 100644 --- a/capability/policy/selector/selector.go +++ b/capability/policy/selector/selector.go @@ -87,68 +87,77 @@ 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() - 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 { - return nil, nil, err - } - - if m != nil { - many = append(many, m...) - } else { - many = append(many, o) - } + // 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 { + if seg.Optional() { + cur = 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() { + k, v, err := it.Next() + 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 { + return nil, nil, err + } + + if m != nil { + many = append(many, m...) + } else { + many = append(many, o) + } + } + case datamodel.Kind_Map: + it := cur.MapIterator() + for !it.Done() { + 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) + } + } + 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) { @@ -160,138 +169,114 @@ 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 { + + 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 { slice := seg.Slice() - start, end := int64(0), cur.Length() - - if len(slice) > 0 { - start = int64(slice[0]) - if start < 0 { - start = cur.Length() + start - if start < 0 { - start = 0 - } - } + 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))) + default: + return nil, nil, newResolutionError(fmt.Sprintf("can not slice on kind: %s", kindString(cur)), at) } - if len(slice) > 1 { - end = int64(slice[1]) - if end <= 0 { - end = cur.Length() + end - if end < start { - end = start - } - } - } - - if start < 0 || start >= cur.Length() || end < start || end > cur.Length() { + if start < 0 || end < start || end > cur.Length() { if seg.Optional() { cur = nil } else { return nil, nil, newResolutionError(fmt.Sprintf("slice out of bounds: [%d:%d]", start, end), at) } } else { - nb := basicnode.Prototype.List.NewBuilder() - assembler, err := nb.BeginList(int64(end - start)) - if err != nil { - return nil, nil, err - } - for i := start; i < end; i++ { - item, err := cur.LookupByIndex(int64(i)) - if err != nil { - return nil, nil, err - } - if err := assembler.AssembleValue().AssignNode(item); err != nil { + switch cur.Kind() { + case datamodel.Kind_List: + nb := basicnode.Prototype.List.NewBuilder() + assembler, _ := nb.BeginList(end - start) + for i := start; i < end; i++ { + item, _ := cur.LookupByIndex(i) + if err := assembler.AssembleValue().AssignNode(item); err != nil { + return nil, nil, err + } + } + if err := assembler.Finish(); err != nil { return nil, nil, err } + cur = nb.Build() + case datamodel.Kind_Bytes: + b, _ := cur.AsBytes() + cur = basicnode.NewBytes(b[start:end]) } - if err := assembler.Finish(); err != nil { - return nil, nil, err - } - cur = nb.Build() } - } 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 - } else { - return nil, nil, newResolutionError(fmt.Sprintf("can not index: %s on kind: %s", seg.Field(), kindString(cur)), at) } - } 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)) } - } - cur = n - } else if cur != nil && cur.Kind() == datamodel.Kind_String { - str, err := cur.AsString() - if err != nil { - return nil, nil, err - } - idx := seg.Index() - // handle negative indices by adjusting them to count from the end of the string - if idx < 0 { - idx = len(str) + idx - } - if idx < 0 || idx >= len(str) { - if seg.Optional() { - cur = nil + 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 { - return nil, nil, newResolutionError(fmt.Sprintf("index out of bounds: %d", seg.Index()), at) + cur = basicnode.NewString(string(str[idx])) } - } else { - cur = basicnode.NewString(string(str[idx])) - } - } else if cur != nil && cur.Kind() == datamodel.Kind_Bytes { - b, err := cur.AsBytes() - if err != nil { - return nil, nil, err - } - idx := seg.Index() - if idx < 0 { - idx = len(b) + idx - } - if idx < 0 || idx >= len(b) { - if seg.Optional() { - cur = nil + 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 { - return nil, nil, newResolutionError(fmt.Sprintf("index out of bounds: %d", seg.Index()), at) + cur = basicnode.NewInt(int64(b[idx])) } - } 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) } - } 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) } } } @@ -299,6 +284,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" From a183b627bed1ee9c791593387cebb0afb0b28391 Mon Sep 17 00:00:00 2001 From: Fabio Bozzo Date: Fri, 13 Sep 2024 14:49:52 +0200 Subject: [PATCH 5/7] handle String Slice selector --- capability/policy/selector/selector.go | 31 ++++++++++++++++++-------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/capability/policy/selector/selector.go b/capability/policy/selector/selector.go index 5cf12be..fe5dbd5 100644 --- a/capability/policy/selector/selector.go +++ b/capability/policy/selector/selector.go @@ -190,11 +190,14 @@ func resolve(sel Selector, subject ipld.Node, at []string) (ipld.Node, []ipld.No 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 || end > cur.Length() { + if start < 0 || end < start { if seg.Optional() { cur = nil } else { @@ -203,21 +206,31 @@ func resolve(sel Selector, subject ipld.Node, at []string) (ipld.Node, []ipld.No } else { switch cur.Kind() { case datamodel.Kind_List: + if end > cur.Length() { + end = cur.Length() + } nb := basicnode.Prototype.List.NewBuilder() - assembler, _ := nb.BeginList(end - start) + assembler, _ := nb.BeginList(int64(end - start)) for i := start; i < end; i++ { - item, _ := cur.LookupByIndex(i) - if err := assembler.AssembleValue().AssignNode(item); err != nil { - return nil, nil, err - } - } - if err := assembler.Finish(); err != nil { - return nil, nil, err + 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]) } } } From 7060d4bb3370816fd34e2c2201ea767a17b073e6 Mon Sep 17 00:00:00 2001 From: Fabio Bozzo Date: Fri, 13 Sep 2024 15:33:11 +0200 Subject: [PATCH 6/7] handle Optional Null Iterator selector --- capability/policy/selector/selector.go | 15 +++++++++++++-- capability/policy/selector/supported_test.go | 2 +- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/capability/policy/selector/selector.go b/capability/policy/selector/selector.go index fe5dbd5..2fa257f 100644 --- a/capability/policy/selector/selector.go +++ b/capability/policy/selector/selector.go @@ -93,9 +93,20 @@ func resolve(sel Selector, subject ipld.Node, at []string) (ipld.Node, []ipld.No // 2nd level: handle different node kinds (list, map, string, bytes) switch { case seg.Iterator(): - if cur == nil { + if cur == nil || cur.Kind() == datamodel.Kind_Null { if seg.Optional() { - cur = nil + // build empty list + nb := basicnode.Prototype.List.NewBuilder() + assembler, err := nb.BeginList(0) + if err != nil { + return nil, nil, err + } + + if err = assembler.Finish(); err != nil { + return nil, nil, err + } + + return nb.Build(), nil, nil } else { return nil, nil, newResolutionError(fmt.Sprintf("can not iterate over kind: %s", kindString(cur)), at) } diff --git a/capability/policy/selector/supported_test.go b/capability/policy/selector/supported_test.go index 6524950..de8f83c 100644 --- a/capability/policy/selector/supported_test.go +++ b/capability/policy/selector/supported_test.go @@ -30,7 +30,7 @@ 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`}, From d57d2a230bfd33d6ef4578dcb54283d613044165 Mon Sep 17 00:00:00 2001 From: Fabio Bozzo Date: Fri, 13 Sep 2024 16:26:46 +0200 Subject: [PATCH 7/7] handle Optional Iterator selector --- capability/policy/selector/selector.go | 77 +++++++++++++++++++------- 1 file changed, 57 insertions(+), 20 deletions(-) diff --git a/capability/policy/selector/selector.go b/capability/policy/selector/selector.go index 2fa257f..1bd85f2 100644 --- a/capability/policy/selector/selector.go +++ b/capability/policy/selector/selector.go @@ -116,41 +116,78 @@ func resolve(sel Selector, subject ipld.Node, at []string) (ipld.Node, []ipld.No case datamodel.Kind_List: it := cur.ListIterator() for !it.Done() { - k, v, err := it.Next() + _, v, err := it.Next() 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 { - return nil, nil, err - } - - if m != nil { - many = append(many, m...) + // 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 { - many = append(many, o) + // 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() { - k, v, err := it.Next() + _, 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...) + 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, o) + many = append(many, v) } } default: