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  }