brand.dev 0.0.1.pre.alpha.0
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 +7 -0
- data/README.md +229 -0
- data/SECURITY.md +23 -0
- data/lib/brand_dev/client.rb +74 -0
- data/lib/brand_dev/errors.rb +192 -0
- data/lib/brand_dev/file_part.rb +55 -0
- data/lib/brand_dev/internal/transport/base_client.rb +555 -0
- data/lib/brand_dev/internal/transport/pooled_net_requester.rb +209 -0
- data/lib/brand_dev/internal/type/array_of.rb +162 -0
- data/lib/brand_dev/internal/type/base_model.rb +484 -0
- data/lib/brand_dev/internal/type/base_page.rb +55 -0
- data/lib/brand_dev/internal/type/boolean.rb +71 -0
- data/lib/brand_dev/internal/type/converter.rb +292 -0
- data/lib/brand_dev/internal/type/enum.rb +120 -0
- data/lib/brand_dev/internal/type/file_input.rb +103 -0
- data/lib/brand_dev/internal/type/hash_of.rb +182 -0
- data/lib/brand_dev/internal/type/request_parameters.rb +42 -0
- data/lib/brand_dev/internal/type/union.rb +227 -0
- data/lib/brand_dev/internal/type/unknown.rb +75 -0
- data/lib/brand_dev/internal/util.rb +915 -0
- data/lib/brand_dev/internal.rb +20 -0
- data/lib/brand_dev/models/brand_identify_from_transaction_params.rb +22 -0
- data/lib/brand_dev/models/brand_identify_from_transaction_response.rb +435 -0
- data/lib/brand_dev/models/brand_retrieve_by_ticker_params.rb +22 -0
- data/lib/brand_dev/models/brand_retrieve_by_ticker_response.rb +432 -0
- data/lib/brand_dev/models/brand_retrieve_naics_params.rb +27 -0
- data/lib/brand_dev/models/brand_retrieve_naics_response.rb +61 -0
- data/lib/brand_dev/models/brand_retrieve_params.rb +91 -0
- data/lib/brand_dev/models/brand_retrieve_response.rb +432 -0
- data/lib/brand_dev/models/brand_search_params.rb +22 -0
- data/lib/brand_dev/models/brand_search_response.rb +35 -0
- data/lib/brand_dev/models.rb +51 -0
- data/lib/brand_dev/request_options.rb +77 -0
- data/lib/brand_dev/resources/brand.rb +130 -0
- data/lib/brand_dev/version.rb +5 -0
- data/lib/brand_dev.rb +64 -0
- data/manifest.yaml +15 -0
- data/rbi/brand_dev/client.rbi +49 -0
- data/rbi/brand_dev/errors.rbi +162 -0
- data/rbi/brand_dev/file_part.rbi +37 -0
- data/rbi/brand_dev/internal/transport/base_client.rbi +293 -0
- data/rbi/brand_dev/internal/transport/pooled_net_requester.rbi +79 -0
- data/rbi/brand_dev/internal/type/array_of.rbi +104 -0
- data/rbi/brand_dev/internal/type/base_model.rbi +302 -0
- data/rbi/brand_dev/internal/type/base_page.rbi +42 -0
- data/rbi/brand_dev/internal/type/boolean.rbi +56 -0
- data/rbi/brand_dev/internal/type/converter.rbi +162 -0
- data/rbi/brand_dev/internal/type/enum.rbi +82 -0
- data/rbi/brand_dev/internal/type/file_input.rbi +59 -0
- data/rbi/brand_dev/internal/type/hash_of.rbi +104 -0
- data/rbi/brand_dev/internal/type/request_parameters.rbi +29 -0
- data/rbi/brand_dev/internal/type/union.rbi +116 -0
- data/rbi/brand_dev/internal/type/unknown.rbi +56 -0
- data/rbi/brand_dev/internal/util.rbi +485 -0
- data/rbi/brand_dev/internal.rbi +16 -0
- data/rbi/brand_dev/models/brand_identify_from_transaction_params.rbi +46 -0
- data/rbi/brand_dev/models/brand_identify_from_transaction_response.rbi +981 -0
- data/rbi/brand_dev/models/brand_retrieve_by_ticker_params.rbi +43 -0
- data/rbi/brand_dev/models/brand_retrieve_by_ticker_response.rbi +976 -0
- data/rbi/brand_dev/models/brand_retrieve_naics_params.rbi +44 -0
- data/rbi/brand_dev/models/brand_retrieve_naics_response.rbi +127 -0
- data/rbi/brand_dev/models/brand_retrieve_params.rbi +344 -0
- data/rbi/brand_dev/models/brand_retrieve_response.rbi +949 -0
- data/rbi/brand_dev/models/brand_search_params.rbi +40 -0
- data/rbi/brand_dev/models/brand_search_response.rbi +63 -0
- data/rbi/brand_dev/models.rbi +14 -0
- data/rbi/brand_dev/request_options.rbi +59 -0
- data/rbi/brand_dev/resources/brand.rbi +89 -0
- data/rbi/brand_dev/version.rbi +5 -0
- data/sig/brand_dev/client.rbs +26 -0
- data/sig/brand_dev/errors.rbs +101 -0
- data/sig/brand_dev/file_part.rbs +21 -0
- data/sig/brand_dev/internal/transport/base_client.rbs +131 -0
- data/sig/brand_dev/internal/transport/pooled_net_requester.rbs +45 -0
- data/sig/brand_dev/internal/type/array_of.rbs +48 -0
- data/sig/brand_dev/internal/type/base_model.rbs +102 -0
- data/sig/brand_dev/internal/type/base_page.rbs +24 -0
- data/sig/brand_dev/internal/type/boolean.rbs +26 -0
- data/sig/brand_dev/internal/type/converter.rbs +56 -0
- data/sig/brand_dev/internal/type/enum.rbs +32 -0
- data/sig/brand_dev/internal/type/file_input.rbs +25 -0
- data/sig/brand_dev/internal/type/hash_of.rbs +48 -0
- data/sig/brand_dev/internal/type/request_parameters.rbs +17 -0
- data/sig/brand_dev/internal/type/union.rbs +52 -0
- data/sig/brand_dev/internal/type/unknown.rbs +26 -0
- data/sig/brand_dev/internal/util.rbs +185 -0
- data/sig/brand_dev/internal.rbs +9 -0
- data/sig/brand_dev/models/brand_identify_from_transaction_params.rbs +24 -0
- data/sig/brand_dev/models/brand_identify_from_transaction_response.rbs +418 -0
- data/sig/brand_dev/models/brand_retrieve_by_ticker_params.rbs +23 -0
- data/sig/brand_dev/models/brand_retrieve_by_ticker_response.rbs +418 -0
- data/sig/brand_dev/models/brand_retrieve_naics_params.rbs +23 -0
- data/sig/brand_dev/models/brand_retrieve_naics_response.rbs +61 -0
- data/sig/brand_dev/models/brand_retrieve_params.rbs +148 -0
- data/sig/brand_dev/models/brand_retrieve_response.rbs +418 -0
- data/sig/brand_dev/models/brand_search_params.rbs +23 -0
- data/sig/brand_dev/models/brand_search_response.rbs +29 -0
- data/sig/brand_dev/models.rbs +11 -0
- data/sig/brand_dev/request_options.rbs +34 -0
- data/sig/brand_dev/resources/brand.rbs +33 -0
- data/sig/brand_dev/version.rbs +3 -0
- metadata +160 -0
@@ -0,0 +1,915 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BrandDev
|
4
|
+
module Internal
|
5
|
+
# @api private
|
6
|
+
module Util
|
7
|
+
# @api private
|
8
|
+
#
|
9
|
+
# @return [Float]
|
10
|
+
def self.monotonic_secs = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
11
|
+
|
12
|
+
# @api private
|
13
|
+
#
|
14
|
+
# @param ns [Module, Class]
|
15
|
+
#
|
16
|
+
# @return [Enumerable<Module, Class>]
|
17
|
+
def self.walk_namespaces(ns)
|
18
|
+
ns.constants(false).lazy.flat_map do
|
19
|
+
case (c = ns.const_get(_1, false))
|
20
|
+
in Module | Class
|
21
|
+
walk_namespaces(c)
|
22
|
+
else
|
23
|
+
[]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
.chain([ns])
|
27
|
+
end
|
28
|
+
|
29
|
+
class << self
|
30
|
+
# @api private
|
31
|
+
#
|
32
|
+
# @return [String]
|
33
|
+
def arch
|
34
|
+
case (arch = RbConfig::CONFIG["arch"])&.downcase
|
35
|
+
in nil
|
36
|
+
"unknown"
|
37
|
+
in /aarch64|arm64/
|
38
|
+
"arm64"
|
39
|
+
in /x86_64/
|
40
|
+
"x64"
|
41
|
+
in /arm/
|
42
|
+
"arm"
|
43
|
+
else
|
44
|
+
"other:#{arch}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# @api private
|
49
|
+
#
|
50
|
+
# @return [String]
|
51
|
+
def os
|
52
|
+
case (host = RbConfig::CONFIG["host_os"])&.downcase
|
53
|
+
in nil
|
54
|
+
"Unknown"
|
55
|
+
in /linux/
|
56
|
+
"Linux"
|
57
|
+
in /darwin/
|
58
|
+
"MacOS"
|
59
|
+
in /freebsd/
|
60
|
+
"FreeBSD"
|
61
|
+
in /openbsd/
|
62
|
+
"OpenBSD"
|
63
|
+
in /mswin|mingw|cygwin|ucrt/
|
64
|
+
"Windows"
|
65
|
+
else
|
66
|
+
"Other:#{host}"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class << self
|
72
|
+
# @api private
|
73
|
+
#
|
74
|
+
# @param input [Object]
|
75
|
+
#
|
76
|
+
# @return [Boolean]
|
77
|
+
def primitive?(input)
|
78
|
+
case input
|
79
|
+
in true | false | Numeric | Symbol | String
|
80
|
+
true
|
81
|
+
else
|
82
|
+
false
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# @api private
|
87
|
+
#
|
88
|
+
# @param input [String, Boolean]
|
89
|
+
#
|
90
|
+
# @return [Boolean, Object]
|
91
|
+
def coerce_boolean(input)
|
92
|
+
case input.is_a?(String) ? input.downcase : input
|
93
|
+
in "true"
|
94
|
+
true
|
95
|
+
in "false"
|
96
|
+
false
|
97
|
+
else
|
98
|
+
input
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# @api private
|
103
|
+
#
|
104
|
+
# @param input [String, Boolean]
|
105
|
+
#
|
106
|
+
# @raise [ArgumentError]
|
107
|
+
# @return [Boolean, nil]
|
108
|
+
def coerce_boolean!(input)
|
109
|
+
case coerce_boolean(input)
|
110
|
+
in true | false | nil => coerced
|
111
|
+
coerced
|
112
|
+
else
|
113
|
+
raise ArgumentError.new("Unable to coerce #{input.inspect} into boolean value")
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# @api private
|
118
|
+
#
|
119
|
+
# @param input [String, Integer]
|
120
|
+
#
|
121
|
+
# @return [Integer, Object]
|
122
|
+
def coerce_integer(input)
|
123
|
+
Integer(input, exception: false) || input
|
124
|
+
end
|
125
|
+
|
126
|
+
# @api private
|
127
|
+
#
|
128
|
+
# @param input [String, Integer, Float]
|
129
|
+
#
|
130
|
+
# @return [Float, Object]
|
131
|
+
def coerce_float(input)
|
132
|
+
Float(input, exception: false) || input
|
133
|
+
end
|
134
|
+
|
135
|
+
# @api private
|
136
|
+
#
|
137
|
+
# @param input [Object]
|
138
|
+
#
|
139
|
+
# @return [Hash{Object=>Object}, Object]
|
140
|
+
def coerce_hash(input)
|
141
|
+
case input
|
142
|
+
in NilClass | Array | Set | Enumerator | StringIO | IO
|
143
|
+
input
|
144
|
+
else
|
145
|
+
input.respond_to?(:to_h) ? input.to_h : input
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# @api private
|
150
|
+
#
|
151
|
+
# @param input [Object]
|
152
|
+
#
|
153
|
+
# @raise [ArgumentError]
|
154
|
+
# @return [Hash{Object=>Object}, nil]
|
155
|
+
def coerce_hash!(input)
|
156
|
+
case coerce_hash(input)
|
157
|
+
in Hash | nil => coerced
|
158
|
+
coerced
|
159
|
+
else
|
160
|
+
message = "Expected a #{Hash} or #{BrandDev::Internal::Type::BaseModel}, got #{data.inspect}"
|
161
|
+
raise ArgumentError.new(message)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
class << self
|
167
|
+
# @api private
|
168
|
+
#
|
169
|
+
# @param lhs [Object]
|
170
|
+
# @param rhs [Object]
|
171
|
+
# @param concat [Boolean]
|
172
|
+
#
|
173
|
+
# @return [Object]
|
174
|
+
private def deep_merge_lr(lhs, rhs, concat: false)
|
175
|
+
case [lhs, rhs, concat]
|
176
|
+
in [Hash, Hash, _]
|
177
|
+
lhs.merge(rhs) { deep_merge_lr(_2, _3, concat: concat) }
|
178
|
+
in [Array, Array, true]
|
179
|
+
lhs.concat(rhs)
|
180
|
+
else
|
181
|
+
rhs
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# @api private
|
186
|
+
#
|
187
|
+
# Recursively merge one hash with another. If the values at a given key are not
|
188
|
+
# both hashes, just take the new value.
|
189
|
+
#
|
190
|
+
# @param values [Array<Object>]
|
191
|
+
#
|
192
|
+
# @param sentinel [Object, nil] the value to return if no values are provided.
|
193
|
+
#
|
194
|
+
# @param concat [Boolean] whether to merge sequences by concatenation.
|
195
|
+
#
|
196
|
+
# @return [Object]
|
197
|
+
def deep_merge(*values, sentinel: nil, concat: false)
|
198
|
+
case values
|
199
|
+
in [value, *values]
|
200
|
+
values.reduce(value) do |acc, val|
|
201
|
+
deep_merge_lr(acc, val, concat: concat)
|
202
|
+
end
|
203
|
+
else
|
204
|
+
sentinel
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# @api private
|
209
|
+
#
|
210
|
+
# @param data [Hash{Symbol=>Object}, Array<Object>, Object]
|
211
|
+
# @param pick [Symbol, Integer, Array<Symbol, Integer>, Proc, nil]
|
212
|
+
# @param blk [Proc, nil]
|
213
|
+
#
|
214
|
+
# @return [Object, nil]
|
215
|
+
def dig(data, pick, &blk)
|
216
|
+
case [data, pick]
|
217
|
+
in [_, nil]
|
218
|
+
data
|
219
|
+
in [Hash, Symbol] | [Array, Integer]
|
220
|
+
data.fetch(pick) { blk&.call }
|
221
|
+
in [Hash | Array, Array]
|
222
|
+
pick.reduce(data) do |acc, key|
|
223
|
+
case acc
|
224
|
+
in Hash if acc.key?(key)
|
225
|
+
acc.fetch(key)
|
226
|
+
in Array if key.is_a?(Integer) && key < acc.length
|
227
|
+
acc[key]
|
228
|
+
else
|
229
|
+
return blk&.call
|
230
|
+
end
|
231
|
+
end
|
232
|
+
in [_, Proc]
|
233
|
+
pick.call(data)
|
234
|
+
else
|
235
|
+
blk&.call
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
class << self
|
241
|
+
# @api private
|
242
|
+
#
|
243
|
+
# @param uri [URI::Generic]
|
244
|
+
#
|
245
|
+
# @return [String]
|
246
|
+
def uri_origin(uri)
|
247
|
+
"#{uri.scheme}://#{uri.host}#{uri.port == uri.default_port ? '' : ":#{uri.port}"}"
|
248
|
+
end
|
249
|
+
|
250
|
+
# @api private
|
251
|
+
#
|
252
|
+
# @param path [String, Array<String>]
|
253
|
+
#
|
254
|
+
# @return [String]
|
255
|
+
def interpolate_path(path)
|
256
|
+
case path
|
257
|
+
in String
|
258
|
+
path
|
259
|
+
in []
|
260
|
+
""
|
261
|
+
in [String => p, *interpolations]
|
262
|
+
encoded = interpolations.map { ERB::Util.url_encode(_1) }
|
263
|
+
format(p, *encoded)
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
class << self
|
269
|
+
# @api private
|
270
|
+
#
|
271
|
+
# @param query [String, nil]
|
272
|
+
#
|
273
|
+
# @return [Hash{String=>Array<String>}]
|
274
|
+
def decode_query(query)
|
275
|
+
CGI.parse(query.to_s)
|
276
|
+
end
|
277
|
+
|
278
|
+
# @api private
|
279
|
+
#
|
280
|
+
# @param query [Hash{String=>Array<String>, String, nil}, nil]
|
281
|
+
#
|
282
|
+
# @return [String, nil]
|
283
|
+
def encode_query(query)
|
284
|
+
query.to_h.empty? ? nil : URI.encode_www_form(query)
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
class << self
|
289
|
+
# @api private
|
290
|
+
#
|
291
|
+
# @param url [URI::Generic, String]
|
292
|
+
#
|
293
|
+
# @return [Hash{Symbol=>String, Integer, nil}]
|
294
|
+
def parse_uri(url)
|
295
|
+
parsed = URI::Generic.component.zip(URI.split(url)).to_h
|
296
|
+
{**parsed, query: decode_query(parsed.fetch(:query))}
|
297
|
+
end
|
298
|
+
|
299
|
+
# @api private
|
300
|
+
#
|
301
|
+
# @param parsed [Hash{Symbol=>String, Integer, nil}] .
|
302
|
+
#
|
303
|
+
# @option parsed [String, nil] :scheme
|
304
|
+
#
|
305
|
+
# @option parsed [String, nil] :host
|
306
|
+
#
|
307
|
+
# @option parsed [Integer, nil] :port
|
308
|
+
#
|
309
|
+
# @option parsed [String, nil] :path
|
310
|
+
#
|
311
|
+
# @option parsed [Hash{String=>Array<String>}] :query
|
312
|
+
#
|
313
|
+
# @return [URI::Generic]
|
314
|
+
def unparse_uri(parsed)
|
315
|
+
URI::Generic.build(**parsed, query: encode_query(parsed.fetch(:query)))
|
316
|
+
end
|
317
|
+
|
318
|
+
# @api private
|
319
|
+
#
|
320
|
+
# @param lhs [Hash{Symbol=>String, Integer, nil}] .
|
321
|
+
#
|
322
|
+
# @option lhs [String, nil] :scheme
|
323
|
+
#
|
324
|
+
# @option lhs [String, nil] :host
|
325
|
+
#
|
326
|
+
# @option lhs [Integer, nil] :port
|
327
|
+
#
|
328
|
+
# @option lhs [String, nil] :path
|
329
|
+
#
|
330
|
+
# @option lhs [Hash{String=>Array<String>}] :query
|
331
|
+
#
|
332
|
+
# @param rhs [Hash{Symbol=>String, Integer, nil}] .
|
333
|
+
#
|
334
|
+
# @option rhs [String, nil] :scheme
|
335
|
+
#
|
336
|
+
# @option rhs [String, nil] :host
|
337
|
+
#
|
338
|
+
# @option rhs [Integer, nil] :port
|
339
|
+
#
|
340
|
+
# @option rhs [String, nil] :path
|
341
|
+
#
|
342
|
+
# @option rhs [Hash{String=>Array<String>}] :query
|
343
|
+
#
|
344
|
+
# @return [URI::Generic]
|
345
|
+
def 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}/"
|
348
|
+
|
349
|
+
parsed_path, parsed_query = parse_uri(rhs.fetch(:path)).fetch_values(:path, :query)
|
350
|
+
override = URI::Generic.build(**rhs.slice(:scheme, :host, :port), path: parsed_path)
|
351
|
+
|
352
|
+
joined = URI.join(URI::Generic.build(lhs.except(:path, :query)), slashed, override)
|
353
|
+
query = deep_merge(
|
354
|
+
joined.path == base_path ? base_query : {},
|
355
|
+
parsed_query,
|
356
|
+
rhs[:query].to_h,
|
357
|
+
concat: true
|
358
|
+
)
|
359
|
+
|
360
|
+
joined.query = encode_query(query)
|
361
|
+
joined
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
class << self
|
366
|
+
# @api private
|
367
|
+
#
|
368
|
+
# @param headers [Hash{String=>String, Integer, Array<String, Integer, nil>, nil}]
|
369
|
+
#
|
370
|
+
# @return [Hash{String=>String}]
|
371
|
+
def normalized_headers(*headers)
|
372
|
+
{}.merge(*headers.compact).to_h do |key, val|
|
373
|
+
value =
|
374
|
+
case val
|
375
|
+
in Array
|
376
|
+
val.filter_map { _1&.to_s&.strip }.join(", ")
|
377
|
+
else
|
378
|
+
val&.to_s&.strip
|
379
|
+
end
|
380
|
+
[key.downcase, value]
|
381
|
+
end
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
# @api private
|
386
|
+
#
|
387
|
+
# An adapter that satisfies the IO interface required by `::IO.copy_stream`
|
388
|
+
class ReadIOAdapter
|
389
|
+
# @api private
|
390
|
+
#
|
391
|
+
# @return [Boolean, nil]
|
392
|
+
def close? = @closing
|
393
|
+
|
394
|
+
# @api private
|
395
|
+
def close
|
396
|
+
case @stream
|
397
|
+
in Enumerator
|
398
|
+
BrandDev::Internal::Util.close_fused!(@stream)
|
399
|
+
in IO if close?
|
400
|
+
@stream.close
|
401
|
+
else
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
# @api private
|
406
|
+
#
|
407
|
+
# @param max_len [Integer, nil]
|
408
|
+
#
|
409
|
+
# @return [String]
|
410
|
+
private def read_enum(max_len)
|
411
|
+
case max_len
|
412
|
+
in nil
|
413
|
+
@stream.to_a.join
|
414
|
+
in Integer
|
415
|
+
@buf << @stream.next while @buf.length < max_len
|
416
|
+
@buf.slice!(..max_len)
|
417
|
+
end
|
418
|
+
rescue StopIteration
|
419
|
+
@stream = nil
|
420
|
+
@buf.slice!(0..)
|
421
|
+
end
|
422
|
+
|
423
|
+
# @api private
|
424
|
+
#
|
425
|
+
# @param max_len [Integer, nil]
|
426
|
+
# @param out_string [String, nil]
|
427
|
+
#
|
428
|
+
# @return [String, nil]
|
429
|
+
def read(max_len = nil, out_string = nil)
|
430
|
+
case @stream
|
431
|
+
in nil
|
432
|
+
nil
|
433
|
+
in IO | StringIO
|
434
|
+
@stream.read(max_len, out_string)
|
435
|
+
in Enumerator
|
436
|
+
read = read_enum(max_len)
|
437
|
+
case out_string
|
438
|
+
in String
|
439
|
+
out_string.replace(read)
|
440
|
+
in nil
|
441
|
+
read
|
442
|
+
end
|
443
|
+
end
|
444
|
+
.tap(&@blk)
|
445
|
+
end
|
446
|
+
|
447
|
+
# @api private
|
448
|
+
#
|
449
|
+
# @param src [String, Pathname, StringIO, Enumerable<String>]
|
450
|
+
# @param blk [Proc]
|
451
|
+
#
|
452
|
+
# @yieldparam [String]
|
453
|
+
def initialize(src, &blk)
|
454
|
+
@stream =
|
455
|
+
case src
|
456
|
+
in String
|
457
|
+
StringIO.new(src)
|
458
|
+
in Pathname
|
459
|
+
@closing = true
|
460
|
+
src.open(binmode: true)
|
461
|
+
else
|
462
|
+
src
|
463
|
+
end
|
464
|
+
@buf = String.new
|
465
|
+
@blk = blk
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
469
|
+
class << self
|
470
|
+
# @param blk [Proc]
|
471
|
+
#
|
472
|
+
# @yieldparam [Enumerator::Yielder]
|
473
|
+
# @return [Enumerable<String>]
|
474
|
+
def writable_enum(&blk)
|
475
|
+
Enumerator.new do |y|
|
476
|
+
buf = String.new
|
477
|
+
y.define_singleton_method(:write) do
|
478
|
+
self << buf.replace(_1)
|
479
|
+
buf.bytesize
|
480
|
+
end
|
481
|
+
|
482
|
+
blk.call(y)
|
483
|
+
end
|
484
|
+
end
|
485
|
+
end
|
486
|
+
|
487
|
+
# @type [Regexp]
|
488
|
+
JSON_CONTENT = %r{^application/(?:vnd(?:\.[^.]+)*\+)?json(?!l)}
|
489
|
+
# @type [Regexp]
|
490
|
+
JSONL_CONTENT = %r{^application/(:?x-(?:n|l)djson)|(:?(?:x-)?jsonl)}
|
491
|
+
|
492
|
+
class << self
|
493
|
+
# @api private
|
494
|
+
#
|
495
|
+
# @param y [Enumerator::Yielder]
|
496
|
+
# @param val [Object]
|
497
|
+
# @param closing [Array<Proc>]
|
498
|
+
# @param content_type [String, nil]
|
499
|
+
private def write_multipart_content(y, val:, closing:, content_type: nil)
|
500
|
+
content_type ||= "application/octet-stream"
|
501
|
+
|
502
|
+
case val
|
503
|
+
in BrandDev::FilePart
|
504
|
+
return write_multipart_content(
|
505
|
+
y,
|
506
|
+
val: val.content,
|
507
|
+
closing: closing,
|
508
|
+
content_type: val.content_type
|
509
|
+
)
|
510
|
+
in Pathname
|
511
|
+
y << "Content-Type: #{content_type}\r\n\r\n"
|
512
|
+
io = val.open(binmode: true)
|
513
|
+
closing << io.method(:close)
|
514
|
+
IO.copy_stream(io, y)
|
515
|
+
in IO
|
516
|
+
y << "Content-Type: #{content_type}\r\n\r\n"
|
517
|
+
IO.copy_stream(val, y)
|
518
|
+
in StringIO
|
519
|
+
y << "Content-Type: #{content_type}\r\n\r\n"
|
520
|
+
y << val.string
|
521
|
+
in String
|
522
|
+
y << "Content-Type: #{content_type}\r\n\r\n"
|
523
|
+
y << val.to_s
|
524
|
+
in -> { primitive?(_1) }
|
525
|
+
y << "Content-Type: text/plain\r\n\r\n"
|
526
|
+
y << val.to_s
|
527
|
+
else
|
528
|
+
y << "Content-Type: application/json\r\n\r\n"
|
529
|
+
y << JSON.generate(val)
|
530
|
+
end
|
531
|
+
y << "\r\n"
|
532
|
+
end
|
533
|
+
|
534
|
+
# @api private
|
535
|
+
#
|
536
|
+
# @param y [Enumerator::Yielder]
|
537
|
+
# @param boundary [String]
|
538
|
+
# @param key [Symbol, String]
|
539
|
+
# @param val [Object]
|
540
|
+
# @param closing [Array<Proc>]
|
541
|
+
private def write_multipart_chunk(y, boundary:, key:, val:, closing:)
|
542
|
+
y << "--#{boundary}\r\n"
|
543
|
+
y << "Content-Disposition: form-data"
|
544
|
+
|
545
|
+
unless key.nil?
|
546
|
+
name = ERB::Util.url_encode(key.to_s)
|
547
|
+
y << "; name=\"#{name}\""
|
548
|
+
end
|
549
|
+
|
550
|
+
case val
|
551
|
+
in BrandDev::FilePart unless val.filename.nil?
|
552
|
+
filename = ERB::Util.url_encode(val.filename)
|
553
|
+
y << "; filename=\"#{filename}\""
|
554
|
+
in Pathname | IO
|
555
|
+
filename = ERB::Util.url_encode(::File.basename(val.to_path))
|
556
|
+
y << "; filename=\"#{filename}\""
|
557
|
+
else
|
558
|
+
end
|
559
|
+
y << "\r\n"
|
560
|
+
|
561
|
+
write_multipart_content(y, val: val, closing: closing)
|
562
|
+
end
|
563
|
+
|
564
|
+
# @api private
|
565
|
+
#
|
566
|
+
# @param body [Object]
|
567
|
+
#
|
568
|
+
# @return [Array(String, Enumerable<String>)]
|
569
|
+
private def encode_multipart_streaming(body)
|
570
|
+
boundary = SecureRandom.urlsafe_base64(60)
|
571
|
+
|
572
|
+
closing = []
|
573
|
+
strio = writable_enum do |y|
|
574
|
+
case body
|
575
|
+
in Hash
|
576
|
+
body.each do |key, val|
|
577
|
+
case val
|
578
|
+
in Array if val.all? { primitive?(_1) }
|
579
|
+
val.each do |v|
|
580
|
+
write_multipart_chunk(y, boundary: boundary, key: key, val: v, closing: closing)
|
581
|
+
end
|
582
|
+
else
|
583
|
+
write_multipart_chunk(y, boundary: boundary, key: key, val: val, closing: closing)
|
584
|
+
end
|
585
|
+
end
|
586
|
+
else
|
587
|
+
write_multipart_chunk(y, boundary: boundary, key: nil, val: body, closing: closing)
|
588
|
+
end
|
589
|
+
y << "--#{boundary}--\r\n"
|
590
|
+
end
|
591
|
+
|
592
|
+
fused_io = fused_enum(strio) { closing.each(&:call) }
|
593
|
+
[boundary, fused_io]
|
594
|
+
end
|
595
|
+
|
596
|
+
# @api private
|
597
|
+
#
|
598
|
+
# @param headers [Hash{String=>String}]
|
599
|
+
# @param body [Object]
|
600
|
+
#
|
601
|
+
# @return [Object]
|
602
|
+
def encode_content(headers, body)
|
603
|
+
# rubocop:disable Style/CaseEquality
|
604
|
+
# rubocop:disable Layout/LineLength
|
605
|
+
content_type = headers["content-type"]
|
606
|
+
case [content_type, body]
|
607
|
+
in [BrandDev::Internal::Util::JSON_CONTENT, Hash | Array | -> { primitive?(_1) }]
|
608
|
+
[headers, JSON.generate(body)]
|
609
|
+
in [BrandDev::Internal::Util::JSONL_CONTENT, Enumerable] unless BrandDev::Internal::Type::FileInput === body
|
610
|
+
[headers, body.lazy.map { JSON.generate(_1) }]
|
611
|
+
in [%r{^multipart/form-data}, Hash | BrandDev::Internal::Type::FileInput]
|
612
|
+
boundary, strio = encode_multipart_streaming(body)
|
613
|
+
headers = {**headers, "content-type" => "#{content_type}; boundary=#{boundary}"}
|
614
|
+
[headers, strio]
|
615
|
+
in [_, Symbol | Numeric]
|
616
|
+
[headers, body.to_s]
|
617
|
+
in [_, StringIO]
|
618
|
+
[headers, body.string]
|
619
|
+
in [_, BrandDev::FilePart]
|
620
|
+
[headers, body.content]
|
621
|
+
else
|
622
|
+
[headers, body]
|
623
|
+
end
|
624
|
+
# rubocop:enable Layout/LineLength
|
625
|
+
# rubocop:enable Style/CaseEquality
|
626
|
+
end
|
627
|
+
|
628
|
+
# @api private
|
629
|
+
#
|
630
|
+
# https://www.iana.org/assignments/character-sets/character-sets.xhtml
|
631
|
+
#
|
632
|
+
# @param content_type [String]
|
633
|
+
# @param text [String]
|
634
|
+
def force_charset!(content_type, text:)
|
635
|
+
charset = /charset=([^;\s]+)/.match(content_type)&.captures&.first
|
636
|
+
|
637
|
+
return unless charset
|
638
|
+
|
639
|
+
begin
|
640
|
+
encoding = Encoding.find(charset)
|
641
|
+
text.force_encoding(encoding)
|
642
|
+
rescue ArgumentError
|
643
|
+
nil
|
644
|
+
end
|
645
|
+
end
|
646
|
+
|
647
|
+
# @api private
|
648
|
+
#
|
649
|
+
# Assumes each chunk in stream has `Encoding::BINARY`.
|
650
|
+
#
|
651
|
+
# @param headers [Hash{String=>String}, Net::HTTPHeader]
|
652
|
+
# @param stream [Enumerable<String>]
|
653
|
+
# @param suppress_error [Boolean]
|
654
|
+
#
|
655
|
+
# @raise [JSON::ParserError]
|
656
|
+
# @return [Object]
|
657
|
+
def decode_content(headers, stream:, suppress_error: false)
|
658
|
+
case (content_type = headers["content-type"])
|
659
|
+
in BrandDev::Internal::Util::JSON_CONTENT
|
660
|
+
json = stream.to_a.join
|
661
|
+
begin
|
662
|
+
JSON.parse(json, symbolize_names: true)
|
663
|
+
rescue JSON::ParserError => e
|
664
|
+
raise e unless suppress_error
|
665
|
+
json
|
666
|
+
end
|
667
|
+
in BrandDev::Internal::Util::JSONL_CONTENT
|
668
|
+
lines = decode_lines(stream)
|
669
|
+
chain_fused(lines) do |y|
|
670
|
+
lines.each { y << JSON.parse(_1, symbolize_names: true) }
|
671
|
+
end
|
672
|
+
in %r{^text/event-stream}
|
673
|
+
lines = decode_lines(stream)
|
674
|
+
decode_sse(lines)
|
675
|
+
else
|
676
|
+
text = stream.to_a.join
|
677
|
+
force_charset!(content_type, text: text)
|
678
|
+
StringIO.new(text)
|
679
|
+
end
|
680
|
+
end
|
681
|
+
end
|
682
|
+
|
683
|
+
class << self
|
684
|
+
# @api private
|
685
|
+
#
|
686
|
+
# https://doc.rust-lang.org/std/iter/trait.FusedIterator.html
|
687
|
+
#
|
688
|
+
# @param enum [Enumerable<Object>]
|
689
|
+
# @param external [Boolean]
|
690
|
+
# @param close [Proc]
|
691
|
+
#
|
692
|
+
# @return [Enumerable<Object>]
|
693
|
+
def fused_enum(enum, external: false, &close)
|
694
|
+
fused = false
|
695
|
+
iter = Enumerator.new do |y|
|
696
|
+
next if fused
|
697
|
+
|
698
|
+
fused = true
|
699
|
+
if external
|
700
|
+
loop { y << enum.next }
|
701
|
+
else
|
702
|
+
enum.each(&y)
|
703
|
+
end
|
704
|
+
ensure
|
705
|
+
close&.call
|
706
|
+
close = nil
|
707
|
+
end
|
708
|
+
|
709
|
+
iter.define_singleton_method(:rewind) do
|
710
|
+
fused = true
|
711
|
+
self
|
712
|
+
end
|
713
|
+
iter
|
714
|
+
end
|
715
|
+
|
716
|
+
# @api private
|
717
|
+
#
|
718
|
+
# @param enum [Enumerable<Object>, nil]
|
719
|
+
def close_fused!(enum)
|
720
|
+
return unless enum.is_a?(Enumerator)
|
721
|
+
|
722
|
+
# rubocop:disable Lint/UnreachableLoop
|
723
|
+
enum.rewind.each { break }
|
724
|
+
# rubocop:enable Lint/UnreachableLoop
|
725
|
+
end
|
726
|
+
|
727
|
+
# @api private
|
728
|
+
#
|
729
|
+
# @param enum [Enumerable<Object>, nil]
|
730
|
+
# @param blk [Proc]
|
731
|
+
#
|
732
|
+
# @yieldparam [Enumerator::Yielder]
|
733
|
+
# @return [Enumerable<Object>]
|
734
|
+
def chain_fused(enum, &blk)
|
735
|
+
iter = Enumerator.new { blk.call(_1) }
|
736
|
+
fused_enum(iter) { close_fused!(enum) }
|
737
|
+
end
|
738
|
+
end
|
739
|
+
|
740
|
+
class << self
|
741
|
+
# @api private
|
742
|
+
#
|
743
|
+
# Assumes Strings have been forced into having `Encoding::BINARY`.
|
744
|
+
#
|
745
|
+
# This decoder is responsible for reassembling lines split across multiple
|
746
|
+
# fragments.
|
747
|
+
#
|
748
|
+
# @param enum [Enumerable<String>]
|
749
|
+
#
|
750
|
+
# @return [Enumerable<String>]
|
751
|
+
def decode_lines(enum)
|
752
|
+
re = /(\r\n|\r|\n)/
|
753
|
+
buffer = String.new
|
754
|
+
cr_seen = nil
|
755
|
+
|
756
|
+
chain_fused(enum) do |y|
|
757
|
+
enum.each do |row|
|
758
|
+
offset = buffer.bytesize
|
759
|
+
buffer << row
|
760
|
+
while (match = re.match(buffer, cr_seen&.to_i || offset))
|
761
|
+
case [match.captures.first, cr_seen]
|
762
|
+
in ["\r", nil]
|
763
|
+
cr_seen = match.end(1)
|
764
|
+
next
|
765
|
+
in ["\r" | "\r\n", Integer]
|
766
|
+
y << buffer.slice!(..(cr_seen.pred))
|
767
|
+
else
|
768
|
+
y << buffer.slice!(..(match.end(1).pred))
|
769
|
+
end
|
770
|
+
offset = 0
|
771
|
+
cr_seen = nil
|
772
|
+
end
|
773
|
+
end
|
774
|
+
|
775
|
+
y << buffer.slice!(..(cr_seen.pred)) unless cr_seen.nil?
|
776
|
+
y << buffer unless buffer.empty?
|
777
|
+
end
|
778
|
+
end
|
779
|
+
|
780
|
+
# @api private
|
781
|
+
#
|
782
|
+
# https://html.spec.whatwg.org/multipage/server-sent-events.html#parsing-an-event-stream
|
783
|
+
#
|
784
|
+
# Assumes that `lines` has been decoded with `#decode_lines`.
|
785
|
+
#
|
786
|
+
# @param lines [Enumerable<String>]
|
787
|
+
#
|
788
|
+
# @return [Enumerable<Hash{Symbol=>Object}>]
|
789
|
+
def decode_sse(lines)
|
790
|
+
# rubocop:disable Metrics/BlockLength
|
791
|
+
chain_fused(lines) do |y|
|
792
|
+
blank = {event: nil, data: nil, id: nil, retry: nil}
|
793
|
+
current = {}
|
794
|
+
|
795
|
+
lines.each do |line|
|
796
|
+
case line.sub(/\R$/, "")
|
797
|
+
in ""
|
798
|
+
next if current.empty?
|
799
|
+
y << {**blank, **current}
|
800
|
+
current = {}
|
801
|
+
in /^:/
|
802
|
+
next
|
803
|
+
in /^([^:]+):\s?(.*)$/
|
804
|
+
field, value = Regexp.last_match.captures
|
805
|
+
case field
|
806
|
+
in "event"
|
807
|
+
current.merge!(event: value)
|
808
|
+
in "data"
|
809
|
+
(current[:data] ||= String.new) << (value << "\n")
|
810
|
+
in "id" unless value.include?("\0")
|
811
|
+
current.merge!(id: value)
|
812
|
+
in "retry" if /^\d+$/ =~ value
|
813
|
+
current.merge!(retry: Integer(value))
|
814
|
+
else
|
815
|
+
end
|
816
|
+
else
|
817
|
+
end
|
818
|
+
end
|
819
|
+
# rubocop:enable Metrics/BlockLength
|
820
|
+
|
821
|
+
y << {**blank, **current} unless current.empty?
|
822
|
+
end
|
823
|
+
end
|
824
|
+
end
|
825
|
+
|
826
|
+
# @api private
|
827
|
+
module SorbetRuntimeSupport
|
828
|
+
class MissingSorbetRuntimeError < ::RuntimeError
|
829
|
+
end
|
830
|
+
|
831
|
+
# @api private
|
832
|
+
#
|
833
|
+
# @return [Hash{Symbol=>Object}]
|
834
|
+
private def sorbet_runtime_constants = @sorbet_runtime_constants ||= {}
|
835
|
+
|
836
|
+
# @api private
|
837
|
+
#
|
838
|
+
# @param name [Symbol]
|
839
|
+
def const_missing(name)
|
840
|
+
super unless sorbet_runtime_constants.key?(name)
|
841
|
+
|
842
|
+
unless Object.const_defined?(:T)
|
843
|
+
message = "Trying to access a Sorbet constant #{name.inspect} without `sorbet-runtime`."
|
844
|
+
raise MissingSorbetRuntimeError.new(message)
|
845
|
+
end
|
846
|
+
|
847
|
+
sorbet_runtime_constants.fetch(name).call
|
848
|
+
end
|
849
|
+
|
850
|
+
# @api private
|
851
|
+
#
|
852
|
+
# @param name [Symbol]
|
853
|
+
#
|
854
|
+
# @return [Boolean]
|
855
|
+
def sorbet_constant_defined?(name) = sorbet_runtime_constants.key?(name)
|
856
|
+
|
857
|
+
# @api private
|
858
|
+
#
|
859
|
+
# @param name [Symbol]
|
860
|
+
# @param blk [Proc]
|
861
|
+
def define_sorbet_constant!(name, &blk) = sorbet_runtime_constants.store(name, blk)
|
862
|
+
|
863
|
+
# @api private
|
864
|
+
#
|
865
|
+
# @return [Object]
|
866
|
+
def to_sorbet_type = raise NotImplementedError
|
867
|
+
|
868
|
+
class << self
|
869
|
+
# @api private
|
870
|
+
#
|
871
|
+
# @param type [BrandDev::Internal::Util::SorbetRuntimeSupport, Object]
|
872
|
+
#
|
873
|
+
# @return [Object]
|
874
|
+
def to_sorbet_type(type)
|
875
|
+
case type
|
876
|
+
in BrandDev::Internal::Util::SorbetRuntimeSupport
|
877
|
+
type.to_sorbet_type
|
878
|
+
in Class | Module
|
879
|
+
type
|
880
|
+
in true | false
|
881
|
+
T::Boolean
|
882
|
+
else
|
883
|
+
type.class
|
884
|
+
end
|
885
|
+
end
|
886
|
+
end
|
887
|
+
end
|
888
|
+
|
889
|
+
extend BrandDev::Internal::Util::SorbetRuntimeSupport
|
890
|
+
|
891
|
+
define_sorbet_constant!(:ParsedUri) do
|
892
|
+
T.type_alias do
|
893
|
+
{
|
894
|
+
scheme: T.nilable(String),
|
895
|
+
host: T.nilable(String),
|
896
|
+
port: T.nilable(Integer),
|
897
|
+
path: T.nilable(String),
|
898
|
+
query: T::Hash[String, T::Array[String]]
|
899
|
+
}
|
900
|
+
end
|
901
|
+
end
|
902
|
+
|
903
|
+
define_sorbet_constant!(:ServerSentEvent) do
|
904
|
+
T.type_alias do
|
905
|
+
{
|
906
|
+
event: T.nilable(String),
|
907
|
+
data: T.nilable(String),
|
908
|
+
id: T.nilable(String),
|
909
|
+
retry: T.nilable(Integer)
|
910
|
+
}
|
911
|
+
end
|
912
|
+
end
|
913
|
+
end
|
914
|
+
end
|
915
|
+
end
|