github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/pkg/paging/page_iterator.go (about) 1 package paging 2 3 import ( 4 "fmt" 5 "net/url" 6 "strconv" 7 8 "github.com/pkg/errors" 9 ) 10 11 const queryPairFormat = "%s=%s" 12 13 // FetchPageFunc is responsible for executing an HTTP request to the url which the PageIterator provided. 14 // If the results need to be further processed, they should be saved in a closure. 15 // The function returns the number of the results fetched and an error if any occurred during the request execution. 16 type FetchPageFunc func(string) (uint64, error) 17 18 // PageIterator is responsible for executing multiple HTTP requests until all results 19 // of a pageable API are fetched 20 type PageIterator struct { 21 baseURL string 22 pageSkipParam string 23 pageSizeParam string 24 additionalQueryParams map[string]string 25 26 do FetchPageFunc 27 skip uint64 28 pageSize uint64 29 30 nextURL string 31 hasNext bool 32 paramsCount uint64 33 } 34 35 // NewPageIterator constructs a new page iterator with the given args 36 func NewPageIterator(baseURL, skipParam, sizeParam string, additionalQueryParams map[string]string, pageSize uint64, do FetchPageFunc) PageIterator { 37 return PageIterator{ 38 baseURL: baseURL, 39 pageSkipParam: skipParam, 40 pageSizeParam: sizeParam, 41 additionalQueryParams: additionalQueryParams, 42 skip: 0, 43 pageSize: pageSize, 44 do: do, 45 hasNext: true, 46 } 47 } 48 49 // Next fetches the next page of the PageIterator. In order to get all the results that the PageIterator can fetch 50 // Next should be called until it returns false and no error. 51 // Once Next returns false and no error, Next should not be called anymore because it will do nothing. 52 func (p *PageIterator) Next() (bool, error) { 53 if !p.hasNext { 54 return false, nil 55 } 56 p.buildNextURL() 57 count, err := p.do(p.nextURL) 58 if err != nil { 59 return p.hasNext, errors.Wrapf(err, "while fetching next page: ") 60 } 61 62 if count == p.pageSize { 63 p.skip += p.pageSize 64 } else { 65 p.hasNext = false 66 } 67 return p.hasNext, nil 68 } 69 70 // FetchAll fetches all the pages (calls Next method) that the PageIterator can fetch. 71 func (p *PageIterator) FetchAll() (err error) { 72 var hasNext bool 73 for hasNext, err = p.Next(); hasNext; hasNext, err = p.Next() { 74 if err != nil { 75 return err 76 } 77 } 78 return err 79 } 80 81 func (p *PageIterator) buildNextURL() { 82 p.resetURL() 83 p.setQueryParam(p.pageSkipParam, strconv.FormatUint(p.skip, 10)) 84 p.setQueryParam(p.pageSizeParam, strconv.FormatUint(p.pageSize, 10)) 85 for k, v := range p.additionalQueryParams { 86 p.setQueryParam(k, v) 87 } 88 } 89 90 func (p *PageIterator) resetURL() { 91 p.nextURL = p.baseURL 92 p.paramsCount = 0 93 } 94 95 // setQueryParam is needed because the builtin functions of net/url will encode everything 96 // that is in the query param section which is not what we desire. We only want the values to be 97 // encoded. 98 func (p *PageIterator) setQueryParam(key, value string) { 99 if p.paramsCount == 0 { 100 p.nextURL = p.nextURL + "?" + fmt.Sprintf(queryPairFormat, key, url.QueryEscape(value)) 101 } else { 102 p.nextURL = p.nextURL + "&" + fmt.Sprintf(queryPairFormat, key, url.QueryEscape(value)) 103 } 104 p.paramsCount++ 105 }