blob: 82e6c7a0091cefabb30b05d3a378a453b7c8c5f4 [file] [log] [blame]
Ash Wilson64d67b22014-09-05 13:04:12 -04001package gophercloud
2
3import (
4 "errors"
5
6 "github.com/racker/perigee"
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 Collection page does not exist.")
12)
13
14// Collection describes the minimum functionality that any collection resource must implement to be able to use
15// the global paging and iteration functions.
16// Every resource that returns a list of multiple results must implement this functionality, whether or not it is paged.
17// In addition to the methods provided here, each collection should also provide an AsItem(Page) method that
18// casts the Page to its more specific type and returns the Page's contents as a slice.
19type Collection interface {
20
21 // Pager returns one of the concrete Pager implementations from this package, or a custom one.
22 // The style of Pager returned determines how the collection is paged.
23 Pager() Pager
24}
25
26// EachPage iterates through a Collection one page at a time.
27// The handler function will be invoked with a Collection containing each page.
28// If the handler returns true, iteration will continue. If it returns false, no more pages will be fetched.
29func EachPage(first Collection, handler func(Collection) bool) error {
30 p := first.Pager()
31 var err error
32 current := first
33
34 for {
35 if !handler(current) {
36 return nil
37 }
38
39 if !p.HasNextPage() {
40 return nil
41 }
42
43 current, err = p.NextPage()
44 if err != nil {
45 return err
46 }
47 }
48}
49
50// AllPages consolidates all pages reachable from a provided starting point into a single mega-Page.
51// Use this only when you know that the full set will always fit within memory.
52func AllPages(first Collection) (Collection, error) {
53 return first, nil
54}
55
56// Pager describes a specific paging idiom for a Page resource.
57// Generally, to use a Pager, the Page must also implement a more specialized interface than Page.
58// Clients should not generally interact with Pagers directly.
59// Instead, use the more convenient collection traversal methods: All and EachPage.
60type Pager interface {
61
62 // HasNextPage returns true if a call to NextPage will return an additional Page of results.
63 HasNextPage() bool
64
65 // NextPage returns the next Page in the sequence.
66 // Panics if no page is available, so always check HasNextPage first.
67 NextPage() (Collection, error)
68}
69
70// SinglePager is used by collections that are not actually paged.
71// It has no additional interface requirements for its host Page.
72type SinglePager struct{}
73
74// HasNextPage always reports false.
75func (p SinglePager) HasNextPage() bool {
76 return false
77}
78
79// NextPage always returns an ErrPageNotAvailable.
80func (p SinglePager) NextPage() (Collection, error) {
81 return nil, ErrPageNotAvailable
82}
83
84// PaginationLinks stores the `next` and `previous` links that are provided by some (but not all) paginated resources.
85type PaginationLinks struct {
86
87 // Next is the full URL to the next page of results, or nil if this is the last page.
88 Next *string `json:"next,omitempty"`
89
90 // Previous is the full URL to the previous page of results, or nil if this is the first page.
91 Previous *string `json:"previous,omitempty"`
92}
93
94// LinkCollection must be satisfied by a Page that uses a LinkPager.
95type LinkCollection interface {
Ash Wilson612df9e2014-09-08 08:59:12 -040096 Collection
Ash Wilson64d67b22014-09-05 13:04:12 -040097
98 // Service returns the client used to make further requests.
99 Service() *ServiceClient
100
101 // Links returns the pagination links from a single page.
102 Links() PaginationLinks
103
104 // Interpret an arbitrary JSON result as a new LinkCollection.
105 Interpret(interface{}) (LinkCollection, error)
106}
107
108// LinkPager implements paging for collections that provide a link structure in their response JSON.
109// It follows explicit `next` links and stops when the `next` link is "null".
110type LinkPager struct {
111 current LinkCollection
112}
113
114// NewLinkPager creates and initializes a pager for a LinkCollection.
115func NewLinkPager(first LinkCollection) *LinkPager {
116 return &LinkPager{current: first}
117}
118
119// HasNextPage checks the `next` link in the pagination data.
120func (p *LinkPager) HasNextPage() bool {
121 return p.current.Links().Next != nil
122}
123
124// NextPage follows the `next` link to construct the next page of data.
125func (p *LinkPager) NextPage() (Collection, error) {
126 url := p.current.Links().Next
127 if url == nil {
128 return nil, ErrPageNotAvailable
129 }
130
131 var response interface{}
132 _, err := perigee.Request("GET", *url, perigee.Options{
133 MoreHeaders: p.current.Service().Provider.AuthenticatedHeaders(),
134 Results: &response,
135 OkCodes: []int{200},
136 })
137 if err != nil {
138 return nil, err
139 }
140
141 interpreted, err := p.current.Interpret(response)
142 if err != nil {
143 return nil, err
144 }
145
146 p.current = interpreted
147 return interpreted, nil
148}