github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/lib/cursor.go (about)

     1  package lib
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"reflect"
     8  	"strings"
     9  )
    10  
    11  // Cursor represents a position into a list of paginated results. Users
    12  // of this interface can retrieve all results by repeatedly calling
    13  // `cursor.Next()` until it returns ErrCursorComplete. When this error is
    14  // returned, the other returned value may still have data; users should
    15  // append this to their total collection of results.
    16  //
    17  // The implementation of a Cursor contains info about the method being
    18  // called, as well as whatever input parameters that method requires.
    19  // Moving to the next page of results is done by adjusting the input
    20  // parameters as needed to retrieve the next page.
    21  //
    22  // Methods that want to create and return a Cursor are encouraged to use
    23  // the helper `scope.MakeCursor`. The arguments to this method are the
    24  // number of results being returned in the current page, as well as
    25  // the input parameters to the method, after being adjusted. This method
    26  // will correctly handle the invariant that no Cursor should be returned
    27  // if the returned list of items is empty.
    28  //
    29  // Finally, a Cursor can be serialized for use in HTTP requests by using
    30  // the ToParams method. This basically just converts the input params
    31  // into a map of key value pairs. If passed as a POST json body, the
    32  // receiving HTTP server will be able to convert them back into input
    33  // params to retrieve the next page of results.
    34  
    35  var (
    36  	// ErrCursorComplete is returned by Cursor.Next when the cursor is completed
    37  	ErrCursorComplete = errors.New("cursor is complete")
    38  )
    39  
    40  // Cursor is used to paginate results for methods that support it
    41  type Cursor interface {
    42  	Next(ctx context.Context) (interface{}, error)
    43  	ToParams() (map[string]string, error)
    44  }
    45  
    46  type cursor struct {
    47  	d        dispatcher
    48  	method   string
    49  	nextPage interface{}
    50  }
    51  
    52  // Next will fetch the next page of results, or return ErrCursorComplete if no
    53  // results are left
    54  func (c cursor) Next(ctx context.Context) (interface{}, error) {
    55  	res, cur, err := c.d.Dispatch(ctx, c.method, c.nextPage)
    56  	if err != nil {
    57  		return nil, err
    58  	}
    59  	if cur == nil {
    60  		return nil, ErrCursorComplete
    61  	}
    62  	return res, nil
    63  }
    64  
    65  // ToParams returns the cursor's input params as a map of strings to strings
    66  func (c cursor) ToParams() (map[string]string, error) {
    67  	params := make(map[string]string)
    68  	target := reflect.ValueOf(c.nextPage)
    69  	if target.Kind() == reflect.Ptr {
    70  		target = target.Elem()
    71  	}
    72  	for i := 0; i < target.NumField(); i++ {
    73  		// Lowercase the key in order to make matching case-insensitive.
    74  		fieldName := target.Type().Field(i).Name
    75  		lowerName := strings.ToLower(fieldName)
    76  		fieldTag := target.Type().Field(i).Tag
    77  		if fieldTag != "" && fieldTag.Get("json") != "" {
    78  			jsonName := fieldTag.Get("json")
    79  			pos := strings.Index(jsonName, ",")
    80  			if pos != -1 {
    81  				jsonName = jsonName[:pos]
    82  			}
    83  			lowerName = strings.ToLower(jsonName)
    84  		}
    85  		v := target.Field(i)
    86  		if v.IsZero() {
    87  			continue
    88  		}
    89  		params[lowerName] = fmt.Sprintf("%v", v)
    90  	}
    91  	return params, nil
    92  }