terminal-shop 0.1.0.pre.alpha.13 → 0.1.0.pre.alpha.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/lib/terminal-shop/base_client.rb +12 -12
- data/lib/terminal-shop/base_model.rb +4 -3
- data/lib/terminal-shop/client.rb +12 -1
- data/lib/terminal-shop/pooled_net_requester.rb +3 -3
- data/lib/terminal-shop/util.rb +440 -432
- data/lib/terminal-shop/version.rb +1 -1
- data/rbi/lib/terminal-shop/base_client.rbi +22 -3
- data/rbi/lib/terminal-shop/client.rbi +6 -0
- data/rbi/lib/terminal-shop/pooled_net_requester.rbi +1 -3
- data/rbi/lib/terminal-shop/util.rbi +26 -30
- data/rbi/lib/terminal-shop/version.rbi +1 -1
- data/sig/terminal-shop/base_client.rbs +9 -3
- data/sig/terminal-shop/client.rbs +3 -0
- data/sig/terminal-shop/pooled_net_requester.rbs +1 -5
- data/sig/terminal-shop/util.rbs +20 -23
- data/sig/terminal-shop/version.rbs +1 -1
- metadata +2 -2
data/lib/terminal-shop/util.rb
CHANGED
@@ -8,163 +8,153 @@ module TerminalShop
|
|
8
8
|
module Util
|
9
9
|
# @private
|
10
10
|
#
|
11
|
-
# @return [
|
11
|
+
# @return [Float]
|
12
12
|
#
|
13
|
-
def self.
|
14
|
-
case (arch = RbConfig::CONFIG["arch"])&.downcase
|
15
|
-
in nil
|
16
|
-
"unknown"
|
17
|
-
in /aarch64|arm64/
|
18
|
-
"arm64"
|
19
|
-
in /x86_64/
|
20
|
-
"x64"
|
21
|
-
in /arm/
|
22
|
-
"arm"
|
23
|
-
else
|
24
|
-
"other:#{arch}"
|
25
|
-
end
|
26
|
-
end
|
13
|
+
def self.monotonic_secs = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
27
14
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
else
|
47
|
-
"Other:#{host}"
|
15
|
+
class << self
|
16
|
+
# @private
|
17
|
+
#
|
18
|
+
# @return [String]
|
19
|
+
#
|
20
|
+
def arch
|
21
|
+
case (arch = RbConfig::CONFIG["arch"])&.downcase
|
22
|
+
in nil
|
23
|
+
"unknown"
|
24
|
+
in /aarch64|arm64/
|
25
|
+
"arm64"
|
26
|
+
in /x86_64/
|
27
|
+
"x64"
|
28
|
+
in /arm/
|
29
|
+
"arm"
|
30
|
+
else
|
31
|
+
"other:#{arch}"
|
32
|
+
end
|
48
33
|
end
|
49
|
-
end
|
50
34
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
35
|
+
# @private
|
36
|
+
#
|
37
|
+
# @return [String]
|
38
|
+
#
|
39
|
+
def os
|
40
|
+
case (host = RbConfig::CONFIG["host_os"])&.downcase
|
41
|
+
in nil
|
42
|
+
"Unknown"
|
43
|
+
in /linux/
|
44
|
+
"Linux"
|
45
|
+
in /darwin/
|
46
|
+
"MacOS"
|
47
|
+
in /freebsd/
|
48
|
+
"FreeBSD"
|
49
|
+
in /openbsd/
|
50
|
+
"OpenBSD"
|
51
|
+
in /mswin|mingw|cygwin|ucrt/
|
52
|
+
"Windows"
|
53
|
+
else
|
54
|
+
"Other:#{host}"
|
55
|
+
end
|
63
56
|
end
|
64
57
|
end
|
65
58
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
else
|
81
|
-
input
|
59
|
+
class << self
|
60
|
+
# @private
|
61
|
+
#
|
62
|
+
# @param input [Object]
|
63
|
+
#
|
64
|
+
# @return [Boolean, Object]
|
65
|
+
#
|
66
|
+
def primitive?(input)
|
67
|
+
case input
|
68
|
+
in true | false | Integer | Float | Symbol | String
|
69
|
+
true
|
70
|
+
else
|
71
|
+
false
|
72
|
+
end
|
82
73
|
end
|
83
|
-
end
|
84
74
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
75
|
+
# @private
|
76
|
+
#
|
77
|
+
# @param input [Object]
|
78
|
+
#
|
79
|
+
# @return [Boolean, Object]
|
80
|
+
#
|
81
|
+
def coerce_boolean(input)
|
82
|
+
case input.is_a?(String) ? input.downcase : input
|
83
|
+
in Numeric
|
84
|
+
!input.zero?
|
85
|
+
in "true"
|
86
|
+
true
|
87
|
+
in "false"
|
88
|
+
false
|
89
|
+
else
|
90
|
+
input
|
91
|
+
end
|
98
92
|
end
|
99
|
-
end
|
100
93
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
94
|
+
# @private
|
95
|
+
#
|
96
|
+
# @param input [Object]
|
97
|
+
#
|
98
|
+
# @raise [ArgumentError]
|
99
|
+
# @return [Boolean, nil]
|
100
|
+
#
|
101
|
+
def coerce_boolean!(input)
|
102
|
+
case coerce_boolean(input)
|
103
|
+
in true | false | nil => coerced
|
104
|
+
coerced
|
105
|
+
else
|
106
|
+
raise ArgumentError.new("Unable to coerce #{input.inspect} into boolean value")
|
107
|
+
end
|
115
108
|
end
|
116
|
-
end
|
117
109
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
110
|
+
# @private
|
111
|
+
#
|
112
|
+
# @param input [Object]
|
113
|
+
#
|
114
|
+
# @return [Integer, Object]
|
115
|
+
#
|
116
|
+
def coerce_integer(input)
|
117
|
+
case input
|
118
|
+
in true
|
119
|
+
1
|
120
|
+
in false
|
121
|
+
0
|
122
|
+
else
|
123
|
+
Integer(input, exception: false) || input
|
124
|
+
end
|
132
125
|
end
|
133
|
-
end
|
134
126
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
127
|
+
# @private
|
128
|
+
#
|
129
|
+
# @param input [Object]
|
130
|
+
#
|
131
|
+
# @return [Float, Object]
|
132
|
+
#
|
133
|
+
def coerce_float(input)
|
134
|
+
case input
|
135
|
+
in true
|
136
|
+
1.0
|
137
|
+
in false
|
138
|
+
0.0
|
139
|
+
else
|
140
|
+
Float(input, exception: false) || input
|
141
|
+
end
|
147
142
|
end
|
148
|
-
end
|
149
143
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
def self.suppress(*exceptions, sentinel: nil, &blk)
|
165
|
-
blk.call
|
166
|
-
rescue *exceptions
|
167
|
-
sentinel
|
144
|
+
# @private
|
145
|
+
#
|
146
|
+
# @param input [Object]
|
147
|
+
#
|
148
|
+
# @return [Hash{Object=>Object}, Object]
|
149
|
+
#
|
150
|
+
def coerce_hash(input)
|
151
|
+
case input
|
152
|
+
in NilClass | Array | Set | Enumerator
|
153
|
+
input
|
154
|
+
else
|
155
|
+
input.respond_to?(:to_h) ? input.to_h : input
|
156
|
+
end
|
157
|
+
end
|
168
158
|
end
|
169
159
|
|
170
160
|
# Use this to indicate that a value should be explicitly removed from a data
|
@@ -174,335 +164,353 @@ module TerminalShop
|
|
174
164
|
# `{a: 1}` and `{}` would produce `{a: 1}`.
|
175
165
|
OMIT = Object.new.freeze
|
176
166
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
167
|
+
class << self
|
168
|
+
# @private
|
169
|
+
#
|
170
|
+
# @param lhs [Object]
|
171
|
+
# @param rhs [Object]
|
172
|
+
# @param concat [Boolean]
|
173
|
+
#
|
174
|
+
# @return [Object]
|
175
|
+
#
|
176
|
+
private def deep_merge_lr(lhs, rhs, concat: false)
|
177
|
+
case [lhs, rhs, concat]
|
178
|
+
in [Hash, Hash, _]
|
179
|
+
# rubocop:disable Style/YodaCondition
|
180
|
+
rhs_cleaned = rhs.reject { |_, val| OMIT == val }
|
181
|
+
lhs
|
182
|
+
.reject { |key, _| OMIT == rhs[key] }
|
183
|
+
.merge(rhs_cleaned) do |_, old_val, new_val|
|
184
|
+
deep_merge_lr(old_val, new_val, concat: concat)
|
185
|
+
end
|
186
|
+
# rubocop:enable Style/YodaCondition
|
187
|
+
in [Array, Array, true]
|
188
|
+
lhs.concat(rhs)
|
189
|
+
else
|
190
|
+
rhs
|
195
191
|
end
|
196
|
-
else
|
197
|
-
sentinel
|
198
192
|
end
|
199
|
-
end
|
200
193
|
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
194
|
+
# @private
|
195
|
+
#
|
196
|
+
# Recursively merge one hash with another. If the values at a given key are not
|
197
|
+
# both hashes, just take the new value.
|
198
|
+
#
|
199
|
+
# @param values [Array<Object>]
|
200
|
+
#
|
201
|
+
# @param sentinel [Object, nil] the value to return if no values are provided.
|
202
|
+
#
|
203
|
+
# @param concat [Boolean] whether to merge sequences by concatenation.
|
204
|
+
#
|
205
|
+
# @return [Object]
|
206
|
+
#
|
207
|
+
def deep_merge(*values, sentinel: nil, concat: false)
|
208
|
+
case values
|
209
|
+
in [value, *values]
|
210
|
+
values.reduce(value) do |acc, val|
|
211
|
+
deep_merge_lr(acc, val, concat: concat)
|
218
212
|
end
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
else
|
223
|
-
rhs
|
213
|
+
else
|
214
|
+
sentinel
|
215
|
+
end
|
224
216
|
end
|
225
|
-
end
|
226
217
|
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
218
|
+
# @private
|
219
|
+
#
|
220
|
+
# @param data [Hash{Symbol=>Object}, Array<Object>, Object]
|
221
|
+
# @param pick [Symbol, Integer, Array<Symbol, Integer>, nil]
|
222
|
+
# @param sentinel [Object, nil]
|
223
|
+
# @param blk [Proc, nil]
|
224
|
+
#
|
225
|
+
# @return [Object, nil]
|
226
|
+
#
|
227
|
+
def dig(data, pick, sentinel = nil, &blk)
|
228
|
+
case [data, pick, blk]
|
229
|
+
in [_, nil, nil]
|
230
|
+
data
|
231
|
+
in [Hash, Symbol, _] | [Array, Integer, _]
|
232
|
+
blk.nil? ? data.fetch(pick, sentinel) : data.fetch(pick, &blk)
|
233
|
+
in [Hash | Array, Array, _]
|
234
|
+
pick.reduce(data) do |acc, key|
|
235
|
+
case acc
|
236
|
+
in Hash if acc.key?(key)
|
237
|
+
acc.fetch(key)
|
238
|
+
in Array if key.is_a?(Integer) && key < acc.length
|
239
|
+
acc[key]
|
240
|
+
else
|
241
|
+
return blk.nil? ? sentinel : blk.call
|
242
|
+
end
|
251
243
|
end
|
244
|
+
in _
|
245
|
+
blk.nil? ? sentinel : blk.call
|
252
246
|
end
|
253
|
-
in _
|
254
|
-
blk.nil? ? sentinel : blk.call
|
255
247
|
end
|
256
248
|
end
|
257
249
|
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
250
|
+
class << self
|
251
|
+
# @private
|
252
|
+
#
|
253
|
+
# @param uri [URI::Generic]
|
254
|
+
#
|
255
|
+
# @return [String]
|
256
|
+
#
|
257
|
+
def uri_origin(uri)
|
258
|
+
"#{uri.scheme}://#{uri.host}#{uri.port == uri.default_port ? '' : ":#{uri.port}"}"
|
259
|
+
end
|
267
260
|
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
261
|
+
# @private
|
262
|
+
#
|
263
|
+
# @param path [String, Array<String>]
|
264
|
+
#
|
265
|
+
# @return [String]
|
266
|
+
#
|
267
|
+
def interpolate_path(path)
|
268
|
+
case path
|
269
|
+
in String
|
270
|
+
path
|
271
|
+
in []
|
272
|
+
""
|
273
|
+
in [String, *interpolations]
|
274
|
+
encoded = interpolations.map { ERB::Util.url_encode(_1) }
|
275
|
+
path.first % encoded
|
276
|
+
end
|
283
277
|
end
|
284
278
|
end
|
285
279
|
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
280
|
+
class << self
|
281
|
+
# @private
|
282
|
+
#
|
283
|
+
# @param query [String, nil]
|
284
|
+
#
|
285
|
+
# @return [Hash{String=>Array<String>}]
|
286
|
+
#
|
287
|
+
def decode_query(query)
|
288
|
+
CGI.parse(query.to_s)
|
289
|
+
end
|
296
290
|
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
#
|
307
|
-
# @option parsed [String, nil] :path
|
308
|
-
#
|
309
|
-
# @option parsed [Hash{String=>Array<String>}] :query
|
310
|
-
#
|
311
|
-
# @return [URI::Generic]
|
312
|
-
#
|
313
|
-
def self.unparse_uri(parsed)
|
314
|
-
URI::Generic.build(**parsed, query: encode_query(parsed.fetch(:query)))
|
291
|
+
# @private
|
292
|
+
#
|
293
|
+
# @param query [Hash{String=>Array<String>, String, nil}, nil]
|
294
|
+
#
|
295
|
+
# @return [String, nil]
|
296
|
+
#
|
297
|
+
def encode_query(query)
|
298
|
+
query.to_h.empty? ? nil : URI.encode_www_form(query)
|
299
|
+
end
|
315
300
|
end
|
316
301
|
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
#
|
329
|
-
# @option lhs [Hash{String=>Array<String>}] :query
|
330
|
-
#
|
331
|
-
# @param rhs [Hash{Symbol=>String, Integer, nil}] .
|
332
|
-
#
|
333
|
-
# @option rhs [String, nil] :scheme
|
334
|
-
#
|
335
|
-
# @option rhs [String, nil] :host
|
336
|
-
#
|
337
|
-
# @option rhs [Integer, nil] :port
|
338
|
-
#
|
339
|
-
# @option rhs [String, nil] :path
|
340
|
-
#
|
341
|
-
# @option rhs [Hash{String=>Array<String>}] :query
|
342
|
-
#
|
343
|
-
# @return [URI::Generic]
|
344
|
-
#
|
345
|
-
def self.join_parsed_uri(lhs, rhs)
|
346
|
-
base_path, base_query = lhs.fetch_values(:path, :query)
|
347
|
-
slashed = base_path.end_with?("/") ? base_path : "#{base_path}/"
|
302
|
+
class << self
|
303
|
+
# @private
|
304
|
+
#
|
305
|
+
# @param url [URI::Generic, String]
|
306
|
+
#
|
307
|
+
# @return [Hash{Symbol=>String, Integer, nil}]
|
308
|
+
#
|
309
|
+
def parse_uri(url)
|
310
|
+
parsed = URI::Generic.component.zip(URI.split(url)).to_h
|
311
|
+
{**parsed, query: decode_query(parsed.fetch(:query))}
|
312
|
+
end
|
348
313
|
|
349
|
-
|
350
|
-
|
314
|
+
# @private
|
315
|
+
#
|
316
|
+
# @param parsed [Hash{Symbol=>String, Integer, nil}] .
|
317
|
+
#
|
318
|
+
# @option parsed [String, nil] :scheme
|
319
|
+
#
|
320
|
+
# @option parsed [String, nil] :host
|
321
|
+
#
|
322
|
+
# @option parsed [Integer, nil] :port
|
323
|
+
#
|
324
|
+
# @option parsed [String, nil] :path
|
325
|
+
#
|
326
|
+
# @option parsed [Hash{String=>Array<String>}] :query
|
327
|
+
#
|
328
|
+
# @return [URI::Generic]
|
329
|
+
#
|
330
|
+
def unparse_uri(parsed)
|
331
|
+
URI::Generic.build(**parsed, query: encode_query(parsed.fetch(:query)))
|
332
|
+
end
|
351
333
|
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
334
|
+
# @private
|
335
|
+
#
|
336
|
+
# @param lhs [Hash{Symbol=>String, Integer, nil}] .
|
337
|
+
#
|
338
|
+
# @option lhs [String, nil] :scheme
|
339
|
+
#
|
340
|
+
# @option lhs [String, nil] :host
|
341
|
+
#
|
342
|
+
# @option lhs [Integer, nil] :port
|
343
|
+
#
|
344
|
+
# @option lhs [String, nil] :path
|
345
|
+
#
|
346
|
+
# @option lhs [Hash{String=>Array<String>}] :query
|
347
|
+
#
|
348
|
+
# @param rhs [Hash{Symbol=>String, Integer, nil}] .
|
349
|
+
#
|
350
|
+
# @option rhs [String, nil] :scheme
|
351
|
+
#
|
352
|
+
# @option rhs [String, nil] :host
|
353
|
+
#
|
354
|
+
# @option rhs [Integer, nil] :port
|
355
|
+
#
|
356
|
+
# @option rhs [String, nil] :path
|
357
|
+
#
|
358
|
+
# @option rhs [Hash{String=>Array<String>}] :query
|
359
|
+
#
|
360
|
+
# @return [URI::Generic]
|
361
|
+
#
|
362
|
+
def join_parsed_uri(lhs, rhs)
|
363
|
+
base_path, base_query = lhs.fetch_values(:path, :query)
|
364
|
+
slashed = base_path.end_with?("/") ? base_path : "#{base_path}/"
|
359
365
|
|
360
|
-
|
361
|
-
|
362
|
-
end
|
366
|
+
parsed_path, parsed_query = parse_uri(rhs.fetch(:path)).fetch_values(:path, :query)
|
367
|
+
override = URI::Generic.build(**rhs.slice(:scheme, :host, :port), path: parsed_path)
|
363
368
|
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
CGI.parse(query.to_s)
|
372
|
-
end
|
373
|
-
|
374
|
-
# @private
|
375
|
-
#
|
376
|
-
# @param query [Hash{String=>Array<String>, String, nil}, nil]
|
377
|
-
#
|
378
|
-
# @return [String, nil]
|
379
|
-
#
|
380
|
-
def self.encode_query(query)
|
381
|
-
query.to_h.empty? ? nil : URI.encode_www_form(query)
|
382
|
-
end
|
369
|
+
joined = URI.join(URI::Generic.build(lhs.except(:path, :query)), slashed, override)
|
370
|
+
query = deep_merge(
|
371
|
+
joined.path == base_path ? base_query : {},
|
372
|
+
parsed_query,
|
373
|
+
rhs[:query].to_h,
|
374
|
+
concat: true
|
375
|
+
)
|
383
376
|
|
384
|
-
|
385
|
-
|
386
|
-
# @param headers [Array<Hash{String=>String, Integer, nil}>]
|
387
|
-
#
|
388
|
-
# @return [Hash{String=>String}]
|
389
|
-
#
|
390
|
-
def self.normalized_headers(*headers)
|
391
|
-
{}.merge(*headers.compact).to_h do |key, val|
|
392
|
-
[key.downcase, val&.to_s&.strip]
|
377
|
+
joined.query = encode_query(query)
|
378
|
+
joined
|
393
379
|
end
|
394
380
|
end
|
395
381
|
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
in [%r{^multipart/form-data}, Hash | IO | StringIO]
|
409
|
-
boundary = SecureRandom.urlsafe_base64(60)
|
410
|
-
strio = StringIO.new.tap do |io|
|
411
|
-
case body
|
412
|
-
in Hash
|
413
|
-
body.each do |key, val|
|
414
|
-
case val
|
415
|
-
in Array if val.all? { primitive?(_1) }
|
416
|
-
val.each do |v|
|
417
|
-
encode_multipart_formdata(io, boundary: boundary, key: key, val: v)
|
418
|
-
end
|
419
|
-
else
|
420
|
-
encode_multipart_formdata(io, boundary: boundary, key: key, val: val)
|
421
|
-
end
|
422
|
-
end
|
382
|
+
class << self
|
383
|
+
# @private
|
384
|
+
#
|
385
|
+
# @param headers [Hash{String=>String, Integer, Array<String, Integer, nil>, nil}]
|
386
|
+
#
|
387
|
+
# @return [Hash{String=>String}]
|
388
|
+
#
|
389
|
+
def normalized_headers(*headers)
|
390
|
+
{}.merge(*headers.compact).to_h do |key, val|
|
391
|
+
case val
|
392
|
+
in Array
|
393
|
+
val.map { _1.to_s.strip }.join(", ")
|
423
394
|
else
|
424
|
-
|
395
|
+
val&.to_s&.strip
|
425
396
|
end
|
426
|
-
|
427
|
-
io.rewind
|
397
|
+
[key.downcase, val]
|
428
398
|
end
|
429
|
-
headers = {
|
430
|
-
**headers,
|
431
|
-
"content-type" => "#{content_type}; boundary=#{boundary}",
|
432
|
-
"transfer-encoding" => "chunked"
|
433
|
-
}
|
434
|
-
[headers, strio]
|
435
|
-
in [_, StringIO]
|
436
|
-
[headers, body.string]
|
437
|
-
in [_, IO]
|
438
|
-
headers = {**headers, "transfer-encoding" => "chunked"}
|
439
|
-
[headers, body]
|
440
|
-
else
|
441
|
-
[headers, body]
|
442
399
|
end
|
443
400
|
end
|
444
401
|
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
JSON.parse(json, symbolize_names: true)
|
460
|
-
rescue JSON::ParserError => e
|
461
|
-
raise e unless suppress_error
|
462
|
-
json
|
402
|
+
class << self
|
403
|
+
# @private
|
404
|
+
#
|
405
|
+
# @param io [StringIO]
|
406
|
+
# @param boundary [String]
|
407
|
+
# @param key [Symbol, String]
|
408
|
+
# @param val [Object]
|
409
|
+
#
|
410
|
+
private def encode_multipart_formdata(io, boundary:, key:, val:)
|
411
|
+
io << "--#{boundary}\r\n"
|
412
|
+
io << "Content-Disposition: form-data"
|
413
|
+
unless key.nil?
|
414
|
+
name = ERB::Util.url_encode(key.to_s)
|
415
|
+
io << "; name=\"#{name}\""
|
463
416
|
end
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
417
|
+
if val.is_a?(IO)
|
418
|
+
filename = ERB::Util.url_encode(File.basename(val.to_path))
|
419
|
+
io << "; filename=\"#{filename}\""
|
420
|
+
end
|
421
|
+
io << "\r\n"
|
422
|
+
case val
|
423
|
+
in IO | StringIO
|
424
|
+
io << "Content-Type: application/octet-stream\r\n\r\n"
|
425
|
+
IO.copy_stream(val, io)
|
426
|
+
in String
|
427
|
+
io << "Content-Type: application/octet-stream\r\n\r\n"
|
428
|
+
io << val.to_s
|
429
|
+
in true | false | Integer | Float | Symbol
|
430
|
+
io << "Content-Type: text/plain\r\n\r\n"
|
431
|
+
io << val.to_s
|
432
|
+
else
|
433
|
+
io << "Content-Type: application/json\r\n\r\n"
|
434
|
+
io << JSON.fast_generate(val)
|
435
|
+
end
|
436
|
+
io << "\r\n"
|
469
437
|
end
|
470
|
-
end
|
471
438
|
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
439
|
+
# @private
|
440
|
+
#
|
441
|
+
# @param headers [Hash{String=>String}]
|
442
|
+
# @param body [Object]
|
443
|
+
#
|
444
|
+
# @return [Object]
|
445
|
+
#
|
446
|
+
def encode_content(headers, body)
|
447
|
+
content_type = headers["content-type"]
|
448
|
+
case [content_type, body]
|
449
|
+
in ["application/json", Hash | Array]
|
450
|
+
[headers, JSON.fast_generate(body)]
|
451
|
+
in [%r{^multipart/form-data}, Hash | IO | StringIO]
|
452
|
+
boundary = SecureRandom.urlsafe_base64(60)
|
453
|
+
strio = StringIO.new.tap do |io|
|
454
|
+
case body
|
455
|
+
in Hash
|
456
|
+
body.each do |key, val|
|
457
|
+
case val
|
458
|
+
in Array if val.all? { primitive?(_1) }
|
459
|
+
val.each do |v|
|
460
|
+
encode_multipart_formdata(io, boundary: boundary, key: key, val: v)
|
461
|
+
end
|
462
|
+
else
|
463
|
+
encode_multipart_formdata(io, boundary: boundary, key: key, val: val)
|
464
|
+
end
|
465
|
+
end
|
466
|
+
else
|
467
|
+
encode_multipart_formdata(io, boundary: boundary, key: nil, val: body)
|
468
|
+
end
|
469
|
+
io << "--#{boundary}--\r\n"
|
470
|
+
io.rewind
|
471
|
+
end
|
472
|
+
headers = {
|
473
|
+
**headers,
|
474
|
+
"content-type" => "#{content_type}; boundary=#{boundary}",
|
475
|
+
"transfer-encoding" => "chunked"
|
476
|
+
}
|
477
|
+
[headers, strio]
|
478
|
+
in [_, StringIO]
|
479
|
+
[headers, body.string]
|
480
|
+
in [_, IO]
|
481
|
+
headers = {**headers, "transfer-encoding" => "chunked"}
|
482
|
+
[headers, body]
|
483
|
+
else
|
484
|
+
[headers, body]
|
485
|
+
end
|
489
486
|
end
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
487
|
+
|
488
|
+
# @private
|
489
|
+
#
|
490
|
+
# @param headers [Hash{String=>String}, Net::HTTPHeader]
|
491
|
+
# @param stream [Enumerable]
|
492
|
+
# @param suppress_error [Boolean]
|
493
|
+
#
|
494
|
+
# @raise [JSON::ParserError]
|
495
|
+
# @return [Object]
|
496
|
+
#
|
497
|
+
def decode_content(headers, stream:, suppress_error: false)
|
498
|
+
case headers["content-type"]
|
499
|
+
in %r{^application/json}
|
500
|
+
json = stream.to_a.join
|
501
|
+
begin
|
502
|
+
JSON.parse(json, symbolize_names: true)
|
503
|
+
rescue JSON::ParserError => e
|
504
|
+
raise e unless suppress_error
|
505
|
+
json
|
506
|
+
end
|
507
|
+
in %r{^text/}
|
508
|
+
stream.to_a.join
|
509
|
+
else
|
510
|
+
# TODO: parsing other response types
|
511
|
+
StringIO.new(stream.to_a.join)
|
512
|
+
end
|
504
513
|
end
|
505
|
-
io << "\r\n"
|
506
514
|
end
|
507
515
|
end
|
508
516
|
|