forked from stripe/stripe-go
-
Notifications
You must be signed in to change notification settings - Fork 0
/
params.go
271 lines (232 loc) · 7.08 KB
/
params.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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
package stripe
import (
"bytes"
"crypto/rand"
"encoding/base64"
"fmt"
"net/url"
"strconv"
"time"
)
const (
startafter = "starting_after"
endbefore = "ending_before"
)
// RequestValues is a collection of values that can be submitted along with a
// request that specifically allows for duplicate keys and encodes its entries
// in the same order that they were added.
type RequestValues struct {
values []formValue
}
// Add adds a key/value tuple to the form.
func (f *RequestValues) Add(key, val string) {
f.values = append(f.values, formValue{key, val})
}
// Encode encodes the values into “URL encoded” form ("bar=baz&foo=quux").
func (f *RequestValues) Encode() string {
var buf bytes.Buffer
for _, v := range f.values {
if buf.Len() > 0 {
buf.WriteByte('&')
}
buf.WriteString(url.QueryEscape(v.Key))
buf.WriteString("=")
buf.WriteString(url.QueryEscape(v.Value))
}
return buf.String()
}
// Empty returns true if no parameters have been set.
func (f *RequestValues) Empty() bool {
return len(f.values) == 0
}
// Set sets the first instance of a parameter for the given key to the given
// value. If no parameters exist with the key, a new one is added.
//
// Note that Set is O(n) and may be quite slow for a very large parameter list.
func (f *RequestValues) Set(key, val string) {
for i, v := range f.values {
if v.Key == key {
f.values[i].Value = val
return
}
}
f.Add(key, val)
}
// ToValues converts an instance of RequestValues into an instance of
// url.Values. This can be useful in cases where it's useful to make an
// unordered comparison of two sets of request values.
//
// Note that url.Values is incapable of representing certain Rack form types in
// a cohesive way. For example, an array of maps in Rack is encoded with a
// string like:
//
// arr[][foo]=foo0&arr[][bar]=bar0&arr[][foo]=foo1&arr[][bar]=bar1
//
// Because url.Values is a map, values will be handled in a way that's grouped
// by their key instead of in the order they were added. Therefore the above
// may by encoded to something like (maps are unordered so the actual result is
// somewhat non-deterministic):
//
// arr[][foo]=foo0&arr[][foo]=foo1&arr[][bar]=bar0&arr[][bar]=bar1
//
// And thus result in an incorrect request to Stripe.
func (f *RequestValues) ToValues() url.Values {
values := url.Values{}
for _, v := range f.values {
values.Add(v.Key, v.Value)
}
return values
}
// A key/value tuple for use in the RequestValues type.
type formValue struct {
Key string
Value string
}
// Params is the structure that contains the common properties
// of any *Params structure.
type Params struct {
Exp []string
Meta map[string]string
Extra url.Values
IdempotencyKey string
// StripeAccount may contain the ID of a connected account. By including
// this field, the request is made as if it originated from the connected
// account instead of under the account of the owner of the configured
// Stripe key.
StripeAccount string
// Account is deprecated form of StripeAccount that will do the same thing.
// Please use StripeAccount instead.
Account string
}
// ListParams is the structure that contains the common properties
// of any *ListParams structure.
type ListParams struct {
Exp []string
Start, End string
Limit int
Filters Filters
// By default, listing through an iterator will automatically grab
// additional pages as the query progresses. To change this behavior
// and just load a single page, set this to true.
Single bool
// StripeAccount may contain the ID of a connected account. By including
// this field, the request is made as if it originated from the connected
// account instead of under the account of the owner of the configured
// Stripe key.
StripeAccount string
}
// ListMeta is the structure that contains the common properties
// of List iterators. The Count property is only populated if the
// total_count include option is passed in (see tests for example).
type ListMeta struct {
Count uint32 `json:"total_count"`
More bool `json:"has_more"`
URL string `json:"url"`
}
// Filters is a structure that contains a collection of filters for list-related APIs.
type Filters struct {
f []*filter
}
// AppendTo adds the list of filters to the query string values.
func (f *Filters) AppendTo(values *RequestValues) {
for _, v := range f.f {
if len(v.Op) > 0 {
values.Add(fmt.Sprintf("%v[%v]", v.Key, v.Op), v.Val)
} else {
values.Add(v.Key, v.Val)
}
}
}
// filter is the structure that contains a filter for list-related APIs.
// It ends up passing query string parameters in the format key[op]=value.
type filter struct {
Key, Op, Val string
}
// NewIdempotencyKey generates a new idempotency key that
// can be used on a request.
func NewIdempotencyKey() string {
now := time.Now().UnixNano()
buf := make([]byte, 4)
rand.Read(buf)
return fmt.Sprintf("%v_%v", now, base64.URLEncoding.EncodeToString(buf)[:6])
}
// SetAccount sets a value for the Stripe-Account header.
func (p *Params) SetAccount(val string) {
p.Account = val
p.StripeAccount = val
}
// SetStripeAccount sets a value for the Stripe-Account header.
func (p *Params) SetStripeAccount(val string) {
p.StripeAccount = val
}
// Expand appends a new field to expand.
func (p *Params) Expand(f string) {
p.Exp = append(p.Exp, f)
}
// AddMeta adds a new key-value pair to the Metadata.
func (p *Params) AddMeta(key, value string) {
if p.Meta == nil {
p.Meta = make(map[string]string)
}
p.Meta[key] = value
}
// AddExtra adds a new arbitrary key-value pair to the request data
func (p *Params) AddExtra(key, value string) {
if p.Extra == nil {
p.Extra = make(url.Values)
}
p.Extra.Add(key, value)
}
// AddFilter adds a new filter with a given key, op and value.
func (f *Filters) AddFilter(key, op, value string) {
filter := &filter{Key: key, Op: op, Val: value}
f.f = append(f.f, filter)
}
// AppendTo adds the common parameters to the query string values.
func (p *Params) AppendTo(body *RequestValues) {
for k, v := range p.Meta {
body.Add(fmt.Sprintf("metadata[%v]", k), v)
}
for _, v := range p.Exp {
body.Add("expand[]", v)
}
for k, vs := range p.Extra {
for _, v := range vs {
body.Add(k, v)
}
}
}
// Expand appends a new field to expand.
func (p *ListParams) Expand(f string) {
p.Exp = append(p.Exp, f)
}
// AppendTo adds the common parameters to the query string values.
func (p *ListParams) AppendTo(body *RequestValues) {
if len(p.Filters.f) > 0 {
p.Filters.AppendTo(body)
}
if len(p.Start) > 0 {
body.Add(startafter, p.Start)
}
if len(p.End) > 0 {
body.Add(endbefore, p.End)
}
if p.Limit > 0 {
if p.Limit > 100 {
p.Limit = 100
}
body.Add("limit", strconv.Itoa(p.Limit))
}
for _, v := range p.Exp {
body.Add("expand[]", v)
}
}
// ToParams converts a ListParams to a Params by moving over any fields that
// have valid targets in the new type. This is useful because fields in
// Params can be injected directly into an http.Request while generally
// ListParams is only used to build a set of parameters.
func (p *ListParams) ToParams() *Params {
return &Params{
StripeAccount: p.StripeAccount,
}
}