Code refactoring for bpa operator
[icn.git] / cmd / bpa-operator / vendor / github.com / gophercloud / gophercloud / pagination / pager.go
1 package pagination
2
3 import (
4         "errors"
5         "fmt"
6         "net/http"
7         "reflect"
8         "strings"
9
10         "github.com/gophercloud/gophercloud"
11 )
12
13 var (
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.")
16 )
17
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,
21 // instead.
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.
24 type Page interface {
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)
28
29         // IsEmpty returns true if this Page has no items in it.
30         IsEmpty() (bool, error)
31
32         // GetBody returns the Page Body. This is used in the `AllPages` method.
33         GetBody() interface{}
34 }
35
36 // Pager knows how to advance through a specific resource collection, one page at a time.
37 type Pager struct {
38         client *gophercloud.ServiceClient
39
40         initialURL string
41
42         createPage func(r PageResult) Page
43
44         firstPage Page
45
46         Err error
47
48         // Headers supplies additional HTTP headers to populate on each paged request.
49         Headers map[string]string
50 }
51
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 {
55         return Pager{
56                 client:     client,
57                 initialURL: initialURL,
58                 createPage: createPage,
59         }
60 }
61
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 {
65         return Pager{
66                 client:     p.client,
67                 initialURL: p.initialURL,
68                 createPage: createPage,
69         }
70 }
71
72 func (p Pager) fetchNextPage(url string) (Page, error) {
73         resp, err := Request(p.client, p.Headers, url)
74         if err != nil {
75                 return nil, err
76         }
77
78         remembered, err := PageResultFrom(resp)
79         if err != nil {
80                 return nil, err
81         }
82
83         return p.createPage(remembered), nil
84 }
85
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 {
89         if p.Err != nil {
90                 return p.Err
91         }
92         currentURL := p.initialURL
93         for {
94                 var currentPage Page
95
96                 // if first page has already been fetched, no need to fetch it again
97                 if p.firstPage != nil {
98                         currentPage = p.firstPage
99                         p.firstPage = nil
100                 } else {
101                         var err error
102                         currentPage, err = p.fetchNextPage(currentURL)
103                         if err != nil {
104                                 return err
105                         }
106                 }
107
108                 empty, err := currentPage.IsEmpty()
109                 if err != nil {
110                         return err
111                 }
112                 if empty {
113                         return nil
114                 }
115
116                 ok, err := handler(currentPage)
117                 if err != nil {
118                         return err
119                 }
120                 if !ok {
121                         return nil
122                 }
123
124                 currentURL, err = currentPage.NextPageURL()
125                 if err != nil {
126                         return err
127                 }
128                 if currentURL == "" {
129                         return nil
130                 }
131         }
132 }
133
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
141
142         // Grab a first page to ascertain the page body type.
143         firstPage, err := p.fetchNextPage(p.initialURL)
144         if err != nil {
145                 return nil, err
146         }
147         // Store the page type so we can use reflection to create a new mega-page of
148         // that type.
149         pageType := reflect.TypeOf(firstPage)
150
151         // if it's a single page, just return the firstPage (first page)
152         if _, found := pageType.FieldByName("SinglePageBase"); found {
153                 return firstPage, nil
154         }
155
156         // store the first page to avoid getting it twice
157         p.firstPage = firstPage
158
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{}`.
164                 var key string
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) {
173                                         case []interface{}:
174                                                 key = k
175                                                 pagesSlice = append(pagesSlice, vt...)
176                                         }
177                                 }
178                         }
179                         return true, nil
180                 })
181                 if err != nil {
182                         return nil, err
183                 }
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))
187         case []byte:
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})
194                         return true, nil
195                 })
196                 if err != nil {
197                         return nil, err
198                 }
199                 if len(pagesSlice) > 0 {
200                         // Remove the trailing comma.
201                         pagesSlice = pagesSlice[:len(pagesSlice)-1]
202                 }
203                 var b []byte
204                 // Combine the slice of slices in to a single slice.
205                 for _, slice := range pagesSlice {
206                         b = append(b, slice.([]byte)...)
207                 }
208                 // Set body to value of type `bytes`.
209                 body = reflect.New(reflect.TypeOf(b)).Elem()
210                 body.SetBytes(b)
211         case []interface{}:
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...)
216                         return true, nil
217                 })
218                 if err != nil {
219                         return nil, err
220                 }
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))
225                 }
226         default:
227                 err := gophercloud.ErrUnexpectedType{}
228                 err.Expected = "map[string]interface{}/[]byte/[]interface{}"
229                 err.Actual = fmt.Sprintf("%T", pb)
230                 return nil, err
231         }
232
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
237         // pages.
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 {
245                 h.Add(k, v)
246         }
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
251 }