-
Notifications
You must be signed in to change notification settings - Fork 12
/
framework_pagination.go
133 lines (105 loc) · 2.84 KB
/
framework_pagination.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
package gimlet
import (
"fmt"
"net/url"
"strings"
"github.com/pkg/errors"
"github.com/mongodb/grip"
"github.com/mongodb/grip/message"
)
// ResponsePages holds pagination metadata for a route built with the
// Responder interface.
//
// The pagination types and methods are
type ResponsePages struct {
Next *Page
Prev *Page
}
// GetLinks returns the strings for use in the links header
func (r *ResponsePages) GetLinks(route string) string {
if strings.HasPrefix(route, "/") {
route = route[1:]
}
links := []string{}
if r.Next != nil {
links = append(links, r.Next.GetLink(route))
}
if r.Prev != nil {
links = append(links, r.Prev.GetLink(route))
}
return strings.Join(links, ",")
}
// Validate checks each page, if present, and ensures that the
// pagination metadata are consistent.
func (r *ResponsePages) Validate() error {
catcher := grip.NewCatcher()
for _, p := range []*Page{r.Next, r.Prev} {
if p == nil {
continue
}
catcher.Add(p.Validate())
}
return catcher.Resolve()
}
// Page represents the metadata required to build pagination links. To
// build the page, the route must have access to the full realized
// path, including any extra query parameters, to make it possible to
// build the metadata.
type Page struct {
BaseURL string
KeyQueryParam string
LimitQueryParam string
Key string
Limit int
Relation string
url *url.URL
}
// Validate ensures that the page has populated all of the required
// data. Additionally Validate parses the BaseURL, and *must* be
// called before GetLink.
func (p *Page) Validate() error {
errs := []string{}
if p.BaseURL == "" {
errs = append(errs, "base url not specified")
}
if p.KeyQueryParam == "" {
errs = append(errs, "key query parameter name not specified")
}
if p.LimitQueryParam == "" {
errs = append(errs, "limit query parameter name not specified")
}
if p.Relation == "" {
errs = append(errs, "page relation not specified")
}
if p.Key == "" {
errs = append(errs, "limit not specified")
}
_, err := url.Parse(p.BaseURL)
if err != nil {
errs = append(errs, err.Error())
}
if len(errs) > 0 {
return errors.New(strings.Join(errs, "; "))
}
return nil
}
// GetLink returns the pagination metadata for this page. It is called
// by the GetLinks function. Your code need not use this call
// directly in most cases, except for testing.
func (p *Page) GetLink(route string) string {
url, err := url.Parse(fmt.Sprintf("%s/%s", p.BaseURL, route))
if err != nil {
grip.Alert(message.WrapError(err, message.Fields{
"message": "failed to build page, falling back to base URL",
"base_url": p.BaseURL,
}))
url = p.url
}
q := url.Query()
q.Set(p.KeyQueryParam, p.Key)
if p.Limit != 0 {
q.Set(p.LimitQueryParam, fmt.Sprintf("%d", p.Limit))
}
url.RawQuery = q.Encode()
return fmt.Sprintf("<%s>; rel=\"%s\"", url, p.Relation)
}