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  }