github.com/chnsz/golangsdk@v0.0.0-20240506093406-85a3fbfa605b/pagination/marker.go (about) 1 package pagination 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/chnsz/golangsdk" 8 ) 9 10 // MarkerPage is a stricter Page interface that describes additional functionality required for use with NewMarkerPager. 11 // For convenience, embed the MarkedPageBase struct. 12 type MarkerPage interface { 13 Page 14 15 // LastMarker returns the last "marker" value on this page. 16 LastMarker() (string, error) 17 } 18 19 // MarkerPageBase is a page in a collection that's paginated by "limit" and "marker" query parameters. 20 type MarkerPageBase struct { 21 PageResult 22 23 // Owner is a reference to the embedding struct. 24 Owner MarkerPage 25 // markerField is the field/key to get the next marker, the default value is "id" 26 markerField string 27 } 28 29 // NextPageURL generates the URL for the page of results after this one. 30 func (current MarkerPageBase) NextPageURL() (string, error) { 31 currentURL := current.URL 32 33 mark, err := current.Owner.LastMarker() 34 if err != nil { 35 return "", err 36 } 37 38 if mark == "" { 39 return "", nil 40 } 41 42 q := currentURL.Query() 43 q.Set("marker", mark) 44 currentURL.RawQuery = q.Encode() 45 46 return currentURL.String(), nil 47 } 48 49 // IsEmpty satisifies the IsEmpty method of the Page interface 50 func (current MarkerPageBase) IsEmpty() (bool, error) { 51 if pb, ok := current.Body.(map[string]interface{}); ok { 52 for k, v := range pb { 53 // ignore xxx_links 54 if !strings.HasSuffix(k, "links") { 55 // check the field's type. we only want []interface{} (which is really []map[string]interface{}) 56 switch vt := v.(type) { 57 case []interface{}: 58 return len(vt) == 0, nil 59 } 60 } 61 } 62 } 63 64 if b, ok := current.Body.([]interface{}); ok { 65 return len(b) == 0, nil 66 } 67 68 err := golangsdk.ErrUnexpectedType{} 69 err.Expected = "map[string]interface{}/[]interface{}" 70 err.Actual = fmt.Sprintf("%T", current.Body) 71 return true, err 72 } 73 74 // GetBody returns the linked page's body. This method is needed to satisfy the 75 // Page interface. 76 func (current MarkerPageBase) GetBody() interface{} { 77 return current.Body 78 } 79 80 // LastMarker method returns the last ID in a page. 81 func (current MarkerPageBase) LastMarker() (string, error) { 82 var pageItems []interface{} 83 84 switch pb := current.Body.(type) { 85 case map[string]interface{}: 86 for k, v := range pb { 87 // ignore xxx_links 88 if !strings.HasSuffix(k, "links") { 89 // check the field's type. we only want []interface{} (which is really []map[string]interface{}) 90 switch vt := v.(type) { 91 case []interface{}: 92 pageItems = vt 93 break 94 } 95 } 96 } 97 case []interface{}: 98 pageItems = pb 99 default: 100 err := golangsdk.ErrUnexpectedType{} 101 err.Expected = "map[string]interface{}/[]interface{}" 102 err.Actual = fmt.Sprintf("%T", pb) 103 return "", err 104 } 105 106 if len(pageItems) == 0 { 107 return "", nil 108 } 109 110 lastItem := pageItems[len(pageItems)-1] 111 field := current.getMarkerField() 112 lastID, err := searchField(lastItem, field) 113 if err != nil { 114 return "", err 115 } 116 117 // check the marker field 118 if lastID != nil { 119 if id, ok := lastID.(string); ok && id != "" { 120 return id, nil 121 } 122 } 123 124 return "", fmt.Errorf("can not find '%s' in item", field) 125 } 126 127 func (current *MarkerPageBase) getMarkerField() string { 128 if current.markerField == "" { 129 return "id" 130 } 131 return current.markerField 132 } 133 134 func (current *MarkerPageBase) setMarkerField(field string) { 135 if field != "" { 136 current.markerField = field 137 } 138 return 139 } 140 141 func searchField(root interface{}, field string) (interface{}, error) { 142 keys := strings.Split(field, ".") 143 current := root 144 for _, k := range keys { 145 if currentMap, ok := current.(map[string]interface{}); ok { 146 if v, exist := currentMap[k]; exist { 147 current = v 148 } else { 149 return nil, fmt.Errorf("can not find '%s' in item", field) 150 } 151 } else { 152 return nil, fmt.Errorf("item is not a map but %T", current) 153 } 154 } 155 156 return current, nil 157 }