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  }