1package linodego
2
3/**
4 * Pagination and Filtering types and helpers
5 */
6
7import (
8	"context"
9	"fmt"
10	"log"
11	"strconv"
12
13	"gopkg.in/resty.v1"
14)
15
16// PageOptions are the pagination parameters for List endpoints
17type PageOptions struct {
18	Page    int `url:"page,omitempty" json:"page"`
19	Pages   int `url:"pages,omitempty" json:"pages"`
20	Results int `url:"results,omitempty" json:"results"`
21}
22
23// ListOptions are the pagination and filtering (TODO) parameters for endpoints
24type ListOptions struct {
25	*PageOptions
26	Filter string
27}
28
29// NewListOptions simplified construction of ListOptions using only
30// the two writable properties, Page and Filter
31func NewListOptions(Page int, Filter string) *ListOptions {
32	return &ListOptions{PageOptions: &PageOptions{Page: Page}, Filter: Filter}
33
34}
35
36// listHelper abstracts fetching and pagination for GET endpoints that
37// do not require any Ids (top level endpoints).
38// When opts (or opts.Page) is nil, all pages will be fetched and
39// returned in a single (endpoint-specific)PagedResponse
40// opts.results and opts.pages will be updated from the API response
41func (c *Client) listHelper(ctx context.Context, i interface{}, opts *ListOptions) error {
42	req := c.R(ctx)
43	if opts != nil && opts.PageOptions != nil && opts.Page > 0 {
44		req.SetQueryParam("page", strconv.Itoa(opts.Page))
45	}
46
47	var (
48		err     error
49		pages   int
50		results int
51		r       *resty.Response
52	)
53
54	if opts != nil && len(opts.Filter) > 0 {
55		req.SetHeader("X-Filter", opts.Filter)
56	}
57
58	switch v := i.(type) {
59	case *LinodeKernelsPagedResponse:
60		if r, err = coupleAPIErrors(req.SetResult(LinodeKernelsPagedResponse{}).Get(v.endpoint(c))); err == nil {
61			pages = r.Result().(*LinodeKernelsPagedResponse).Pages
62			results = r.Result().(*LinodeKernelsPagedResponse).Results
63			v.appendData(r.Result().(*LinodeKernelsPagedResponse))
64		}
65	case *LinodeTypesPagedResponse:
66		if r, err = coupleAPIErrors(req.SetResult(LinodeTypesPagedResponse{}).Get(v.endpoint(c))); err == nil {
67			pages = r.Result().(*LinodeTypesPagedResponse).Pages
68			results = r.Result().(*LinodeTypesPagedResponse).Results
69			v.appendData(r.Result().(*LinodeTypesPagedResponse))
70		}
71	case *ImagesPagedResponse:
72		if r, err = coupleAPIErrors(req.SetResult(ImagesPagedResponse{}).Get(v.endpoint(c))); err == nil {
73			pages = r.Result().(*ImagesPagedResponse).Pages
74			results = r.Result().(*ImagesPagedResponse).Results
75			v.appendData(r.Result().(*ImagesPagedResponse))
76		}
77	case *StackscriptsPagedResponse:
78		if r, err = coupleAPIErrors(req.SetResult(StackscriptsPagedResponse{}).Get(v.endpoint(c))); err == nil {
79			pages = r.Result().(*StackscriptsPagedResponse).Pages
80			results = r.Result().(*StackscriptsPagedResponse).Results
81			v.appendData(r.Result().(*StackscriptsPagedResponse))
82		}
83	case *InstancesPagedResponse:
84		if r, err = coupleAPIErrors(req.SetResult(InstancesPagedResponse{}).Get(v.endpoint(c))); err == nil {
85			pages = r.Result().(*InstancesPagedResponse).Pages
86			results = r.Result().(*InstancesPagedResponse).Results
87			v.appendData(r.Result().(*InstancesPagedResponse))
88		}
89	case *RegionsPagedResponse:
90		if r, err = coupleAPIErrors(req.SetResult(RegionsPagedResponse{}).Get(v.endpoint(c))); err == nil {
91			pages = r.Result().(*RegionsPagedResponse).Pages
92			results = r.Result().(*RegionsPagedResponse).Results
93			v.appendData(r.Result().(*RegionsPagedResponse))
94		}
95	case *VolumesPagedResponse:
96		if r, err = coupleAPIErrors(req.SetResult(VolumesPagedResponse{}).Get(v.endpoint(c))); err == nil {
97			pages = r.Result().(*VolumesPagedResponse).Pages
98			results = r.Result().(*VolumesPagedResponse).Results
99			v.appendData(r.Result().(*VolumesPagedResponse))
100		}
101	case *DomainsPagedResponse:
102		if r, err = coupleAPIErrors(req.SetResult(DomainsPagedResponse{}).Get(v.endpoint(c))); err == nil {
103			response, ok := r.Result().(*DomainsPagedResponse)
104			if !ok {
105				return fmt.Errorf("Response is not a *DomainsPagedResponse")
106			}
107			pages = response.Pages
108			results = response.Results
109			v.appendData(response)
110		}
111	case *EventsPagedResponse:
112		if r, err = coupleAPIErrors(req.SetResult(EventsPagedResponse{}).Get(v.endpoint(c))); err == nil {
113			pages = r.Result().(*EventsPagedResponse).Pages
114			results = r.Result().(*EventsPagedResponse).Results
115			v.appendData(r.Result().(*EventsPagedResponse))
116		}
117	case *LongviewSubscriptionsPagedResponse:
118		if r, err = coupleAPIErrors(req.SetResult(LongviewSubscriptionsPagedResponse{}).Get(v.endpoint(c))); err == nil {
119			pages = r.Result().(*LongviewSubscriptionsPagedResponse).Pages
120			results = r.Result().(*LongviewSubscriptionsPagedResponse).Results
121			v.appendData(r.Result().(*LongviewSubscriptionsPagedResponse))
122		}
123	case *LongviewClientsPagedResponse:
124		if r, err = coupleAPIErrors(req.SetResult(LongviewClientsPagedResponse{}).Get(v.endpoint(c))); err == nil {
125			pages = r.Result().(*LongviewClientsPagedResponse).Pages
126			results = r.Result().(*LongviewClientsPagedResponse).Results
127			v.appendData(r.Result().(*LongviewClientsPagedResponse))
128		}
129	case *IPAddressesPagedResponse:
130		if r, err = coupleAPIErrors(req.SetResult(IPAddressesPagedResponse{}).Get(v.endpoint(c))); err == nil {
131			pages = r.Result().(*IPAddressesPagedResponse).Pages
132			results = r.Result().(*IPAddressesPagedResponse).Results
133			v.appendData(r.Result().(*IPAddressesPagedResponse))
134		}
135	case *IPv6PoolsPagedResponse:
136		if r, err = coupleAPIErrors(req.SetResult(IPv6PoolsPagedResponse{}).Get(v.endpoint(c))); err == nil {
137			pages = r.Result().(*IPv6PoolsPagedResponse).Pages
138			results = r.Result().(*IPv6PoolsPagedResponse).Results
139			v.appendData(r.Result().(*IPv6PoolsPagedResponse))
140		}
141	case *IPv6RangesPagedResponse:
142		if r, err = coupleAPIErrors(req.SetResult(IPv6RangesPagedResponse{}).Get(v.endpoint(c))); err == nil {
143			pages = r.Result().(*IPv6RangesPagedResponse).Pages
144			results = r.Result().(*IPv6RangesPagedResponse).Results
145			v.appendData(r.Result().(*IPv6RangesPagedResponse))
146			// @TODO consolidate this type with IPv6PoolsPagedResponse?
147		}
148	case *SSHKeysPagedResponse:
149		if r, err = coupleAPIErrors(req.SetResult(SSHKeysPagedResponse{}).Get(v.endpoint(c))); err == nil {
150			response, ok := r.Result().(*SSHKeysPagedResponse)
151			if !ok {
152				return fmt.Errorf("Response is not a *SSHKeysPagedResponse")
153			}
154			pages = response.Pages
155			results = response.Results
156			v.appendData(response)
157		}
158	case *TicketsPagedResponse:
159		if r, err = coupleAPIErrors(req.SetResult(TicketsPagedResponse{}).Get(v.endpoint(c))); err == nil {
160			pages = r.Result().(*TicketsPagedResponse).Pages
161			results = r.Result().(*TicketsPagedResponse).Results
162			v.appendData(r.Result().(*TicketsPagedResponse))
163		}
164	case *InvoicesPagedResponse:
165		if r, err = coupleAPIErrors(req.SetResult(InvoicesPagedResponse{}).Get(v.endpoint(c))); err == nil {
166			pages = r.Result().(*InvoicesPagedResponse).Pages
167			results = r.Result().(*InvoicesPagedResponse).Results
168			v.appendData(r.Result().(*InvoicesPagedResponse))
169		}
170	case *NotificationsPagedResponse:
171		if r, err = coupleAPIErrors(req.SetResult(NotificationsPagedResponse{}).Get(v.endpoint(c))); err == nil {
172			pages = r.Result().(*NotificationsPagedResponse).Pages
173			results = r.Result().(*NotificationsPagedResponse).Results
174			v.appendData(r.Result().(*NotificationsPagedResponse))
175		}
176	case *NodeBalancersPagedResponse:
177		if r, err = coupleAPIErrors(req.SetResult(NodeBalancersPagedResponse{}).Get(v.endpoint(c))); err == nil {
178			pages = r.Result().(*NodeBalancersPagedResponse).Pages
179			results = r.Result().(*NodeBalancersPagedResponse).Results
180			v.appendData(r.Result().(*NodeBalancersPagedResponse))
181		}
182	case *TagsPagedResponse:
183		if r, err = coupleAPIErrors(req.SetResult(TagsPagedResponse{}).Get(v.endpoint(c))); err == nil {
184			pages = r.Result().(*TagsPagedResponse).Pages
185			results = r.Result().(*TagsPagedResponse).Results
186			v.appendData(r.Result().(*TagsPagedResponse))
187		}
188	case *TokensPagedResponse:
189		if r, err = coupleAPIErrors(req.SetResult(TokensPagedResponse{}).Get(v.endpoint(c))); err == nil {
190			pages = r.Result().(*TokensPagedResponse).Pages
191			results = r.Result().(*TokensPagedResponse).Results
192			v.appendData(r.Result().(*TokensPagedResponse))
193		}
194	case *UsersPagedResponse:
195		if r, err = coupleAPIErrors(req.SetResult(UsersPagedResponse{}).Get(v.endpoint(c))); err == nil {
196			pages = r.Result().(*UsersPagedResponse).Pages
197			results = r.Result().(*UsersPagedResponse).Results
198			v.appendData(r.Result().(*UsersPagedResponse))
199		}
200	/**
201	case AccountOauthClientsPagedResponse:
202	case AccountPaymentsPagedResponse:
203	case ProfileAppsPagedResponse:
204	case ProfileWhitelistPagedResponse:
205	case ManagedContactsPagedResponse:
206	case ManagedCredentialsPagedResponse:
207	case ManagedIssuesPagedResponse:
208	case ManagedLinodeSettingsPagedResponse:
209	case ManagedServicesPagedResponse:
210	**/
211	default:
212		log.Fatalf("listHelper interface{} %+v used", i)
213	}
214
215	if err != nil {
216		return err
217	}
218
219	if opts == nil {
220		for page := 2; page <= pages; page = page + 1 {
221			if err := c.listHelper(ctx, i, &ListOptions{PageOptions: &PageOptions{Page: page}}); err != nil {
222				return err
223			}
224		}
225	} else {
226		if opts.PageOptions == nil {
227			opts.PageOptions = &PageOptions{}
228		}
229
230		if opts.Page == 0 {
231			for page := 2; page <= pages; page = page + 1 {
232				opts.Page = page
233				if err := c.listHelper(ctx, i, opts); err != nil {
234					return err
235				}
236			}
237		}
238		opts.Results = results
239		opts.Pages = pages
240	}
241
242	return nil
243}
244
245// listHelperWithID abstracts fetching and pagination for GET endpoints that
246// require an Id (second level endpoints).
247// When opts (or opts.Page) is nil, all pages will be fetched and
248// returned in a single (endpoint-specific)PagedResponse
249// opts.results and opts.pages will be updated from the API response
250func (c *Client) listHelperWithID(ctx context.Context, i interface{}, idRaw interface{}, opts *ListOptions) error {
251	req := c.R(ctx)
252	if opts != nil && opts.Page > 0 {
253		req.SetQueryParam("page", strconv.Itoa(opts.Page))
254	}
255
256	var (
257		err     error
258		pages   int
259		results int
260		r       *resty.Response
261	)
262
263	id, _ := idRaw.(int)
264
265	if opts != nil && len(opts.Filter) > 0 {
266		req.SetHeader("X-Filter", opts.Filter)
267	}
268
269	switch v := i.(type) {
270	case *InvoiceItemsPagedResponse:
271		if r, err = coupleAPIErrors(req.SetResult(InvoiceItemsPagedResponse{}).Get(v.endpointWithID(c, id))); err == nil {
272			pages = r.Result().(*InvoiceItemsPagedResponse).Pages
273			results = r.Result().(*InvoiceItemsPagedResponse).Results
274			v.appendData(r.Result().(*InvoiceItemsPagedResponse))
275		}
276	case *DomainRecordsPagedResponse:
277		if r, err = coupleAPIErrors(req.SetResult(DomainRecordsPagedResponse{}).Get(v.endpointWithID(c, id))); err == nil {
278			response, ok := r.Result().(*DomainRecordsPagedResponse)
279			if !ok {
280				return fmt.Errorf("Response is not a *DomainRecordsPagedResponse")
281			}
282			pages = response.Pages
283			results = response.Results
284			v.appendData(response)
285		}
286	case *InstanceConfigsPagedResponse:
287		if r, err = coupleAPIErrors(req.SetResult(InstanceConfigsPagedResponse{}).Get(v.endpointWithID(c, id))); err == nil {
288			pages = r.Result().(*InstanceConfigsPagedResponse).Pages
289			results = r.Result().(*InstanceConfigsPagedResponse).Results
290			v.appendData(r.Result().(*InstanceConfigsPagedResponse))
291		}
292	case *InstanceDisksPagedResponse:
293		if r, err = coupleAPIErrors(req.SetResult(InstanceDisksPagedResponse{}).Get(v.endpointWithID(c, id))); err == nil {
294			pages = r.Result().(*InstanceDisksPagedResponse).Pages
295			results = r.Result().(*InstanceDisksPagedResponse).Results
296			v.appendData(r.Result().(*InstanceDisksPagedResponse))
297		}
298	case *NodeBalancerConfigsPagedResponse:
299		if r, err = coupleAPIErrors(req.SetResult(NodeBalancerConfigsPagedResponse{}).Get(v.endpointWithID(c, id))); err == nil {
300			pages = r.Result().(*NodeBalancerConfigsPagedResponse).Pages
301			results = r.Result().(*NodeBalancerConfigsPagedResponse).Results
302			v.appendData(r.Result().(*NodeBalancerConfigsPagedResponse))
303		}
304	case *InstanceVolumesPagedResponse:
305		if r, err = coupleAPIErrors(req.SetResult(InstanceVolumesPagedResponse{}).Get(v.endpointWithID(c, id))); err == nil {
306			pages = r.Result().(*InstanceVolumesPagedResponse).Pages
307			results = r.Result().(*InstanceVolumesPagedResponse).Results
308			v.appendData(r.Result().(*InstanceVolumesPagedResponse))
309		}
310	case *TaggedObjectsPagedResponse:
311		idStr := idRaw.(string)
312
313		if r, err = coupleAPIErrors(req.SetResult(TaggedObjectsPagedResponse{}).Get(v.endpointWithID(c, idStr))); err == nil {
314			pages = r.Result().(*TaggedObjectsPagedResponse).Pages
315			results = r.Result().(*TaggedObjectsPagedResponse).Results
316			v.appendData(r.Result().(*TaggedObjectsPagedResponse))
317		}
318	/**
319	case TicketAttachmentsPagedResponse:
320		if r, err = req.SetResult(v).Get(v.endpoint(c)); r.Error() != nil {
321			return NewError(r)
322		} else if err == nil {
323			pages = r.Result().(*TicketAttachmentsPagedResponse).Pages
324			results = r.Result().(*TicketAttachmentsPagedResponse).Results
325			v.appendData(r.Result().(*TicketAttachmentsPagedResponse))
326		}
327	case TicketRepliesPagedResponse:
328		if r, err = req.SetResult(v).Get(v.endpoint(c)); r.Error() != nil {
329			return NewError(r)
330		} else if err == nil {
331			pages = r.Result().(*TicketRepliesPagedResponse).Pages
332			results = r.Result().(*TicketRepliesPagedResponse).Results
333			v.appendData(r.Result().(*TicketRepliesPagedResponse))
334		}
335	**/
336	default:
337		log.Fatalf("Unknown listHelperWithID interface{} %T used", i)
338	}
339
340	if err != nil {
341		return err
342	}
343
344	if opts == nil {
345		for page := 2; page <= pages; page = page + 1 {
346			if err := c.listHelperWithID(ctx, i, id, &ListOptions{PageOptions: &PageOptions{Page: page}}); err != nil {
347				return err
348			}
349		}
350	} else {
351		if opts.PageOptions == nil {
352			opts.PageOptions = &PageOptions{}
353		}
354		if opts.Page == 0 {
355			for page := 2; page <= pages; page = page + 1 {
356				opts.Page = page
357				if err := c.listHelperWithID(ctx, i, id, opts); err != nil {
358					return err
359				}
360			}
361		}
362		opts.Results = results
363		opts.Pages = pages
364	}
365
366	return nil
367}
368
369// listHelperWithTwoIDs abstracts fetching and pagination for GET endpoints that
370// require twos IDs (third level endpoints).
371// When opts (or opts.Page) is nil, all pages will be fetched and
372// returned in a single (endpoint-specific)PagedResponse
373// opts.results and opts.pages will be updated from the API response
374func (c *Client) listHelperWithTwoIDs(ctx context.Context, i interface{}, firstID, secondID int, opts *ListOptions) error {
375	req := c.R(ctx)
376	if opts != nil && opts.Page > 0 {
377		req.SetQueryParam("page", strconv.Itoa(opts.Page))
378	}
379
380	var (
381		err     error
382		pages   int
383		results int
384		r       *resty.Response
385	)
386
387	if opts != nil && len(opts.Filter) > 0 {
388		req.SetHeader("X-Filter", opts.Filter)
389	}
390
391	switch v := i.(type) {
392	case *NodeBalancerNodesPagedResponse:
393		if r, err = coupleAPIErrors(req.SetResult(NodeBalancerNodesPagedResponse{}).Get(v.endpointWithTwoIDs(c, firstID, secondID))); err == nil {
394			pages = r.Result().(*NodeBalancerNodesPagedResponse).Pages
395			results = r.Result().(*NodeBalancerNodesPagedResponse).Results
396			v.appendData(r.Result().(*NodeBalancerNodesPagedResponse))
397		}
398
399	default:
400		log.Fatalf("Unknown listHelperWithTwoIDs interface{} %T used", i)
401	}
402
403	if err != nil {
404		return err
405	}
406
407	if opts == nil {
408		for page := 2; page <= pages; page = page + 1 {
409			if err := c.listHelper(ctx, i, &ListOptions{PageOptions: &PageOptions{Page: page}}); err != nil {
410				return err
411			}
412		}
413	} else {
414		if opts.PageOptions == nil {
415			opts.PageOptions = &PageOptions{}
416		}
417		if opts.Page == 0 {
418			for page := 2; page <= pages; page = page + 1 {
419				opts.Page = page
420				if err := c.listHelperWithTwoIDs(ctx, i, firstID, secondID, opts); err != nil {
421					return err
422				}
423			}
424		}
425		opts.Results = results
426		opts.Pages = pages
427	}
428
429	return nil
430}
431