10 "github.com/gophercloud/gophercloud"
14 // ErrPageNotAvailable is returned from a Pager when a next or previous page is requested, but does not exist.
15 ErrPageNotAvailable = errors.New("The requested page does not exist.")
18 // Page must be satisfied by the result type of any resource collection.
19 // It allows clients to interact with the resource uniformly, regardless of whether or not or how it's paginated.
20 // Generally, rather than implementing this interface directly, implementors should embed one of the concrete PageBase structs,
22 // Depending on the pagination strategy of a particular resource, there may be an additional subinterface that the result type
23 // will need to implement.
25 // NextPageURL generates the URL for the page of data that follows this collection.
26 // Return "" if no such page exists.
27 NextPageURL() (string, error)
29 // IsEmpty returns true if this Page has no items in it.
30 IsEmpty() (bool, error)
32 // GetBody returns the Page Body. This is used in the `AllPages` method.
36 // Pager knows how to advance through a specific resource collection, one page at a time.
38 client *gophercloud.ServiceClient
42 createPage func(r PageResult) Page
48 // Headers supplies additional HTTP headers to populate on each paged request.
49 Headers map[string]string
52 // NewPager constructs a manually-configured pager.
53 // Supply the URL for the first page, a function that requests a specific page given a URL, and a function that counts a page.
54 func NewPager(client *gophercloud.ServiceClient, initialURL string, createPage func(r PageResult) Page) Pager {
57 initialURL: initialURL,
58 createPage: createPage,
62 // WithPageCreator returns a new Pager that substitutes a different page creation function. This is
63 // useful for overriding List functions in delegation.
64 func (p Pager) WithPageCreator(createPage func(r PageResult) Page) Pager {
67 initialURL: p.initialURL,
68 createPage: createPage,
72 func (p Pager) fetchNextPage(url string) (Page, error) {
73 resp, err := Request(p.client, p.Headers, url)
78 remembered, err := PageResultFrom(resp)
83 return p.createPage(remembered), nil
86 // EachPage iterates over each page returned by a Pager, yielding one at a time to a handler function.
87 // Return "false" from the handler to prematurely stop iterating.
88 func (p Pager) EachPage(handler func(Page) (bool, error)) error {
92 currentURL := p.initialURL
96 // if first page has already been fetched, no need to fetch it again
97 if p.firstPage != nil {
98 currentPage = p.firstPage
102 currentPage, err = p.fetchNextPage(currentURL)
108 empty, err := currentPage.IsEmpty()
116 ok, err := handler(currentPage)
124 currentURL, err = currentPage.NextPageURL()
128 if currentURL == "" {
134 // AllPages returns all the pages from a `List` operation in a single page,
135 // allowing the user to retrieve all the pages at once.
136 func (p Pager) AllPages() (Page, error) {
137 // pagesSlice holds all the pages until they get converted into as Page Body.
138 var pagesSlice []interface{}
139 // body will contain the final concatenated Page body.
140 var body reflect.Value
142 // Grab a first page to ascertain the page body type.
143 firstPage, err := p.fetchNextPage(p.initialURL)
147 // Store the page type so we can use reflection to create a new mega-page of
149 pageType := reflect.TypeOf(firstPage)
151 // if it's a single page, just return the firstPage (first page)
152 if _, found := pageType.FieldByName("SinglePageBase"); found {
153 return firstPage, nil
156 // store the first page to avoid getting it twice
157 p.firstPage = firstPage
159 // Switch on the page body type. Recognized types are `map[string]interface{}`,
160 // `[]byte`, and `[]interface{}`.
161 switch pb := firstPage.GetBody().(type) {
162 case map[string]interface{}:
163 // key is the map key for the page body if the body type is `map[string]interface{}`.
165 // Iterate over the pages to concatenate the bodies.
166 err = p.EachPage(func(page Page) (bool, error) {
167 b := page.GetBody().(map[string]interface{})
168 for k, v := range b {
169 // If it's a linked page, we don't want the `links`, we want the other one.
170 if !strings.HasSuffix(k, "links") {
171 // check the field's type. we only want []interface{} (which is really []map[string]interface{})
172 switch vt := v.(type) {
175 pagesSlice = append(pagesSlice, vt...)
184 // Set body to value of type `map[string]interface{}`
185 body = reflect.MakeMap(reflect.MapOf(reflect.TypeOf(key), reflect.TypeOf(pagesSlice)))
186 body.SetMapIndex(reflect.ValueOf(key), reflect.ValueOf(pagesSlice))
188 // Iterate over the pages to concatenate the bodies.
189 err = p.EachPage(func(page Page) (bool, error) {
190 b := page.GetBody().([]byte)
191 pagesSlice = append(pagesSlice, b)
192 // seperate pages with a comma
193 pagesSlice = append(pagesSlice, []byte{10})
199 if len(pagesSlice) > 0 {
200 // Remove the trailing comma.
201 pagesSlice = pagesSlice[:len(pagesSlice)-1]
204 // Combine the slice of slices in to a single slice.
205 for _, slice := range pagesSlice {
206 b = append(b, slice.([]byte)...)
208 // Set body to value of type `bytes`.
209 body = reflect.New(reflect.TypeOf(b)).Elem()
212 // Iterate over the pages to concatenate the bodies.
213 err = p.EachPage(func(page Page) (bool, error) {
214 b := page.GetBody().([]interface{})
215 pagesSlice = append(pagesSlice, b...)
221 // Set body to value of type `[]interface{}`
222 body = reflect.MakeSlice(reflect.TypeOf(pagesSlice), len(pagesSlice), len(pagesSlice))
223 for i, s := range pagesSlice {
224 body.Index(i).Set(reflect.ValueOf(s))
227 err := gophercloud.ErrUnexpectedType{}
228 err.Expected = "map[string]interface{}/[]byte/[]interface{}"
229 err.Actual = fmt.Sprintf("%T", pb)
233 // Each `Extract*` function is expecting a specific type of page coming back,
234 // otherwise the type assertion in those functions will fail. pageType is needed
235 // to create a type in this method that has the same type that the `Extract*`
236 // function is expecting and set the Body of that object to the concatenated
238 page := reflect.New(pageType)
239 // Set the page body to be the concatenated pages.
240 page.Elem().FieldByName("Body").Set(body)
241 // Set any additional headers that were pass along. The `objectstorage` pacakge,
242 // for example, passes a Content-Type header.
243 h := make(http.Header)
244 for k, v := range p.Headers {
247 page.Elem().FieldByName("Header").Set(reflect.ValueOf(h))
248 // Type assert the page to a Page interface so that the type assertion in the
249 // `Extract*` methods will work.
250 return page.Elem().Interface().(Page), err