blob: 75fe408a76c9a6ff71cec0929b31b6e11d3ea964 [file] [log] [blame]
Jamie Hannaford2aaf1a62014-10-16 12:55:50 +02001package pagination
2
3import (
4 "errors"
5
6 "github.com/rackspace/gophercloud"
7)
8
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 client *gophercloud.ServiceClient
33
34 initialURL string
35
36 createPage func(r LastHTTPResponse) Page
37
38 Err error
39
40 // Headers supplies additional HTTP headers to populate on each paged request.
41 Headers map[string]string
42}
43
44// NewPager constructs a manually-configured pager.
45// Supply the URL for the first page, a function that requests a specific page given a URL, and a function that counts a page.
46func NewPager(client *gophercloud.ServiceClient, initialURL string, createPage func(r LastHTTPResponse) Page) Pager {
47 return Pager{
48 client: client,
49 initialURL: initialURL,
50 createPage: createPage,
51 }
52}
53
54// WithPageCreator returns a new Pager that substitutes a different page creation function. This is
55// useful for overriding List functions in delegation.
56func (p Pager) WithPageCreator(createPage func(r LastHTTPResponse) Page) Pager {
57 return Pager{
58 client: p.client,
59 initialURL: p.initialURL,
60 createPage: createPage,
61 }
62}
63
64func (p Pager) fetchNextPage(url string) (Page, error) {
65 resp, err := Request(p.client, p.Headers, url)
66 if err != nil {
67 return nil, err
68 }
69
70 remembered, err := RememberHTTPResponse(resp)
71 if err != nil {
72 return nil, err
73 }
74
75 return p.createPage(remembered), nil
76}
77
78// EachPage iterates over each page returned by a Pager, yielding one at a time to a handler function.
79// Return "false" from the handler to prematurely stop iterating.
80func (p Pager) EachPage(handler func(Page) (bool, error)) error {
81 if p.Err != nil {
82 return p.Err
83 }
84 currentURL := p.initialURL
85 for {
86 currentPage, err := p.fetchNextPage(currentURL)
87 if err != nil {
88 return err
89 }
90
91 empty, err := currentPage.IsEmpty()
92 if err != nil {
93 return err
94 }
95 if empty {
96 return nil
97 }
98
99 ok, err := handler(currentPage)
100 if err != nil {
101 return err
102 }
103 if !ok {
104 return nil
105 }
106
107 currentURL, err = currentPage.NextPageURL()
108 if err != nil {
109 return err
110 }
111 if currentURL == "" {
112 return nil
113 }
114 }
115}