-
Notifications
You must be signed in to change notification settings - Fork 1
/
pagination.go
185 lines (149 loc) · 3.52 KB
/
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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
package pagination
import (
"encoding/json"
"fmt"
)
type StreamToken struct {
Size int
Cursor string
}
type StreamState struct {
Cursor string `json:"cursor"`
HasMore bool `json:"done"`
}
type Token struct {
Size int
Token string
}
type PageState struct {
Token string `json:"token"`
ResourceTypeID string `json:"resource_type_id"`
ResourceID string `json:"resource_id"`
}
// Bag holds pagination states that can be serialized for use as page tokens. It acts as a stack that you can push and pop
// pagination operations from.
type Bag struct {
states []PageState
currentState *PageState
}
type serializedPaginationBag struct {
States []PageState `json:"states"`
CurrentState *PageState `json:"current_state"`
}
func (pb *Bag) push(s PageState) {
if pb.currentState == nil {
pb.currentState = &s
return
}
pb.states = append(pb.states, *pb.currentState)
pb.currentState = &s
}
func (pb *Bag) pop() *PageState {
if pb.currentState == nil {
return nil
}
ret := *pb.currentState
if len(pb.states) > 0 {
pb.currentState = &pb.states[len(pb.states)-1]
pb.states = pb.states[:len(pb.states)-1]
} else {
pb.currentState = nil
}
return &ret
}
// Push pushes a new page state onto the stack.
func (pb *Bag) Push(state PageState) {
pb.push(state)
}
// Pop returns the current page action, and makes the top of the stack the current.
func (pb *Bag) Pop() *PageState {
return pb.pop()
}
// Next pops the current token, and pushes a copy of it with an updated page token.
func (pb *Bag) Next(pageToken string) error {
st := pb.pop()
if st == nil {
return fmt.Errorf("no active page state")
}
if pageToken != "" {
newState := *st
newState.Token = pageToken
pb.push(newState)
}
return nil
}
// Next pops the current token, and pushes a copy of it with an updated page token.
func (pb *Bag) NextToken(pageToken string) (string, error) {
st := pb.pop()
if st == nil {
return "", fmt.Errorf("no active page state")
}
if pageToken != "" {
newState := *st
newState.Token = pageToken
pb.push(newState)
}
return pb.Marshal()
}
// Current returns the current page state for the bag.
func (pb *Bag) Current() *PageState {
if pb.currentState == nil {
return nil
}
current := *pb.currentState
return ¤t
}
// Unmarshal takes an input string and unmarshals it onto the state object.
func (pb *Bag) Unmarshal(input string) error {
token := serializedPaginationBag{}
if input != "" {
err := json.Unmarshal([]byte(input), &token)
if err != nil {
return fmt.Errorf("page token corrupt: %w", err)
}
pb.states = token.States
pb.currentState = token.CurrentState
} else {
pb.states = nil
pb.currentState = nil
}
return nil
}
// Marshal returns a string encoding of the state object.
func (pb *Bag) Marshal() (string, error) {
if pb.currentState == nil {
return "", nil
}
data, err := json.Marshal(serializedPaginationBag{
States: pb.states,
CurrentState: pb.currentState,
})
if err != nil {
return "", err
}
return string(data), nil
}
// PageToken returns the page token for the current state.
func (pb *Bag) PageToken() string {
c := pb.Current()
if c == nil {
return ""
}
return c.Token
}
// ResourceTypeID returns the resource type id for the current state.
func (pb *Bag) ResourceTypeID() string {
c := pb.Current()
if c == nil {
return ""
}
return c.ResourceTypeID
}
// ResourceID returns the resource ID for the current state.
func (pb *Bag) ResourceID() string {
c := pb.Current()
if c == nil {
return ""
}
return c.ResourceID
}