oursprivacy-ingest 0.1.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.
Files changed (89) hide show
  1. checksums.yaml +7 -0
  2. data/.ignore +2 -0
  3. data/CHANGELOG.md +16 -0
  4. data/README.md +206 -0
  5. data/SECURITY.md +27 -0
  6. data/lib/oursprivacy_ingest/client.rb +64 -0
  7. data/lib/oursprivacy_ingest/errors.rb +228 -0
  8. data/lib/oursprivacy_ingest/file_part.rb +58 -0
  9. data/lib/oursprivacy_ingest/internal/transport/base_client.rb +573 -0
  10. data/lib/oursprivacy_ingest/internal/transport/pooled_net_requester.rb +204 -0
  11. data/lib/oursprivacy_ingest/internal/type/array_of.rb +168 -0
  12. data/lib/oursprivacy_ingest/internal/type/base_model.rb +534 -0
  13. data/lib/oursprivacy_ingest/internal/type/base_page.rb +55 -0
  14. data/lib/oursprivacy_ingest/internal/type/boolean.rb +77 -0
  15. data/lib/oursprivacy_ingest/internal/type/converter.rb +327 -0
  16. data/lib/oursprivacy_ingest/internal/type/enum.rb +133 -0
  17. data/lib/oursprivacy_ingest/internal/type/file_input.rb +111 -0
  18. data/lib/oursprivacy_ingest/internal/type/hash_of.rb +188 -0
  19. data/lib/oursprivacy_ingest/internal/type/request_parameters.rb +42 -0
  20. data/lib/oursprivacy_ingest/internal/type/union.rb +245 -0
  21. data/lib/oursprivacy_ingest/internal/type/unknown.rb +81 -0
  22. data/lib/oursprivacy_ingest/internal/util.rb +915 -0
  23. data/lib/oursprivacy_ingest/internal.rb +20 -0
  24. data/lib/oursprivacy_ingest/models/track_event_params.rb +1009 -0
  25. data/lib/oursprivacy_ingest/models/track_event_response.rb +26 -0
  26. data/lib/oursprivacy_ingest/models/visitor_upsert_params.rb +972 -0
  27. data/lib/oursprivacy_ingest/models/visitor_upsert_response.rb +26 -0
  28. data/lib/oursprivacy_ingest/models.rb +48 -0
  29. data/lib/oursprivacy_ingest/request_options.rb +79 -0
  30. data/lib/oursprivacy_ingest/resources/track.rb +61 -0
  31. data/lib/oursprivacy_ingest/resources/visitor.rb +52 -0
  32. data/lib/oursprivacy_ingest/version.rb +5 -0
  33. data/lib/oursprivacy_ingest.rb +59 -0
  34. data/manifest.yaml +15 -0
  35. data/rbi/oursprivacy_ingest/client.rbi +46 -0
  36. data/rbi/oursprivacy_ingest/errors.rbi +205 -0
  37. data/rbi/oursprivacy_ingest/file_part.rbi +37 -0
  38. data/rbi/oursprivacy_ingest/internal/transport/base_client.rbi +305 -0
  39. data/rbi/oursprivacy_ingest/internal/transport/pooled_net_requester.rbi +80 -0
  40. data/rbi/oursprivacy_ingest/internal/type/array_of.rbi +108 -0
  41. data/rbi/oursprivacy_ingest/internal/type/base_model.rbi +316 -0
  42. data/rbi/oursprivacy_ingest/internal/type/base_page.rbi +43 -0
  43. data/rbi/oursprivacy_ingest/internal/type/boolean.rbi +58 -0
  44. data/rbi/oursprivacy_ingest/internal/type/converter.rbi +225 -0
  45. data/rbi/oursprivacy_ingest/internal/type/enum.rbi +82 -0
  46. data/rbi/oursprivacy_ingest/internal/type/file_input.rbi +59 -0
  47. data/rbi/oursprivacy_ingest/internal/type/hash_of.rbi +108 -0
  48. data/rbi/oursprivacy_ingest/internal/type/request_parameters.rbi +33 -0
  49. data/rbi/oursprivacy_ingest/internal/type/union.rbi +134 -0
  50. data/rbi/oursprivacy_ingest/internal/type/unknown.rbi +58 -0
  51. data/rbi/oursprivacy_ingest/internal/util.rbi +487 -0
  52. data/rbi/oursprivacy_ingest/internal.rbi +18 -0
  53. data/rbi/oursprivacy_ingest/models/track_event_params.rbi +1072 -0
  54. data/rbi/oursprivacy_ingest/models/track_event_response.rbi +71 -0
  55. data/rbi/oursprivacy_ingest/models/visitor_upsert_params.rbi +1035 -0
  56. data/rbi/oursprivacy_ingest/models/visitor_upsert_response.rbi +71 -0
  57. data/rbi/oursprivacy_ingest/models.rbi +7 -0
  58. data/rbi/oursprivacy_ingest/request_options.rbi +64 -0
  59. data/rbi/oursprivacy_ingest/resources/track.rbi +74 -0
  60. data/rbi/oursprivacy_ingest/resources/visitor.rbi +58 -0
  61. data/rbi/oursprivacy_ingest/version.rbi +5 -0
  62. data/sig/oursprivacy_ingest/client.rbs +25 -0
  63. data/sig/oursprivacy_ingest/errors.rbs +117 -0
  64. data/sig/oursprivacy_ingest/file_part.rbs +21 -0
  65. data/sig/oursprivacy_ingest/internal/transport/base_client.rbs +131 -0
  66. data/sig/oursprivacy_ingest/internal/transport/pooled_net_requester.rbs +45 -0
  67. data/sig/oursprivacy_ingest/internal/type/array_of.rbs +48 -0
  68. data/sig/oursprivacy_ingest/internal/type/base_model.rbs +106 -0
  69. data/sig/oursprivacy_ingest/internal/type/base_page.rbs +24 -0
  70. data/sig/oursprivacy_ingest/internal/type/boolean.rbs +26 -0
  71. data/sig/oursprivacy_ingest/internal/type/converter.rbs +79 -0
  72. data/sig/oursprivacy_ingest/internal/type/enum.rbs +32 -0
  73. data/sig/oursprivacy_ingest/internal/type/file_input.rbs +25 -0
  74. data/sig/oursprivacy_ingest/internal/type/hash_of.rbs +48 -0
  75. data/sig/oursprivacy_ingest/internal/type/request_parameters.rbs +20 -0
  76. data/sig/oursprivacy_ingest/internal/type/union.rbs +52 -0
  77. data/sig/oursprivacy_ingest/internal/type/unknown.rbs +26 -0
  78. data/sig/oursprivacy_ingest/internal/util.rbs +185 -0
  79. data/sig/oursprivacy_ingest/internal.rbs +10 -0
  80. data/sig/oursprivacy_ingest/models/track_event_params.rbs +673 -0
  81. data/sig/oursprivacy_ingest/models/track_event_response.rbs +28 -0
  82. data/sig/oursprivacy_ingest/models/visitor_upsert_params.rbs +653 -0
  83. data/sig/oursprivacy_ingest/models/visitor_upsert_response.rbs +28 -0
  84. data/sig/oursprivacy_ingest/models.rbs +5 -0
  85. data/sig/oursprivacy_ingest/request_options.rbs +36 -0
  86. data/sig/oursprivacy_ingest/resources/track.rbs +21 -0
  87. data/sig/oursprivacy_ingest/resources/visitor.rbs +17 -0
  88. data/sig/oursprivacy_ingest/version.rbs +3 -0
  89. metadata +146 -0
