-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinternal.go
More file actions
152 lines (135 loc) · 4.24 KB
/
Copy pathinternal.go
File metadata and controls
152 lines (135 loc) · 4.24 KB
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
package tango
import (
"context"
"encoding/json"
"net/url"
"strconv"
)
// ListOptions is the shared embed for every list endpoint. Per-resource
// options structs embed this so callers can set pagination + shaping on
// any list call.
type ListOptions struct {
// Page is 1-based and used for offset-paginated endpoints. Mutually
// exclusive with Cursor; if both are set, Cursor wins.
Page int
// Limit is the page size. The server caps this at 100 on most
// endpoints. 0 means "use the server default".
Limit int
// Cursor is the keyset cursor for cursor-paginated endpoints (most
// list endpoints support it). Pass the Cursor field from the
// previous response.
Cursor string
// Shape is the comma-separated field selector for dynamic response
// shaping. Use one of the Shape* constants or roll your own. Empty
// means "use the API default for this endpoint".
Shape string
// Flat collapses nested objects into dot-separated keys in the
// response. Default false.
Flat bool
// FlatLists when true flattens list-valued nested fields as well.
// Only meaningful when Flat is true.
FlatLists bool
}
// applyTo writes pagination+shape fields into a url.Values, respecting
// "zero means absent". Called by every list method.
func (o *ListOptions) applyTo(q url.Values) {
if o == nil {
return
}
if o.Cursor != "" {
q.Set("cursor", o.Cursor)
} else if o.Page > 0 {
q.Set("page", strconv.Itoa(o.Page))
}
if o.Limit > 0 {
q.Set("limit", strconv.Itoa(o.Limit))
}
if o.Shape != "" {
q.Set("shape", o.Shape)
}
if o.Flat {
q.Set("flat", "true")
}
if o.FlatLists {
q.Set("flat_lists", "true")
}
}
// setIfNotEmpty writes key=value only when value isn't empty/zero.
func setIfNotEmpty(q url.Values, key, value string) {
if value != "" {
q.Set(key, value)
}
}
func setIfNonZeroInt(q url.Values, key string, value int) {
if value != 0 {
q.Set(key, strconv.Itoa(value))
}
}
func setIfNotNilBool(q url.Values, key string, value *bool) {
if value != nil {
q.Set(key, strconv.FormatBool(*value))
}
}
// listGeneric runs a GET against a list endpoint and returns a typed
// PaginatedResponse. Used by every list method on Client.
func listGeneric[T any](ctx context.Context, c *Client, path string, q url.Values) (*PaginatedResponse[T], error) {
raw, err := c.do(ctx, requestSpec{method: "GET", path: path, query: q})
if err != nil {
return nil, err
}
return decodePaginated[T](raw)
}
// getGeneric runs a GET against a detail endpoint and decodes into T.
func getGeneric[T any](ctx context.Context, c *Client, path string, q url.Values) (T, error) {
var zero T
raw, err := c.do(ctx, requestSpec{method: "GET", path: path, query: q})
if err != nil {
return zero, err
}
var out T
if err := json.Unmarshal(raw, &out); err != nil {
return zero, &APIError{Message: "decode response", Cause: err}
}
return out, nil
}
// postGeneric runs a POST and decodes the JSON response into T.
func postGeneric[T any](ctx context.Context, c *Client, path string, body any) (T, error) {
var zero T
raw, err := c.do(ctx, requestSpec{method: "POST", path: path, body: body})
if err != nil {
return zero, err
}
var out T
if len(raw) == 0 {
return out, nil
}
if err := json.Unmarshal(raw, &out); err != nil {
return zero, &APIError{Message: "decode response", Cause: err}
}
return out, nil
}
// patchGeneric runs a PATCH and decodes the JSON response into T.
func patchGeneric[T any](ctx context.Context, c *Client, path string, body any) (T, error) {
var zero T
raw, err := c.do(ctx, requestSpec{method: "PATCH", path: path, body: body})
if err != nil {
return zero, err
}
var out T
if len(raw) == 0 {
return out, nil
}
if err := json.Unmarshal(raw, &out); err != nil {
return zero, &APIError{Message: "decode response", Cause: err}
}
return out, nil
}
// deleteEndpoint runs a DELETE and discards the response.
func (c *Client) deleteEndpoint(ctx context.Context, path string) error {
_, err := c.do(ctx, requestSpec{method: "DELETE", path: path})
return err
}
// pathEscape is url.PathEscape with one extra quirk: the API rejects
// "+" for spaces in path segments, so we use PathEscape (which uses %20)
// rather than QueryEscape. Centralized so callers don't have to think.
func pathEscape(s string) string { return url.PathEscape(s) }