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