]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package request |
2 | ||
3 | import ( | |
4 | "reflect" | |
5 | "sync/atomic" | |
6 | ||
7 | "github.com/aws/aws-sdk-go/aws" | |
8 | "github.com/aws/aws-sdk-go/aws/awsutil" | |
9 | ) | |
10 | ||
11 | // A Pagination provides paginating of SDK API operations which are paginatable. | |
12 | // Generally you should not use this type directly, but use the "Pages" API | |
13 | // operations method to automatically perform pagination for you. Such as, | |
14 | // "S3.ListObjectsPages", and "S3.ListObjectsPagesWithContext" methods. | |
15 | // | |
16 | // Pagination differs from a Paginator type in that pagination is the type that | |
17 | // does the pagination between API operations, and Paginator defines the | |
18 | // configuration that will be used per page request. | |
19 | // | |
20 | // cont := true | |
21 | // for p.Next() && cont { | |
22 | // data := p.Page().(*s3.ListObjectsOutput) | |
23 | // // process the page's data | |
24 | // } | |
25 | // return p.Err() | |
26 | // | |
27 | // See service client API operation Pages methods for examples how the SDK will | |
28 | // use the Pagination type. | |
29 | type Pagination struct { | |
30 | // Function to return a Request value for each pagination request. | |
31 | // Any configuration or handlers that need to be applied to the request | |
32 | // prior to getting the next page should be done here before the request | |
33 | // returned. | |
34 | // | |
35 | // NewRequest should always be built from the same API operations. It is | |
36 | // undefined if different API operations are returned on subsequent calls. | |
37 | NewRequest func() (*Request, error) | |
38 | ||
39 | started bool | |
40 | nextTokens []interface{} | |
41 | ||
42 | err error | |
43 | curPage interface{} | |
44 | } | |
45 | ||
46 | // HasNextPage will return true if Pagination is able to determine that the API | |
47 | // operation has additional pages. False will be returned if there are no more | |
48 | // pages remaining. | |
49 | // | |
50 | // Will always return true if Next has not been called yet. | |
51 | func (p *Pagination) HasNextPage() bool { | |
52 | return !(p.started && len(p.nextTokens) == 0) | |
53 | } | |
54 | ||
55 | // Err returns the error Pagination encountered when retrieving the next page. | |
56 | func (p *Pagination) Err() error { | |
57 | return p.err | |
58 | } | |
59 | ||
60 | // Page returns the current page. Page should only be called after a successful | |
61 | // call to Next. It is undefined what Page will return if Page is called after | |
62 | // Next returns false. | |
63 | func (p *Pagination) Page() interface{} { | |
64 | return p.curPage | |
65 | } | |
66 | ||
67 | // Next will attempt to retrieve the next page for the API operation. When a page | |
68 | // is retrieved true will be returned. If the page cannot be retrieved, or there | |
69 | // are no more pages false will be returned. | |
70 | // | |
71 | // Use the Page method to retrieve the current page data. The data will need | |
72 | // to be cast to the API operation's output type. | |
73 | // | |
74 | // Use the Err method to determine if an error occurred if Page returns false. | |
75 | func (p *Pagination) Next() bool { | |
76 | if !p.HasNextPage() { | |
77 | return false | |
78 | } | |
79 | ||
80 | req, err := p.NewRequest() | |
81 | if err != nil { | |
82 | p.err = err | |
83 | return false | |
84 | } | |
85 | ||
86 | if p.started { | |
87 | for i, intok := range req.Operation.InputTokens { | |
88 | awsutil.SetValueAtPath(req.Params, intok, p.nextTokens[i]) | |
89 | } | |
90 | } | |
91 | p.started = true | |
92 | ||
93 | err = req.Send() | |
94 | if err != nil { | |
95 | p.err = err | |
96 | return false | |
97 | } | |
98 | ||
99 | p.nextTokens = req.nextPageTokens() | |
100 | p.curPage = req.Data | |
101 | ||
102 | return true | |
103 | } | |
104 | ||
105 | // A Paginator is the configuration data that defines how an API operation | |
106 | // should be paginated. This type is used by the API service models to define | |
107 | // the generated pagination config for service APIs. | |
108 | // | |
109 | // The Pagination type is what provides iterating between pages of an API. It | |
110 | // is only used to store the token metadata the SDK should use for performing | |
111 | // pagination. | |
112 | type Paginator struct { | |
113 | InputTokens []string | |
114 | OutputTokens []string | |
115 | LimitToken string | |
116 | TruncationToken string | |
117 | } | |
118 | ||
119 | // nextPageTokens returns the tokens to use when asking for the next page of data. | |
120 | func (r *Request) nextPageTokens() []interface{} { | |
121 | if r.Operation.Paginator == nil { | |
122 | return nil | |
123 | } | |
124 | if r.Operation.TruncationToken != "" { | |
125 | tr, _ := awsutil.ValuesAtPath(r.Data, r.Operation.TruncationToken) | |
126 | if len(tr) == 0 { | |
127 | return nil | |
128 | } | |
129 | ||
130 | switch v := tr[0].(type) { | |
131 | case *bool: | |
132 | if !aws.BoolValue(v) { | |
133 | return nil | |
134 | } | |
135 | case bool: | |
136 | if v == false { | |
137 | return nil | |
138 | } | |
139 | } | |
140 | } | |
141 | ||
142 | tokens := []interface{}{} | |
143 | tokenAdded := false | |
144 | for _, outToken := range r.Operation.OutputTokens { | |
145 | v, _ := awsutil.ValuesAtPath(r.Data, outToken) | |
146 | if len(v) > 0 { | |
147 | tokens = append(tokens, v[0]) | |
148 | tokenAdded = true | |
149 | } else { | |
150 | tokens = append(tokens, nil) | |
151 | } | |
152 | } | |
153 | if !tokenAdded { | |
154 | return nil | |
155 | } | |
156 | ||
157 | return tokens | |
158 | } | |
159 | ||
160 | // Ensure a deprecated item is only logged once instead of each time its used. | |
161 | func logDeprecatedf(logger aws.Logger, flag *int32, msg string) { | |
162 | if logger == nil { | |
163 | return | |
164 | } | |
165 | if atomic.CompareAndSwapInt32(flag, 0, 1) { | |
166 | logger.Log(msg) | |
167 | } | |
168 | } | |
169 | ||
170 | var ( | |
171 | logDeprecatedHasNextPage int32 | |
172 | logDeprecatedNextPage int32 | |
173 | logDeprecatedEachPage int32 | |
174 | ) | |
175 | ||
176 | // HasNextPage returns true if this request has more pages of data available. | |
177 | // | |
178 | // Deprecated Use Pagination type for configurable pagination of API operations | |
179 | func (r *Request) HasNextPage() bool { | |
180 | logDeprecatedf(r.Config.Logger, &logDeprecatedHasNextPage, | |
181 | "Request.HasNextPage deprecated. Use Pagination type for configurable pagination of API operations") | |
182 | ||
183 | return len(r.nextPageTokens()) > 0 | |
184 | } | |
185 | ||
186 | // NextPage returns a new Request that can be executed to return the next | |
187 | // page of result data. Call .Send() on this request to execute it. | |
188 | // | |
189 | // Deprecated Use Pagination type for configurable pagination of API operations | |
190 | func (r *Request) NextPage() *Request { | |
191 | logDeprecatedf(r.Config.Logger, &logDeprecatedNextPage, | |
192 | "Request.NextPage deprecated. Use Pagination type for configurable pagination of API operations") | |
193 | ||
194 | tokens := r.nextPageTokens() | |
195 | if len(tokens) == 0 { | |
196 | return nil | |
197 | } | |
198 | ||
199 | data := reflect.New(reflect.TypeOf(r.Data).Elem()).Interface() | |
200 | nr := New(r.Config, r.ClientInfo, r.Handlers, r.Retryer, r.Operation, awsutil.CopyOf(r.Params), data) | |
201 | for i, intok := range nr.Operation.InputTokens { | |
202 | awsutil.SetValueAtPath(nr.Params, intok, tokens[i]) | |
203 | } | |
204 | return nr | |
205 | } | |
206 | ||
207 | // EachPage iterates over each page of a paginated request object. The fn | |
208 | // parameter should be a function with the following sample signature: | |
209 | // | |
210 | // func(page *T, lastPage bool) bool { | |
211 | // return true // return false to stop iterating | |
212 | // } | |
213 | // | |
214 | // Where "T" is the structure type matching the output structure of the given | |
215 | // operation. For example, a request object generated by | |
216 | // DynamoDB.ListTablesRequest() would expect to see dynamodb.ListTablesOutput | |
217 | // as the structure "T". The lastPage value represents whether the page is | |
218 | // the last page of data or not. The return value of this function should | |
219 | // return true to keep iterating or false to stop. | |
220 | // | |
221 | // Deprecated Use Pagination type for configurable pagination of API operations | |
222 | func (r *Request) EachPage(fn func(data interface{}, isLastPage bool) (shouldContinue bool)) error { | |
223 | logDeprecatedf(r.Config.Logger, &logDeprecatedEachPage, | |
224 | "Request.EachPage deprecated. Use Pagination type for configurable pagination of API operations") | |
225 | ||
226 | for page := r; page != nil; page = page.NextPage() { | |
227 | if err := page.Send(); err != nil { | |
228 | return err | |
229 | } | |
230 | if getNextPage := fn(page.Data, !page.HasNextPage()); !getNextPage { | |
231 | return page.Error | |
232 | } | |
233 | } | |
234 | ||
235 | return nil | |
236 | } |