all page marker/single/linked ops and unit tests
diff --git a/pagination/pager.go b/pagination/pager.go
index 5c20e16..8e55837 100644
--- a/pagination/pager.go
+++ b/pagination/pager.go
@@ -2,6 +2,8 @@
import (
"errors"
+ "fmt"
+ "reflect"
"github.com/rackspace/gophercloud"
)
@@ -25,6 +27,9 @@
// IsEmpty returns true if this Page has no items in it.
IsEmpty() (bool, error)
+
+ // GetBody returns the Page Body. This is used in the `AllPages`.
+ GetBody() interface{}
}
// Pager knows how to advance through a specific resource collection, one page at a time.
@@ -39,6 +44,8 @@
// Headers supplies additional HTTP headers to populate on each paged request.
Headers map[string]string
+
+ PageType Page
}
// NewPager constructs a manually-configured pager.
@@ -113,3 +120,84 @@
}
}
}
+
+// AllPages returns all the pages from a `List` operation in a single page,
+// allowing the user to retrieve all the pages at once.
+func (p Pager) AllPages() (Page, error) {
+ // pagesSlice holds all the pages until they get converted into as Page Body.
+ var pagesSlice []interface{}
+ // body will contain the final concatenated Page body.
+ var body reflect.Value
+
+ // Grab a test page to ascertain the page body type.
+ testPage, err := p.fetchNextPage(p.initialURL)
+ if err != nil {
+ return nil, err
+ }
+
+ // Switch on the page body type. Recognized types are `map[string]interface{}`
+ // and `[]byte`.
+ switch testPage.GetBody().(type) {
+ case map[string]interface{}:
+ // key is the map key for the page body if the body type is `map[string]interface{}`.
+ var key string
+ // Iterate over the pages to concatenate the bodies.
+ err := p.EachPage(func(page Page) (bool, error) {
+ b := page.GetBody().(map[string]interface{})
+ for k := range b {
+ // If it's a linked page, we don't want the `links`, we want the other one.
+ if k != "links" {
+ key = k
+ }
+ }
+ pagesSlice = append(pagesSlice, b[key].([]interface{})...)
+ return true, nil
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ // Set body to value of type `map[string]interface{}`
+ body = reflect.MakeMap(reflect.MapOf(reflect.TypeOf(key), reflect.TypeOf(pagesSlice)))
+ body.SetMapIndex(reflect.ValueOf(key), reflect.ValueOf(pagesSlice))
+
+ case []byte:
+ // Iterate over the pages to concatenate the bodies.
+ err := p.EachPage(func(page Page) (bool, error) {
+ b := page.GetBody().([]byte)
+ pagesSlice = append(pagesSlice, b)
+ // seperate pages with a comma
+ pagesSlice = append(pagesSlice, []byte{10})
+ return true, nil
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ // Remove the last comma.
+ pagesSlice = pagesSlice[:len(pagesSlice)-1]
+ var b []byte
+ // Combine the slice of slices in to a single slice.
+ for _, slice := range pagesSlice {
+ b = append(b, slice.([]byte)...)
+ }
+ // Set body to value of type `bytes`.
+ body = reflect.New(reflect.TypeOf(b)).Elem()
+ body.SetBytes(b)
+
+ default:
+ return nil, fmt.Errorf("Page body has unrecognized type.")
+ }
+
+ // Each `Extract*` function is expecting a specific type of page coming back,
+ // otherwise the type assertion in those functions will fail. PageType is needed
+ // to create a type in this method that has the same type that the `Extract*`
+ // function is expecting and set the Body of that object to the concatenated
+ // pages.
+ page := reflect.New(reflect.TypeOf(p.PageType))
+ page.Elem().FieldByName("Body").Set(body)
+
+ // Type assert the page to a Page interface so that the type assertion in the
+ // `Extract*` methods will work.
+ return page.Elem().Interface().(Page), err
+}