github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/nomad/state/paginator/paginator.go (about)

     1  package paginator
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/hashicorp/go-bexpr"
     7  	"github.com/hashicorp/nomad/nomad/structs"
     8  )
     9  
    10  // Iterator is the interface that must be implemented to supply data to the
    11  // Paginator.
    12  type Iterator interface {
    13  	// Next returns the next element to be considered for pagination.
    14  	// The page will end if nil is returned.
    15  	Next() interface{}
    16  }
    17  
    18  // Paginator wraps an iterator and returns only the expected number of pages.
    19  type Paginator struct {
    20  	iter           Iterator
    21  	tokenizer      Tokenizer
    22  	filters        []Filter
    23  	perPage        int32
    24  	itemCount      int32
    25  	seekingToken   string
    26  	nextToken      string
    27  	reverse        bool
    28  	nextTokenFound bool
    29  	pageErr        error
    30  
    31  	// appendFunc is the function the caller should use to append raw
    32  	// entries to the results set. The object is guaranteed to be
    33  	// non-nil.
    34  	appendFunc func(interface{}) error
    35  }
    36  
    37  // NewPaginator returns a new Paginator. Any error creating the paginator is
    38  // due to bad user filter input, RPC functions should therefore return a 400
    39  // error code along with an appropriate message.
    40  func NewPaginator(iter Iterator, tokenizer Tokenizer, filters []Filter,
    41  	opts structs.QueryOptions, appendFunc func(interface{}) error) (*Paginator, error) {
    42  
    43  	var evaluator *bexpr.Evaluator
    44  	var err error
    45  
    46  	if opts.Filter != "" {
    47  		evaluator, err = bexpr.CreateEvaluator(opts.Filter)
    48  		if err != nil {
    49  			return nil, fmt.Errorf("failed to read filter expression: %v", err)
    50  		}
    51  		filters = append(filters, evaluator)
    52  	}
    53  
    54  	return &Paginator{
    55  		iter:           iter,
    56  		tokenizer:      tokenizer,
    57  		filters:        filters,
    58  		perPage:        opts.PerPage,
    59  		seekingToken:   opts.NextToken,
    60  		reverse:        opts.Reverse,
    61  		nextTokenFound: opts.NextToken == "",
    62  		appendFunc:     appendFunc,
    63  	}, nil
    64  }
    65  
    66  // Page populates a page by running the append function
    67  // over all results. Returns the next token.
    68  func (p *Paginator) Page() (string, error) {
    69  DONE:
    70  	for {
    71  		raw, andThen := p.next()
    72  		switch andThen {
    73  		case paginatorInclude:
    74  			err := p.appendFunc(raw)
    75  			if err != nil {
    76  				p.pageErr = err
    77  				break DONE
    78  			}
    79  		case paginatorSkip:
    80  			continue
    81  		case paginatorComplete:
    82  			break DONE
    83  		}
    84  	}
    85  	return p.nextToken, p.pageErr
    86  }
    87  
    88  func (p *Paginator) next() (interface{}, paginatorState) {
    89  	raw := p.iter.Next()
    90  	if raw == nil {
    91  		p.nextToken = ""
    92  		return nil, paginatorComplete
    93  	}
    94  	token := p.tokenizer.GetToken(raw)
    95  
    96  	// have we found the token we're seeking (if any)?
    97  	p.nextToken = token
    98  
    99  	var passedToken bool
   100  
   101  	if p.reverse {
   102  		passedToken = token > p.seekingToken
   103  	} else {
   104  		passedToken = token < p.seekingToken
   105  	}
   106  
   107  	if !p.nextTokenFound && passedToken {
   108  		return nil, paginatorSkip
   109  	}
   110  
   111  	// apply filters if defined
   112  	for _, f := range p.filters {
   113  		allow, err := f.Evaluate(raw)
   114  		if err != nil {
   115  			p.pageErr = err
   116  			return nil, paginatorComplete
   117  		}
   118  		if !allow {
   119  			return nil, paginatorSkip
   120  		}
   121  	}
   122  
   123  	p.nextTokenFound = true
   124  
   125  	// have we produced enough results for this page?
   126  	p.itemCount++
   127  	if p.perPage != 0 && p.itemCount > p.perPage {
   128  		return raw, paginatorComplete
   129  	}
   130  
   131  	return raw, paginatorInclude
   132  }
   133  
   134  type paginatorState int
   135  
   136  const (
   137  	paginatorInclude paginatorState = iota
   138  	paginatorSkip
   139  	paginatorComplete
   140  )