131 lines
2.9 KiB
Go
131 lines
2.9 KiB
Go
package state
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/hashicorp/go-bexpr"
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
|
)
|
|
|
|
// Iterator is the interface that must be implemented to use the Paginator.
|
|
type Iterator interface {
|
|
// Next returns the next element to be considered for pagination.
|
|
// The page will end if nil is returned.
|
|
Next() interface{}
|
|
}
|
|
|
|
// Paginator is an iterator over a memdb.ResultIterator that returns
|
|
// only the expected number of pages.
|
|
type Paginator struct {
|
|
iter Iterator
|
|
perPage int32
|
|
itemCount int32
|
|
seekingToken string
|
|
nextToken string
|
|
nextTokenFound bool
|
|
pageErr error
|
|
|
|
// filterEvaluator is used to filter results using go-bexpr. It's nil if
|
|
// no filter expression is defined.
|
|
filterEvaluator *bexpr.Evaluator
|
|
|
|
// appendFunc is the function the caller should use to append raw
|
|
// entries to the results set. The object is guaranteed to be
|
|
// non-nil.
|
|
appendFunc func(interface{}) error
|
|
}
|
|
|
|
func NewPaginator(iter Iterator, opts structs.QueryOptions, appendFunc func(interface{}) error) (*Paginator, error) {
|
|
var evaluator *bexpr.Evaluator
|
|
var err error
|
|
|
|
if opts.Filter != "" {
|
|
evaluator, err = bexpr.CreateEvaluator(opts.Filter)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read filter expression: %v", err)
|
|
}
|
|
}
|
|
|
|
return &Paginator{
|
|
iter: iter,
|
|
perPage: opts.PerPage,
|
|
seekingToken: opts.NextToken,
|
|
nextTokenFound: opts.NextToken == "",
|
|
filterEvaluator: evaluator,
|
|
appendFunc: appendFunc,
|
|
}, nil
|
|
}
|
|
|
|
// Page populates a page by running the append function
|
|
// over all results. Returns the next token
|
|
func (p *Paginator) Page() (string, error) {
|
|
DONE:
|
|
for {
|
|
raw, andThen := p.next()
|
|
switch andThen {
|
|
case paginatorInclude:
|
|
err := p.appendFunc(raw)
|
|
if err != nil {
|
|
p.pageErr = err
|
|
break DONE
|
|
}
|
|
case paginatorSkip:
|
|
continue
|
|
case paginatorComplete:
|
|
break DONE
|
|
}
|
|
}
|
|
return p.nextToken, p.pageErr
|
|
}
|
|
|
|
func (p *Paginator) next() (interface{}, paginatorState) {
|
|
raw := p.iter.Next()
|
|
if raw == nil {
|
|
p.nextToken = ""
|
|
return nil, paginatorComplete
|
|
}
|
|
|
|
// have we found the token we're seeking (if any)?
|
|
id := raw.(IDGetter).GetID()
|
|
p.nextToken = id
|
|
if !p.nextTokenFound && id < p.seekingToken {
|
|
return nil, paginatorSkip
|
|
}
|
|
|
|
// apply filter if defined
|
|
if p.filterEvaluator != nil {
|
|
match, err := p.filterEvaluator.Evaluate(raw)
|
|
if err != nil {
|
|
p.pageErr = err
|
|
return nil, paginatorComplete
|
|
}
|
|
if !match {
|
|
return nil, paginatorSkip
|
|
}
|
|
}
|
|
|
|
p.nextTokenFound = true
|
|
|
|
// have we produced enough results for this page?
|
|
p.itemCount++
|
|
if p.perPage != 0 && p.itemCount > p.perPage {
|
|
return raw, paginatorComplete
|
|
}
|
|
|
|
return raw, paginatorInclude
|
|
}
|
|
|
|
// IDGetter must be implemented for the results of any iterator we
|
|
// want to paginate
|
|
type IDGetter interface {
|
|
GetID() string
|
|
}
|
|
|
|
type paginatorState int
|
|
|
|
const (
|
|
paginatorInclude paginatorState = iota
|
|
paginatorSkip
|
|
paginatorComplete
|
|
)
|