Testing out a new pagination idiom.
diff --git a/collections.go b/collections.go
index 443a000..38078f0 100644
--- a/collections.go
+++ b/collections.go
@@ -1,65 +1,87 @@
package gophercloud
-import (
- "errors"
-
- "github.com/racker/perigee"
-)
+import "errors"
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.
+// Collection must be satisfied by the result type of any resource collection.
+// It allows clients to interact with the resource uniformly, regardless of whether or not or how it's paginated.
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
+ // NextPageURL generates the URL for the page of data that follows this collection.
+ // Return "" if no such page exists.
+ NextPageURL() string
- // Concat the contents of another collection on to the end of this one.
- // Return a new collection that contains elements from both.
+ // Concat creates a new Collection that contains all of the elements from this page and another page.
+ // It's used to aggregate results for the AllPages method.
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
+// Pager knows how to advance through a specific resource collection, one page at a time.
+type Pager struct {
+ initialURL string
+ advance func(string) (Collection, error)
+}
+
+// NewPager constructs a manually-configured pager.
+// Supply the URL for the first page and a function that requests a specific page given a URL.
+func NewPager(initialURL string, advance func(string) (Collection, error)) Pager {
+ return Pager{
+ initialURL: initialURL,
+ advance: advance,
+ }
+}
+
+// NewSinglePager constructs a Pager that "iterates" over a single-paged Collection.
+// Supply a function that returns the only page.
+func NewSinglePager(only func() (Collection, error)) Pager {
+ consumed := false
+ single := func(_ string) (Collection, error) {
+ if !consumed {
+ consumed = true
+ return only()
+ }
+ return nil, ErrPageNotAvailable
+ }
+
+ return Pager{
+ initialURL: "",
+ advance: single,
+ }
+}
+
+// EachPage iterates over each page returned by a Pager, yielding one at a time to a handler function.
+// Return "false" from the handler to prematurely stop iterating.
+func (p Pager) EachPage(handler func(Collection) bool) error {
+ currentURL := p.initialURL
for {
- if !handler(current) {
- return nil
- }
-
- if !p.HasNextPage() {
- return nil
- }
-
- current, err = p.NextPage()
+ currentPage, err := p.advance(currentURL)
if err != nil {
return err
}
+
+ if !handler(currentPage) {
+ return nil
+ }
+
+ currentURL = currentPage.NextPageURL()
+ if currentURL == "" {
+ return nil
+ }
}
}
-// 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
+// AllPages accumulates every page reachable from a Pager into a single Collection, for convenience.
+func (p Pager) AllPages() (Collection, error) {
+ var megaPage Collection
- err := EachPage(first, func(page Collection) bool {
- if isFirst {
- isFirst = false
+ err := p.EachPage(func(page Collection) bool {
+ if megaPage == nil {
+ megaPage = page
} else {
megaPage = megaPage.Concat(page)
}
@@ -69,34 +91,6 @@
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 {
@@ -106,59 +100,3 @@
// 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
-}