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