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