| 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 |
| } |