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 )