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