blob: 806d98a103cf190f5b028aee34bb5524e40ef80e [file] [log] [blame]
Ash Wilsonc8e68872014-09-16 10:36:56 -04001package pagination
2
Ash Wilson7049af42014-09-16 13:04:48 -04003import (
4 "errors"
5
6 "github.com/rackspace/gophercloud"
7)
Ash Wilsonc8e68872014-09-16 10:36:56 -04008
9var (
10 // ErrPageNotAvailable is returned from a Pager when a next or previous page is requested, but does not exist.
11 ErrPageNotAvailable = errors.New("The requested page does not exist.")
12)
13
14// Page must be satisfied by the result type of any resource collection.
15// It allows clients to interact with the resource uniformly, regardless of whether or not or how it's paginated.
16// Generally, rather than implementing this interface directly, implementors should embed one of the concrete PageBase structs,
17// instead.
18// Depending on the pagination strategy of a particular resource, there may be an additional subinterface that the result type
19// will need to implement.
20type Page interface {
21
22 // NextPageURL generates the URL for the page of data that follows this collection.
23 // Return "" if no such page exists.
24 NextPageURL() (string, error)
25
26 // IsEmpty returns true if this Page has no items in it.
27 IsEmpty() (bool, error)
28}
29
30// Pager knows how to advance through a specific resource collection, one page at a time.
31type Pager struct {
32 initialURL string
33
Ash Wilson7049af42014-09-16 13:04:48 -040034 client *gophercloud.ServiceClient
35
36 createPage func(r LastHTTPResponse) Page
Ash Wilsona7402472014-09-16 15:18:34 -040037
38 // Headers supplies additional HTTP headers to populate on each paged request.
39 Headers map[string]string
Ash Wilsonc8e68872014-09-16 10:36:56 -040040}
41
42// NewPager constructs a manually-configured pager.
43// Supply the URL for the first page, a function that requests a specific page given a URL, and a function that counts a page.
Ash Wilson7049af42014-09-16 13:04:48 -040044func NewPager(client *gophercloud.ServiceClient, initialURL string, createPage func(r LastHTTPResponse) Page) Pager {
Ash Wilsonc8e68872014-09-16 10:36:56 -040045 return Pager{
Ash Wilson7049af42014-09-16 13:04:48 -040046 initialURL: initialURL,
47 client: client,
48 createPage: createPage,
Ash Wilsonc8e68872014-09-16 10:36:56 -040049 }
50}
51
Ash Wilson7049af42014-09-16 13:04:48 -040052func (p Pager) fetchNextPage(url string) (Page, error) {
Ash Wilsona7402472014-09-16 15:18:34 -040053 resp, err := Request(p.client, p.Headers, url)
Ash Wilson7049af42014-09-16 13:04:48 -040054 if err != nil {
55 return nil, err
56 }
57
58 remembered, err := RememberHTTPResponse(resp)
59 if err != nil {
60 return nil, err
61 }
62
63 return p.createPage(remembered), nil
64}
65
Ash Wilsonc8e68872014-09-16 10:36:56 -040066// EachPage iterates over each page returned by a Pager, yielding one at a time to a handler function.
67// Return "false" from the handler to prematurely stop iterating.
68func (p Pager) EachPage(handler func(Page) (bool, error)) error {
69 currentURL := p.initialURL
70 for {
71 currentPage, err := p.fetchNextPage(currentURL)
72 if err != nil {
73 return err
74 }
75
76 empty, err := currentPage.IsEmpty()
77 if err != nil {
78 return err
79 }
80 if empty {
81 return nil
82 }
83
84 ok, err := handler(currentPage)
85 if err != nil {
86 return err
87 }
88 if !ok {
89 return nil
90 }
91
92 currentURL, err = currentPage.NextPageURL()
93 if err != nil {
94 return err
95 }
96 if currentURL == "" {
97 return nil
98 }
99 }
100}