@@ -0,0 +1,573 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OursprivacyIngest
4
+ module Internal
5
+ module Transport
6
+ # @api private
7
+ #
8
+ # @abstract
9
+ class BaseClient
10
+ extend OursprivacyIngest::Internal::Util::SorbetRuntimeSupport
11
+
12
+ # from whatwg fetch spec
13
+ MAX_REDIRECTS = 20
14
+
15
+ # rubocop:disable Style/MutableConstant
16
+ PLATFORM_HEADERS =
17
+ {
18
+ "x-stainless-arch" => OursprivacyIngest::Internal::Util.arch,
19
+ "x-stainless-lang" => "ruby",
20
+ "x-stainless-os" => OursprivacyIngest::Internal::Util.os,
21
+ "x-stainless-package-version" => OursprivacyIngest::VERSION,
22
+ "x-stainless-runtime" => ::RUBY_ENGINE,
23
+ "x-stainless-runtime-version" => ::RUBY_ENGINE_VERSION
24
+ }
25
+ # rubocop:enable Style/MutableConstant
26
+
27
+ class << self
28
+ # @api private
29
+ #
30
+ # @param req [Hash{Symbol=>Object}]
31
+ #
32
+ # @raise [ArgumentError]
33
+ def validate!(req)
34
+ keys = [:method, :path, :query, :headers, :body, :unwrap, :page, :stream, :model, :options]
35
+ case req
36
+ in Hash
37
+ req.each_key do |k|
38
+ unless keys.include?(k)
39
+ raise ArgumentError.new("Request `req` keys must be one of #{keys}, got #{k.inspect}")
40
+ end
41
+ end
42
+ else
43
+ raise ArgumentError.new("Request `req` must be a Hash or RequestOptions, got #{req.inspect}")
44
+ end
45
+ end
46
+
47
+ # @api private
48
+ #
49
+ # @param status [Integer]
50
+ # @param headers [Hash{String=>String}]
51
+ #
52
+ # @return [Boolean]
53
+ def should_retry?(status, headers:)
54
+ coerced = OursprivacyIngest::Internal::Util.coerce_boolean(headers["x-should-retry"])
55
+ case [coerced, status]
56
+ in [true | false, _]
57
+ coerced
58
+ in [_, 408 | 409 | 429 | (500..)]
59
+ # retry on:
60
+ # 408: timeouts
61
+ # 409: locks
62
+ # 429: rate limits
63
+ # 500+: unknown errors
64
+ true
65
+ else
66
+ false
67
+ end
68
+ end
69
+
70
+ # @api private
71
+ #
72
+ # @param request [Hash{Symbol=>Object}] .
73
+ #
74
+ # @option request [Symbol] :method
75
+ #
76
+ # @option request [URI::Generic] :url
77
+ #
78
+ # @option request [Hash{String=>String}] :headers
79
+ #
80
+ # @option request [Object] :body
81
+ #
82
+ # @option request [Integer] :max_retries
83
+ #
84
+ # @option request [Float] :timeout
85
+ #
86
+ # @param status [Integer]
87
+ #
88
+ # @param response_headers [Hash{String=>String}]
89
+ #
90
+ # @return [Hash{Symbol=>Object}]
91
+ def follow_redirect(request, status:, response_headers:)
92
+ method, url, headers = request.fetch_values(:method, :url, :headers)
93
+ location =
94
+ Kernel.then do
95
+ URI.join(url, response_headers["location"])
96
+ rescue ArgumentError
97
+ message = "Server responded with status #{status} but no valid location header."
98
+ raise OursprivacyIngest::Errors::APIConnectionError.new(
99
+ url: url,
100
+ response: response_headers,
101
+ message: message
102
+ )
103
+ end
104
+
105
+ request = {**request, url: location}
106
+
107
+ case [url.scheme, location.scheme]
108
+ in ["https", "http"]
109
+ message = "Tried to redirect to a insecure URL"
110
+ raise OursprivacyIngest::Errors::APIConnectionError.new(
111
+ url: url,
112
+ response: response_headers,
113
+ message: message
114
+ )
115
+ else
116
+ nil
117
+ end
118
+
119
+ # from whatwg fetch spec
120
+ case [status, method]
121
+ in [301 | 302, :post] | [303, _]
122
+ drop = %w[content-encoding content-language content-length content-location content-type]
123
+ request = {
124
+ **request,
125
+ method: method == :head ? :head : :get,
126
+ headers: headers.except(*drop),
127
+ body: nil
128
+ }
129
+ else
130
+ end
131
+
132
+ # from undici
133
+ if OursprivacyIngest::Internal::Util.uri_origin(url) != OursprivacyIngest::Internal::Util.uri_origin(location)
134
+ drop = %w[authorization cookie host proxy-authorization]
135
+ request = {**request, headers: request.fetch(:headers).except(*drop)}
136
+ end
137
+
138
+ request
139
+ end
140
+
141
+ # @api private
142
+ #
143
+ # @param status [Integer, OursprivacyIngest::Errors::APIConnectionError]
144
+ # @param stream [Enumerable<String>, nil]
145
+ def reap_connection!(status, stream:)
146
+ case status
147
+ in (..199) | (300..499)
148
+ stream&.each { next }
149
+ in OursprivacyIngest::Errors::APIConnectionError | (500..)
150
+ OursprivacyIngest::Internal::Util.close_fused!(stream)
151
+ else
152
+ end
153
+ end
154
+ end
155
+
156
+ # @return [URI::Generic]
157
+ attr_reader :base_url
158
+
159
+ # @return [Float]
160
+ attr_reader :timeout
161
+
162
+ # @return [Integer]
163
+ attr_reader :max_retries
164
+
165
+ # @return [Float]
166
+ attr_reader :initial_retry_delay
167
+
168
+ # @return [Float]
169
+ attr_reader :max_retry_delay
170
+
171
+ # @return [Hash{String=>String}]
172
+ attr_reader :headers
173
+
174
+ # @return [String, nil]
175
+ attr_reader :idempotency_header
176
+
177
+ # @api private
178
+ # @return [OursprivacyIngest::Internal::Transport::PooledNetRequester]
179
+ attr_reader :requester
180
+
181
+ # @api private
182
+ #
183
+ # @param base_url [String]
184
+ # @param timeout [Float]
185
+ # @param max_retries [Integer]
186
+ # @param initial_retry_delay [Float]
187
+ # @param max_retry_delay [Float]
188
+ # @param headers [Hash{String=>String, Integer, Array<String, Integer, nil>, nil}]
189
+ # @param idempotency_header [String, nil]
190
+ def initialize(
191
+ base_url:,
192
+ timeout: 0.0,
193
+ max_retries: 0,
194
+ initial_retry_delay: 0.0,
195
+ max_retry_delay: 0.0,
196
+ headers: {},
197
+ idempotency_header: nil
198
+ )
199
+ @requester = OursprivacyIngest::Internal::Transport::PooledNetRequester.new
200
+ @headers = OursprivacyIngest::Internal::Util.normalized_headers(
201
+ self.class::PLATFORM_HEADERS,
202
+ {
203
+ "accept" => "application/json",
204
+ "content-type" => "application/json",
205
+ "user-agent" => user_agent
206
+ },
207
+ headers
208
+ )
209
+ @base_url_components = OursprivacyIngest::Internal::Util.parse_uri(base_url)
210
+ @base_url = OursprivacyIngest::Internal::Util.unparse_uri(@base_url_components)
211
+ @idempotency_header = idempotency_header&.to_s&.downcase
212
+ @timeout = timeout
213
+ @max_retries = max_retries
214
+ @initial_retry_delay = initial_retry_delay
215
+ @max_retry_delay = max_retry_delay
216
+ end
217
+
218
+ # @api private
219
+ #
220
+ # @return [String]
221
+ private def user_agent = "#{self.class.name}/Ruby #{OursprivacyIngest::VERSION}"
222
+
223
+ # @api private
224
+ #
225
+ # @return [String]
226
+ private def generate_idempotency_key = "stainless-ruby-retry-#{SecureRandom.uuid}"
227
+
228
+ # @api private
229
+ #
230
+ # @param req [Hash{Symbol=>Object}] .
231
+ #
232
+ # @option req [Symbol] :method
233
+ #
234
+ # @option req [String, Array<String>] :path
235
+ #
236
+ # @option req [Hash{String=>Array<String>, String, nil}, nil] :query
237
+ #
238
+ # @option req [Hash{String=>String, Integer, Array<String, Integer, nil>, nil}, nil] :headers
239
+ #
240
+ # @option req [Object, nil] :body
241
+ #
242
+ # @option req [Symbol, Integer, Array<Symbol, Integer>, Proc, nil] :unwrap
243
+ #
244
+ # @option req [Class<OursprivacyIngest::Internal::Type::BasePage>, nil] :page
245
+ #
246
+ # @option req [Class<OursprivacyIngest::Internal::Type::BaseStream>, nil] :stream
247
+ #
248
+ # @option req [OursprivacyIngest::Internal::Type::Converter, Class, nil] :model
249
+ #
250
+ # @param opts [Hash{Symbol=>Object}] .
251
+ #
252
+ # @option opts [String, nil] :idempotency_key
253
+ #
254
+ # @option opts [Hash{String=>Array<String>, String, nil}, nil] :extra_query
255
+ #
256
+ # @option opts [Hash{String=>String, nil}, nil] :extra_headers
257
+ #
258
+ # @option opts [Object, nil] :extra_body
259
+ #
260
+ # @option opts [Integer, nil] :max_retries
261
+ #
262
+ # @option opts [Float, nil] :timeout
263
+ #
264
+ # @return [Hash{Symbol=>Object}]
265
+ private def build_request(req, opts)
266
+ method, uninterpolated_path = req.fetch_values(:method, :path)
267
+
268
+ path = OursprivacyIngest::Internal::Util.interpolate_path(uninterpolated_path)
269
+
270
+ query = OursprivacyIngest::Internal::Util.deep_merge(req[:query].to_h, opts[:extra_query].to_h)
271
+
272
+ headers = OursprivacyIngest::Internal::Util.normalized_headers(
273
+ @headers,
274
+ req[:headers].to_h,
275
+ opts[:extra_headers].to_h
276
+ )
277
+
278
+ if @idempotency_header &&
279
+ !headers.key?(@idempotency_header) &&
280
+ (!Net::HTTP::IDEMPOTENT_METHODS_.include?(method.to_s.upcase) || opts.key?(:idempotency_key))
281
+ headers[@idempotency_header] = opts.fetch(:idempotency_key) { generate_idempotency_key }
282
+ end
283
+
284
+ unless headers.key?("x-stainless-retry-count")
285
+ headers["x-stainless-retry-count"] = "0"
286
+ end
287
+
288
+ timeout = opts.fetch(:timeout, @timeout).to_f.clamp(0..)
289
+ unless headers.key?("x-stainless-timeout") || timeout.zero?
290
+ headers["x-stainless-timeout"] = timeout.to_s
291
+ end
292
+
293
+ headers.reject! { |_, v| v.to_s.empty? }
294
+
295
+ body =
296
+ case method
297
+ in :get | :head | :options | :trace
298
+ nil
299
+ else
300
+ OursprivacyIngest::Internal::Util.deep_merge(*[req[:body], opts[:extra_body]].compact)
301
+ end
302
+
303
+ url = OursprivacyIngest::Internal::Util.join_parsed_uri(
304
+ @base_url_components,
305
+ {**req, path: path, query: query}
306
+ )
307
+ headers, encoded = OursprivacyIngest::Internal::Util.encode_content(headers, body)
308
+ {
309
+ method: method,
310
+ url: url,
311
+ headers: headers,
312
+ body: encoded,
313
+ max_retries: opts.fetch(:max_retries, @max_retries),
314
+ timeout: timeout
315
+ }
316
+ end
317
+
318
+ # @api private
319
+ #
320
+ # @param headers [Hash{String=>String}]
321
+ # @param retry_count [Integer]
322
+ #
323
+ # @return [Float]
324
+ private def retry_delay(headers, retry_count:)
325
+ # Non-standard extension
326
+ span = Float(headers["retry-after-ms"], exception: false)&.then { _1 / 1000 }
327
+ return span if span
328
+
329
+ retry_header = headers["retry-after"]
330
+ return span if (span = Float(retry_header, exception: false))
331
+
332
+ span = retry_header&.then do
333
+ Time.httpdate(_1) - Time.now
334
+ rescue ArgumentError
335
+ nil
336
+ end
337
+ return span if span
338
+
339
+ scale = retry_count**2
340
+ jitter = 1 - (0.25 * rand)
341
+ (@initial_retry_delay * scale * jitter).clamp(0, @max_retry_delay)
342
+ end
343
+
344
+ # @api private
345
+ #
346
+ # @param request [Hash{Symbol=>Object}] .
347
+ #
348
+ # @option request [Symbol] :method
349
+ #
350
+ # @option request [URI::Generic] :url
351
+ #
352
+ # @option request [Hash{String=>String}] :headers
353
+ #
354
+ # @option request [Object] :body
355
+ #
356
+ # @option request [Integer] :max_retries
357
+ #
358
+ # @option request [Float] :timeout
359
+ #
360
+ # @param redirect_count [Integer]
361
+ #
362
+ # @param retry_count [Integer]
363
+ #
364
+ # @param send_retry_header [Boolean]
365
+ #
366
+ # @raise [OursprivacyIngest::Errors::APIError]
367
+ # @return [Array(Integer, Net::HTTPResponse, Enumerable<String>)]
368
+ def send_request(request, redirect_count:, retry_count:, send_retry_header:)
369
+ url, headers, max_retries, timeout = request.fetch_values(:url, :headers, :max_retries, :timeout)
370
+ input = {**request.except(:timeout), deadline: OursprivacyIngest::Internal::Util.monotonic_secs + timeout}
371
+
372
+ if send_retry_header
373
+ headers["x-stainless-retry-count"] = retry_count.to_s
374
+ end
375
+
376
+ begin
377
+ status, response, stream = @requester.execute(input)
378
+ rescue OursprivacyIngest::Errors::APIConnectionError => e
379
+ status = e
380
+ end
381
+ headers = OursprivacyIngest::Internal::Util.normalized_headers(response&.each_header&.to_h)
382
+
383
+ case status
384
+ in ..299
385
+ [status, response, stream]
386
+ in 300..399 if redirect_count >= self.class::MAX_REDIRECTS
387
+ self.class.reap_connection!(status, stream: stream)
388
+
389
+ message = "Failed to complete the request within #{self.class::MAX_REDIRECTS} redirects."
390
+ raise OursprivacyIngest::Errors::APIConnectionError.new(
391
+ url: url,
392
+ response: response,
393
+ message: message
394
+ )
395
+ in 300..399
396
+ self.class.reap_connection!(status, stream: stream)
397
+
398
+ request = self.class.follow_redirect(request, status: status, response_headers: headers)
399
+ send_request(
400
+ request,
401
+ redirect_count: redirect_count + 1,
402
+ retry_count: retry_count,
403
+ send_retry_header: send_retry_header
404
+ )
405
+ in OursprivacyIngest::Errors::APIConnectionError if retry_count >= max_retries
406
+ raise status
407
+ in (400..) if retry_count >= max_retries || !self.class.should_retry?(status, headers: headers)
408
+ decoded = Kernel.then do
409
+ OursprivacyIngest::Internal::Util.decode_content(headers, stream: stream, suppress_error: true)
410
+ ensure
411
+ self.class.reap_connection!(status, stream: stream)
412
+ end
413
+
414
+ raise OursprivacyIngest::Errors::APIStatusError.for(
415
+ url: url,
416
+ status: status,
417
+ headers: headers,
418
+ body: decoded,
419
+ request: nil,
420
+ response: response
421
+ )
422
+ in (400..) | OursprivacyIngest::Errors::APIConnectionError
423
+ self.class.reap_connection!(status, stream: stream)
424
+
425
+ delay = retry_delay(response || {}, retry_count: retry_count)
426
+ sleep(delay)
427
+
428
+ send_request(
429
+ request,
430
+ redirect_count: redirect_count,
431
+ retry_count: retry_count + 1,
432
+ send_retry_header: send_retry_header
433
+ )
434
+ end
435
+ end
436
+
437
+ # Execute the request specified by `req`. This is the method that all resource
438
+ # methods call into.
439
+ #
440
+ # @overload request(method, path, query: {}, headers: {}, body: nil, unwrap: nil, page: nil, stream: nil, model: OursprivacyIngest::Internal::Type::Unknown, options: {})
441
+ #
442
+ # @param method [Symbol]
443
+ #
444
+ # @param path [String, Array<String>]
445
+ #
446
+ # @param query [Hash{String=>Array<String>, String, nil}, nil]
447
+ #
448
+ # @param headers [Hash{String=>String, Integer, Array<String, Integer, nil>, nil}, nil]
449
+ #
450
+ # @param body [Object, nil]
451
+ #
452
+ # @param unwrap [Symbol, Integer, Array<Symbol, Integer>, Proc, nil]
453
+ #
454
+ # @param page [Class<OursprivacyIngest::Internal::Type::BasePage>, nil]
455
+ #
456
+ # @param stream [Class<OursprivacyIngest::Internal::Type::BaseStream>, nil]
457
+ #
458
+ # @param model [OursprivacyIngest::Internal::Type::Converter, Class, nil]
459
+ #
460
+ # @param options [OursprivacyIngest::RequestOptions, Hash{Symbol=>Object}, nil] .
461
+ #
462
+ # @option options [String, nil] :idempotency_key
463
+ #
464
+ # @option options [Hash{String=>Array<String>, String, nil}, nil] :extra_query
465
+ #
466
+ # @option options [Hash{String=>String, nil}, nil] :extra_headers
467
+ #
468
+ # @option options [Object, nil] :extra_body
469
+ #
470
+ # @option options [Integer, nil] :max_retries
471
+ #
472
+ # @option options [Float, nil] :timeout
473
+ #
474
+ # @raise [OursprivacyIngest::Errors::APIError]
475
+ # @return [Object]
476
+ def request(req)
477
+ self.class.validate!(req)
478
+ model = req.fetch(:model) { OursprivacyIngest::Internal::Type::Unknown }
479
+ opts = req[:options].to_h
480
+ unwrap = req[:unwrap]
481
+ OursprivacyIngest::RequestOptions.validate!(opts)
482
+ request = build_request(req.except(:options), opts)
483
+ url = request.fetch(:url)
484
+
485
+ # Don't send the current retry count in the headers if the caller modified the header defaults.
486
+ send_retry_header = request.fetch(:headers)["x-stainless-retry-count"] == "0"
487
+ status, response, stream = send_request(
488
+ request,
489
+ redirect_count: 0,
490
+ retry_count: 0,
491
+ send_retry_header: send_retry_header
492
+ )
493
+
494
+ headers = OursprivacyIngest::Internal::Util.normalized_headers(response.each_header.to_h)
495
+ decoded = OursprivacyIngest::Internal::Util.decode_content(headers, stream: stream)
496
+ case req
497
+ in {stream: Class => st}
498
+ st.new(
499
+ model: model,
500
+ url: url,
501
+ status: status,
502
+ headers: headers,
503
+ response: response,
504
+ unwrap: unwrap,
505
+ stream: decoded
506
+ )
507
+ in {page: Class => page}
508
+ page.new(client: self, req: req, headers: headers, page_data: decoded)
509
+ else
510
+ unwrapped = OursprivacyIngest::Internal::Util.dig(decoded, unwrap)
511
+ OursprivacyIngest::Internal::Type::Converter.coerce(model, unwrapped)
512
+ end
513
+ end
514
+
515
+ # @api private
516
+ #
517
+ # @return [String]
518
+ def inspect
519
+ # rubocop:disable Layout/LineLength
520
+ "#<#{self.class.name}:0x#{object_id.to_s(16)} base_url=#{@base_url} max_retries=#{@max_retries} timeout=#{@timeout}>"
521
+ # rubocop:enable Layout/LineLength
522
+ end
523
+
524
+ define_sorbet_constant!(:RequestComponents) do
525
+ T.type_alias do
526
+ {
527
+ method: Symbol,
528
+ path: T.any(String, T::Array[String]),
529
+ query: T.nilable(T::Hash[String, T.nilable(T.any(T::Array[String], String))]),
530
+ headers: T.nilable(
531
+ T::Hash[String,
532
+ T.nilable(
533
+ T.any(
534
+ String,
535
+ Integer,
536
+ T::Array[T.nilable(T.any(String, Integer))]
537
+ )
538
+ )]
539
+ ),
540
+ body: T.nilable(T.anything),
541
+ unwrap: T.nilable(
542
+ T.any(
543
+ Symbol,
544
+ Integer,
545
+ T::Array[T.any(Symbol, Integer)],
546
+ T.proc.params(arg0: T.anything).returns(T.anything)
547
+ )
548
+ ),
549
+ page: T.nilable(
550
+ T::Class[OursprivacyIngest::Internal::Type::BasePage[OursprivacyIngest::Internal::Type::BaseModel]]
551
+ ),
552
+ stream: T.nilable(T::Class[T.anything]),
553
+ model: T.nilable(OursprivacyIngest::Internal::Type::Converter::Input),
554
+ options: T.nilable(OursprivacyIngest::RequestOptions::OrHash)
555
+ }
556
+ end
557
+ end
558
+ define_sorbet_constant!(:RequestInput) do
559
+ T.type_alias do
560
+ {
561
+ method: Symbol,
562
+ url: URI::Generic,
563
+ headers: T::Hash[String, String],
564
+ body: T.anything,
565
+ max_retries: Integer,
566
+ timeout: Float
567
+ }
568
+ end
569
+ end
570
+ end
571
+ end
572
+ end
573
+ end