Ash Wilson | 64d67b2 | 2014-09-05 13:04:12 -0400 | [diff] [blame] | 1 | package gophercloud |
| 2 | |
Ash Wilson | e30b76b | 2014-09-12 08:36:17 -0400 | [diff] [blame^] | 3 | import "errors" |
Ash Wilson | 64d67b2 | 2014-09-05 13:04:12 -0400 | [diff] [blame] | 4 | |
| 5 | var ( |
| 6 | // ErrPageNotAvailable is returned from a Pager when a next or previous page is requested, but does not exist. |
| 7 | ErrPageNotAvailable = errors.New("The requested Collection page does not exist.") |
| 8 | ) |
| 9 | |
Ash Wilson | e30b76b | 2014-09-12 08:36:17 -0400 | [diff] [blame^] | 10 | // Collection must be satisfied by the result type of any resource collection. |
| 11 | // It allows clients to interact with the resource uniformly, regardless of whether or not or how it's paginated. |
Ash Wilson | 64d67b2 | 2014-09-05 13:04:12 -0400 | [diff] [blame] | 12 | type Collection interface { |
| 13 | |
Ash Wilson | e30b76b | 2014-09-12 08:36:17 -0400 | [diff] [blame^] | 14 | // NextPageURL generates the URL for the page of data that follows this collection. |
| 15 | // Return "" if no such page exists. |
| 16 | NextPageURL() string |
Ash Wilson | b110fc9 | 2014-09-08 13:54:59 -0400 | [diff] [blame] | 17 | |
Ash Wilson | e30b76b | 2014-09-12 08:36:17 -0400 | [diff] [blame^] | 18 | // Concat creates a new Collection that contains all of the elements from this page and another page. |
| 19 | // It's used to aggregate results for the AllPages method. |
Ash Wilson | b110fc9 | 2014-09-08 13:54:59 -0400 | [diff] [blame] | 20 | Concat(Collection) Collection |
Ash Wilson | 64d67b2 | 2014-09-05 13:04:12 -0400 | [diff] [blame] | 21 | } |
| 22 | |
Ash Wilson | e30b76b | 2014-09-12 08:36:17 -0400 | [diff] [blame^] | 23 | // Pager knows how to advance through a specific resource collection, one page at a time. |
| 24 | type Pager struct { |
| 25 | initialURL string |
Ash Wilson | 64d67b2 | 2014-09-05 13:04:12 -0400 | [diff] [blame] | 26 | |
Ash Wilson | e30b76b | 2014-09-12 08:36:17 -0400 | [diff] [blame^] | 27 | advance func(string) (Collection, error) |
| 28 | } |
| 29 | |
| 30 | // NewPager constructs a manually-configured pager. |
| 31 | // Supply the URL for the first page and a function that requests a specific page given a URL. |
| 32 | func NewPager(initialURL string, advance func(string) (Collection, error)) Pager { |
| 33 | return Pager{ |
| 34 | initialURL: initialURL, |
| 35 | advance: advance, |
| 36 | } |
| 37 | } |
| 38 | |
| 39 | // NewSinglePager constructs a Pager that "iterates" over a single-paged Collection. |
| 40 | // Supply a function that returns the only page. |
| 41 | func NewSinglePager(only func() (Collection, error)) Pager { |
| 42 | consumed := false |
| 43 | single := func(_ string) (Collection, error) { |
| 44 | if !consumed { |
| 45 | consumed = true |
| 46 | return only() |
| 47 | } |
| 48 | return nil, ErrPageNotAvailable |
| 49 | } |
| 50 | |
| 51 | return Pager{ |
| 52 | initialURL: "", |
| 53 | advance: single, |
| 54 | } |
| 55 | } |
| 56 | |
| 57 | // EachPage iterates over each page returned by a Pager, yielding one at a time to a handler function. |
| 58 | // Return "false" from the handler to prematurely stop iterating. |
| 59 | func (p Pager) EachPage(handler func(Collection) bool) error { |
| 60 | currentURL := p.initialURL |
Ash Wilson | 64d67b2 | 2014-09-05 13:04:12 -0400 | [diff] [blame] | 61 | for { |
Ash Wilson | e30b76b | 2014-09-12 08:36:17 -0400 | [diff] [blame^] | 62 | currentPage, err := p.advance(currentURL) |
Ash Wilson | 64d67b2 | 2014-09-05 13:04:12 -0400 | [diff] [blame] | 63 | if err != nil { |
| 64 | return err |
| 65 | } |
Ash Wilson | e30b76b | 2014-09-12 08:36:17 -0400 | [diff] [blame^] | 66 | |
| 67 | if !handler(currentPage) { |
| 68 | return nil |
| 69 | } |
| 70 | |
| 71 | currentURL = currentPage.NextPageURL() |
| 72 | if currentURL == "" { |
| 73 | return nil |
| 74 | } |
Ash Wilson | 64d67b2 | 2014-09-05 13:04:12 -0400 | [diff] [blame] | 75 | } |
| 76 | } |
| 77 | |
Ash Wilson | e30b76b | 2014-09-12 08:36:17 -0400 | [diff] [blame^] | 78 | // AllPages accumulates every page reachable from a Pager into a single Collection, for convenience. |
| 79 | func (p Pager) AllPages() (Collection, error) { |
| 80 | var megaPage Collection |
Ash Wilson | b110fc9 | 2014-09-08 13:54:59 -0400 | [diff] [blame] | 81 | |
Ash Wilson | e30b76b | 2014-09-12 08:36:17 -0400 | [diff] [blame^] | 82 | err := p.EachPage(func(page Collection) bool { |
| 83 | if megaPage == nil { |
| 84 | megaPage = page |
Ash Wilson | b110fc9 | 2014-09-08 13:54:59 -0400 | [diff] [blame] | 85 | } else { |
| 86 | megaPage = megaPage.Concat(page) |
| 87 | } |
| 88 | return true |
| 89 | }) |
| 90 | |
| 91 | return megaPage, err |
Ash Wilson | 64d67b2 | 2014-09-05 13:04:12 -0400 | [diff] [blame] | 92 | } |
| 93 | |
Ash Wilson | 64d67b2 | 2014-09-05 13:04:12 -0400 | [diff] [blame] | 94 | // PaginationLinks stores the `next` and `previous` links that are provided by some (but not all) paginated resources. |
| 95 | type PaginationLinks struct { |
| 96 | |
| 97 | // Next is the full URL to the next page of results, or nil if this is the last page. |
| 98 | Next *string `json:"next,omitempty"` |
| 99 | |
| 100 | // Previous is the full URL to the previous page of results, or nil if this is the first page. |
| 101 | Previous *string `json:"previous,omitempty"` |
| 102 | } |