github.com/ipld/go-ipld-prime@v0.21.0/traversal/selector/matcher.go (about) 1 package selector 2 3 import ( 4 "fmt" 5 "io" 6 "math" 7 8 "github.com/ipld/go-ipld-prime/datamodel" 9 "github.com/ipld/go-ipld-prime/node/basicnode" 10 ) 11 12 // Matcher marks a node to be included in the "result" set. 13 // (All nodes traversed by a selector are in the "covered" set (which is a.k.a. 14 // "the merkle proof"); the "result" set is a subset of the "covered" set.) 15 // 16 // In libraries using selectors, the "result" set is typically provided to 17 // some user-specified callback. 18 // 19 // A selector tree with only "explore*"-type selectors and no Matcher selectors 20 // is valid; it will just generate a "covered" set of nodes and no "result" set. 21 // TODO: From spec: implement conditions and labels 22 type Matcher struct { 23 *Slice 24 } 25 26 // Slice limits a result node to a subset of the node. 27 // The returned node will be limited based on slicing the specified range of the 28 // node into a new node, or making use of the `AsLargeBytes` io.ReadSeeker to 29 // restrict response with a SectionReader. 30 // 31 // Slice supports [From,To) ranges, where From is inclusive and To is exclusive. 32 // Negative values for From and To are interpreted as offsets from the end of 33 // the node. If To is greater than the node length, it will be truncated to the 34 // node length. If From is greater than the node length or greater than To, the 35 // result will be a non-match. 36 type Slice struct { 37 From int64 38 To int64 39 } 40 41 func sliceBounds(from, to, length int64) (bool, int64, int64) { 42 if to < 0 { 43 to = length + to 44 } else if length < to { 45 to = length 46 } 47 if from < 0 { 48 from = length + from 49 if from < 0 { 50 from = 0 51 } 52 } 53 if from > to || from >= length { 54 return false, 0, 0 55 } 56 return true, from, to 57 } 58 59 func (s Slice) Slice(n datamodel.Node) (datamodel.Node, error) { 60 var from, to int64 61 switch n.Kind() { 62 case datamodel.Kind_String: 63 str, err := n.AsString() 64 if err != nil { 65 return nil, err 66 } 67 68 var match bool 69 match, from, to = sliceBounds(s.From, s.To, int64(len(str))) 70 if !match { 71 return nil, nil 72 } 73 return basicnode.NewString(str[from:to]), nil 74 case datamodel.Kind_Bytes: 75 to = s.To 76 from = s.From 77 var length int64 = math.MaxInt64 78 var rdr io.ReadSeeker 79 var bytes []byte 80 var err error 81 82 if lbn, ok := n.(datamodel.LargeBytesNode); ok { 83 rdr, err = lbn.AsLargeBytes() 84 if err != nil { 85 return nil, err 86 } 87 // calculate length from seeker 88 length, err = rdr.Seek(0, io.SeekEnd) 89 if err != nil { 90 return nil, err 91 } 92 // reset 93 _, err = rdr.Seek(0, io.SeekStart) 94 if err != nil { 95 return nil, err 96 } 97 } else { 98 bytes, err = n.AsBytes() 99 if err != nil { 100 return nil, err 101 } 102 length = int64(len(bytes)) 103 } 104 105 var match bool 106 match, from, to = sliceBounds(from, to, length) 107 if !match { 108 return nil, nil 109 } 110 if rdr != nil { 111 sr := io.NewSectionReader(&readerat{rdr, 0}, from, to-from) 112 return basicnode.NewBytesFromReader(sr), nil 113 } 114 return basicnode.NewBytes(bytes[from:to]), nil 115 default: 116 return nil, nil 117 } 118 } 119 120 // Interests are empty for a matcher (for now) because 121 // It is always just there to match, not explore further 122 func (s Matcher) Interests() []datamodel.PathSegment { 123 return []datamodel.PathSegment{} 124 } 125 126 // Explore will return nil because a matcher is a terminal selector 127 func (s Matcher) Explore(n datamodel.Node, p datamodel.PathSegment) (Selector, error) { 128 return nil, nil 129 } 130 131 // Decide is always true for a match cause it's in the result set 132 // Deprecated: use Match instead 133 func (s Matcher) Decide(n datamodel.Node) bool { 134 return true 135 } 136 137 // Match is always true for a match cause it's in the result set 138 func (s Matcher) Match(node datamodel.Node) (datamodel.Node, error) { 139 if s.Slice != nil { 140 return s.Slice.Slice(node) 141 } 142 return node, nil 143 } 144 145 // ParseMatcher assembles a Selector 146 // from a matcher selector node 147 // TODO: Parse labels and conditions 148 func (pc ParseContext) ParseMatcher(n datamodel.Node) (Selector, error) { 149 if n.Kind() != datamodel.Kind_Map { 150 return nil, fmt.Errorf("selector spec parse rejected: selector body must be a map") 151 } 152 153 // check if a slice is specified 154 if subset, err := n.LookupByString("subset"); err == nil { 155 if subset.Kind() != datamodel.Kind_Map { 156 return nil, fmt.Errorf("selector spec parse rejected: subset body must be a map") 157 } 158 from, err := subset.LookupByString("[") 159 if err != nil { 160 return nil, fmt.Errorf("selector spec parse rejected: selector body must be a map with a from '[' key") 161 } 162 fromN, err := from.AsInt() 163 if err != nil { 164 return nil, fmt.Errorf("selector spec parse rejected: selector body must be a map with a 'from' key that is a number") 165 } 166 to, err := subset.LookupByString("]") 167 if err != nil { 168 return nil, fmt.Errorf("selector spec parse rejected: selector body must be a map with a to ']' key") 169 } 170 toN, err := to.AsInt() 171 if err != nil { 172 return nil, fmt.Errorf("selector spec parse rejected: selector body must be a map with a 'to' key that is a number") 173 } 174 if toN >= 0 && fromN > toN { 175 return nil, fmt.Errorf("selector spec parse rejected: selector body must be a map with a 'from' key that is less than or equal to the 'to' key") 176 } 177 return Matcher{&Slice{ 178 From: fromN, 179 To: toN, 180 }}, nil 181 } 182 return Matcher{}, nil 183 }