github.com/abdfnx/gh-api@v0.0.0-20210414084727-f5432eec23b8/pkg/cmd/api/pagination.go (about)

     1  package api
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  	"net/url"
     9  	"regexp"
    10  	"strings"
    11  )
    12  
    13  var linkRE = regexp.MustCompile(`<([^>]+)>;\s*rel="([^"]+)"`)
    14  
    15  func findNextPage(resp *http.Response) (string, bool) {
    16  	for _, m := range linkRE.FindAllStringSubmatch(resp.Header.Get("Link"), -1) {
    17  		if len(m) >= 2 && m[2] == "next" {
    18  			return m[1], true
    19  		}
    20  	}
    21  	return "", false
    22  }
    23  
    24  func findEndCursor(r io.Reader) string {
    25  	dec := json.NewDecoder(r)
    26  
    27  	var idx int
    28  	var stack []json.Delim
    29  	var lastKey string
    30  	var contextKey string
    31  
    32  	var endCursor string
    33  	var hasNextPage bool
    34  	var foundEndCursor bool
    35  	var foundNextPage bool
    36  
    37  loop:
    38  	for {
    39  		t, err := dec.Token()
    40  		if err == io.EOF {
    41  			break
    42  		}
    43  		if err != nil {
    44  			return ""
    45  		}
    46  
    47  		switch tt := t.(type) {
    48  		case json.Delim:
    49  			switch tt {
    50  			case '{', '[':
    51  				stack = append(stack, tt)
    52  				contextKey = lastKey
    53  				idx = 0
    54  			case '}', ']':
    55  				stack = stack[:len(stack)-1]
    56  				contextKey = ""
    57  				idx = 0
    58  			}
    59  		default:
    60  			isKey := len(stack) > 0 && stack[len(stack)-1] == '{' && idx%2 == 0
    61  			idx++
    62  
    63  			switch tt := t.(type) {
    64  			case string:
    65  				if isKey {
    66  					lastKey = tt
    67  				} else if contextKey == "pageInfo" && lastKey == "endCursor" {
    68  					endCursor = tt
    69  					foundEndCursor = true
    70  					if foundNextPage {
    71  						break loop
    72  					}
    73  				}
    74  			case bool:
    75  				if contextKey == "pageInfo" && lastKey == "hasNextPage" {
    76  					hasNextPage = tt
    77  					foundNextPage = true
    78  					if foundEndCursor {
    79  						break loop
    80  					}
    81  				}
    82  			}
    83  		}
    84  	}
    85  
    86  	if hasNextPage {
    87  		return endCursor
    88  	}
    89  	return ""
    90  }
    91  
    92  func addPerPage(p string, perPage int, params map[string]interface{}) string {
    93  	if _, hasPerPage := params["per_page"]; hasPerPage {
    94  		return p
    95  	}
    96  
    97  	idx := strings.IndexRune(p, '?')
    98  	sep := "?"
    99  
   100  	if idx >= 0 {
   101  		if qp, err := url.ParseQuery(p[idx+1:]); err == nil && qp.Get("per_page") != "" {
   102  			return p
   103  		}
   104  		sep = "&"
   105  	}
   106  
   107  	return fmt.Sprintf("%s%sper_page=%d", p, sep, perPage)
   108  }