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 }