blob: 443a000f4cd443e81f575262ab3e5e3635da6e07 [file] [log] [blame]
package gophercloud
import (
"errors"
"github.com/racker/perigee"
)
var (
// ErrPageNotAvailable is returned from a Pager when a next or previous page is requested, but does not exist.
ErrPageNotAvailable = errors.New("The requested Collection page does not exist.")
)
// Collection describes the minimum functionality that any collection resource must implement to be able to use
// the global paging and iteration functions.
// Every resource that returns a list of multiple results must implement this functionality, whether or not it is paged.
// In addition to the methods provided here, each collection should also provide an AsItem(Page) method that
// casts the Page to its more specific type and returns the Page's contents as a slice.
type Collection interface {
// Pager returns one of the concrete Pager implementations from this package, or a custom one.
// The style of Pager returned determines how the collection is paged.
Pager() Pager
// Concat the contents of another collection on to the end of this one.
// Return a new collection that contains elements from both.
Concat(Collection) Collection
}
// EachPage iterates through a Collection one page at a time.
// The handler function will be invoked with a Collection containing each page.
// If the handler returns true, iteration will continue. If it returns false, no more pages will be fetched.
func EachPage(first Collection, handler func(Collection) bool) error {
p := first.Pager()
var err error
current := first
for {
if !handler(current) {
return nil
}
if !p.HasNextPage() {
return nil
}
current, err = p.NextPage()
if err != nil {
return err
}
}
}
// AllPages consolidates all pages reachable from a provided starting point into a single mega-Page.
// Use this only when you know that the full set will always fit within memory.
func AllPages(first Collection) (Collection, error) {
megaPage := first
isFirst := true
err := EachPage(first, func(page Collection) bool {
if isFirst {
isFirst = false
} else {
megaPage = megaPage.Concat(page)
}
return true
})
return megaPage, err
}
// Pager describes a specific paging idiom for a Collection resource.
// Generally, to use a Pager, the Collection must also implement a more specialized interface than Collection.
// Clients should not generally interact with Pagers directly.
// Instead, use the more convenient collection traversal methods: AllPages and EachPage.
type Pager interface {
// HasNextPage returns true if a call to NextPage will return an additional Page of results.
HasNextPage() bool
// NextPage returns the next Page in the sequence.
// Panics if no page is available, so always check HasNextPage first.
NextPage() (Collection, error)
}
// SinglePager is used by collections that are not actually paged.
// It has no additional interface requirements for its host Page.
type SinglePager struct{}
// HasNextPage always reports false.
func (p SinglePager) HasNextPage() bool {
return false
}
// NextPage always returns an ErrPageNotAvailable.
func (p SinglePager) NextPage() (Collection, error) {
return nil, ErrPageNotAvailable
}
// PaginationLinks stores the `next` and `previous` links that are provided by some (but not all) paginated resources.
type PaginationLinks struct {
// Next is the full URL to the next page of results, or nil if this is the last page.
Next *string `json:"next,omitempty"`
// Previous is the full URL to the previous page of results, or nil if this is the first page.
Previous *string `json:"previous,omitempty"`
}
// LinkCollection must be satisfied by a Page that uses a LinkPager.
type LinkCollection interface {
Collection
// Service returns the client used to make further requests.
Service() *ServiceClient
// Links returns the pagination links from a single page.
Links() PaginationLinks
// Interpret an arbitrary JSON result as a new LinkCollection.
Interpret(interface{}) (LinkCollection, error)
}
// LinkPager implements paging for collections that provide a link structure in their response JSON.
// It follows explicit `next` links and stops when the `next` link is "null".
type LinkPager struct {
current LinkCollection
}
// NewLinkPager creates and initializes a pager for a LinkCollection.
func NewLinkPager(first LinkCollection) *LinkPager {
return &LinkPager{current: first}
}
// HasNextPage checks the `next` link in the pagination data.
func (p *LinkPager) HasNextPage() bool {
return p.current.Links().Next != nil
}
// NextPage follows the `next` link to construct the next page of data.
func (p *LinkPager) NextPage() (Collection, error) {
url := p.current.Links().Next
if url == nil {
return nil, ErrPageNotAvailable
}
var response interface{}
_, err := perigee.Request("GET", *url, perigee.Options{
MoreHeaders: p.current.Service().Provider.AuthenticatedHeaders(),
Results: &response,
OkCodes: []int{200},
})
if err != nil {
return nil, err
}
interpreted, err := p.current.Interpret(response)
if err != nil {
return nil, err
}
p.current = interpreted
return interpreted, nil
}