finch-api 0.1.0.pre.alpha.4 → 0.1.0.pre.alpha.5

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 (204) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -8
  3. data/lib/finch-api/client.rb +1 -1
  4. data/lib/finch-api/individuals_page.rb +4 -4
  5. data/lib/finch-api/models/access_token_create_params.rb +1 -1
  6. data/lib/finch-api/models/account_disconnect_params.rb +1 -1
  7. data/lib/finch-api/models/account_introspect_params.rb +1 -1
  8. data/lib/finch-api/models/connect/session_new_params.rb +1 -1
  9. data/lib/finch-api/models/connect/session_reauthenticate_params.rb +1 -1
  10. data/lib/finch-api/models/hris/benefit_create_params.rb +1 -1
  11. data/lib/finch-api/models/hris/benefit_list_params.rb +1 -1
  12. data/lib/finch-api/models/hris/benefit_list_supported_benefits_params.rb +1 -1
  13. data/lib/finch-api/models/hris/benefit_retrieve_params.rb +1 -1
  14. data/lib/finch-api/models/hris/benefit_update_params.rb +1 -1
  15. data/lib/finch-api/models/hris/benefits/individual_enroll_many_params.rb +1 -1
  16. data/lib/finch-api/models/hris/benefits/individual_enrolled_ids_params.rb +1 -1
  17. data/lib/finch-api/models/hris/benefits/individual_retrieve_many_benefits_params.rb +1 -1
  18. data/lib/finch-api/models/hris/benefits/individual_unenroll_many_params.rb +1 -1
  19. data/lib/finch-api/models/hris/company_retrieve_params.rb +1 -1
  20. data/lib/finch-api/models/hris/directory_list_individuals_params.rb +1 -1
  21. data/lib/finch-api/models/hris/directory_list_params.rb +1 -1
  22. data/lib/finch-api/models/hris/document_list_params.rb +1 -1
  23. data/lib/finch-api/models/hris/document_retreive_params.rb +1 -1
  24. data/lib/finch-api/models/hris/employment_retrieve_many_params.rb +1 -1
  25. data/lib/finch-api/models/hris/individual_retrieve_many_params.rb +1 -1
  26. data/lib/finch-api/models/hris/pay_statement_retrieve_many_params.rb +1 -1
  27. data/lib/finch-api/models/hris/payment_list_params.rb +1 -1
  28. data/lib/finch-api/models/jobs/automated_create_params.rb +1 -1
  29. data/lib/finch-api/models/jobs/automated_list_params.rb +1 -1
  30. data/lib/finch-api/models/jobs/automated_retrieve_params.rb +1 -1
  31. data/lib/finch-api/models/jobs/manual_retrieve_params.rb +1 -1
  32. data/lib/finch-api/models/payroll/pay_group_list_params.rb +1 -1
  33. data/lib/finch-api/models/payroll/pay_group_retrieve_params.rb +1 -1
  34. data/lib/finch-api/models/provider_list_params.rb +1 -1
  35. data/lib/finch-api/models/request_forwarding_forward_params.rb +1 -1
  36. data/lib/finch-api/models/sandbox/company_update_params.rb +1 -1
  37. data/lib/finch-api/models/sandbox/connection_create_params.rb +1 -1
  38. data/lib/finch-api/models/sandbox/connections/account_create_params.rb +1 -1
  39. data/lib/finch-api/models/sandbox/connections/account_update_params.rb +1 -1
  40. data/lib/finch-api/models/sandbox/directory_create_params.rb +1 -1
  41. data/lib/finch-api/models/sandbox/employment_update_params.rb +1 -1
  42. data/lib/finch-api/models/sandbox/individual_update_params.rb +1 -1
  43. data/lib/finch-api/models/sandbox/job_create_params.rb +1 -1
  44. data/lib/finch-api/models/sandbox/jobs/configuration_retrieve_params.rb +1 -1
  45. data/lib/finch-api/models/sandbox/jobs/configuration_update_params.rb +1 -1
  46. data/lib/finch-api/models/sandbox/payment_create_params.rb +1 -1
  47. data/lib/finch-api/page.rb +4 -4
  48. data/lib/finch-api/request_options.rb +0 -33
  49. data/lib/finch-api/responses_page.rb +3 -3
  50. data/lib/finch-api/single_page.rb +3 -3
  51. data/lib/finch-api/transport/base_client.rb +459 -0
  52. data/lib/finch-api/transport/pooled_net_requester.rb +182 -0
  53. data/lib/finch-api/type/array_of.rb +110 -0
  54. data/lib/finch-api/type/base_model.rb +355 -0
  55. data/lib/finch-api/type/base_page.rb +61 -0
  56. data/lib/finch-api/type/boolean_model.rb +52 -0
  57. data/lib/finch-api/type/converter.rb +211 -0
  58. data/lib/finch-api/type/enum.rb +105 -0
  59. data/lib/finch-api/type/hash_of.rb +136 -0
  60. data/lib/finch-api/type/request_parameters.rb +38 -0
  61. data/lib/finch-api/type/union.rb +204 -0
  62. data/lib/finch-api/type/unknown.rb +56 -0
  63. data/lib/finch-api/type.rb +23 -0
  64. data/lib/finch-api/version.rb +1 -1
  65. data/lib/finch-api.rb +13 -4
  66. data/rbi/lib/finch-api/client.rbi +1 -1
  67. data/rbi/lib/finch-api/individuals_page.rbi +1 -1
  68. data/rbi/lib/finch-api/models/access_token_create_params.rbi +1 -1
  69. data/rbi/lib/finch-api/models/account_disconnect_params.rbi +1 -1
  70. data/rbi/lib/finch-api/models/account_introspect_params.rbi +1 -1
  71. data/rbi/lib/finch-api/models/connect/session_new_params.rbi +1 -1
  72. data/rbi/lib/finch-api/models/connect/session_reauthenticate_params.rbi +1 -1
  73. data/rbi/lib/finch-api/models/hris/benefit_create_params.rbi +1 -1
  74. data/rbi/lib/finch-api/models/hris/benefit_list_params.rbi +1 -1
  75. data/rbi/lib/finch-api/models/hris/benefit_list_supported_benefits_params.rbi +1 -1
  76. data/rbi/lib/finch-api/models/hris/benefit_retrieve_params.rbi +1 -1
  77. data/rbi/lib/finch-api/models/hris/benefit_update_params.rbi +1 -1
  78. data/rbi/lib/finch-api/models/hris/benefits/individual_enroll_many_params.rbi +1 -1
  79. data/rbi/lib/finch-api/models/hris/benefits/individual_enrolled_ids_params.rbi +1 -1
  80. data/rbi/lib/finch-api/models/hris/benefits/individual_retrieve_many_benefits_params.rbi +1 -1
  81. data/rbi/lib/finch-api/models/hris/benefits/individual_unenroll_many_params.rbi +1 -1
  82. data/rbi/lib/finch-api/models/hris/company_retrieve_params.rbi +1 -1
  83. data/rbi/lib/finch-api/models/hris/directory_list_individuals_params.rbi +1 -1
  84. data/rbi/lib/finch-api/models/hris/directory_list_params.rbi +1 -1
  85. data/rbi/lib/finch-api/models/hris/document_list_params.rbi +1 -1
  86. data/rbi/lib/finch-api/models/hris/document_retreive_params.rbi +1 -1
  87. data/rbi/lib/finch-api/models/hris/employment_retrieve_many_params.rbi +1 -1
  88. data/rbi/lib/finch-api/models/hris/individual_retrieve_many_params.rbi +1 -1
  89. data/rbi/lib/finch-api/models/hris/pay_statement_retrieve_many_params.rbi +1 -1
  90. data/rbi/lib/finch-api/models/hris/payment_list_params.rbi +1 -1
  91. data/rbi/lib/finch-api/models/jobs/automated_create_params.rbi +1 -1
  92. data/rbi/lib/finch-api/models/jobs/automated_list_params.rbi +1 -1
  93. data/rbi/lib/finch-api/models/jobs/automated_retrieve_params.rbi +1 -1
  94. data/rbi/lib/finch-api/models/jobs/manual_retrieve_params.rbi +1 -1
  95. data/rbi/lib/finch-api/models/payroll/pay_group_list_params.rbi +1 -1
  96. data/rbi/lib/finch-api/models/payroll/pay_group_retrieve_params.rbi +1 -1
  97. data/rbi/lib/finch-api/models/provider_list_params.rbi +1 -1
  98. data/rbi/lib/finch-api/models/request_forwarding_forward_params.rbi +1 -1
  99. data/rbi/lib/finch-api/models/sandbox/company_update_params.rbi +1 -1
  100. data/rbi/lib/finch-api/models/sandbox/connection_create_params.rbi +1 -1
  101. data/rbi/lib/finch-api/models/sandbox/connections/account_create_params.rbi +1 -1
  102. data/rbi/lib/finch-api/models/sandbox/connections/account_update_params.rbi +1 -1
  103. data/rbi/lib/finch-api/models/sandbox/directory_create_params.rbi +1 -1
  104. data/rbi/lib/finch-api/models/sandbox/directory_create_response.rbi +1 -1
  105. data/rbi/lib/finch-api/models/sandbox/employment_update_params.rbi +1 -1
  106. data/rbi/lib/finch-api/models/sandbox/individual_update_params.rbi +1 -1
  107. data/rbi/lib/finch-api/models/sandbox/job_create_params.rbi +1 -1
  108. data/rbi/lib/finch-api/models/sandbox/jobs/configuration_retrieve_params.rbi +1 -1
  109. data/rbi/lib/finch-api/models/sandbox/jobs/configuration_retrieve_response.rbi +4 -1
  110. data/rbi/lib/finch-api/models/sandbox/jobs/configuration_update_params.rbi +1 -1
  111. data/rbi/lib/finch-api/models/sandbox/payment_create_params.rbi +1 -1
  112. data/rbi/lib/finch-api/page.rbi +1 -1
  113. data/rbi/lib/finch-api/request_options.rbi +0 -15
  114. data/rbi/lib/finch-api/responses_page.rbi +1 -1
  115. data/rbi/lib/finch-api/single_page.rbi +1 -1
  116. data/rbi/lib/finch-api/transport/base_client.rbi +204 -0
  117. data/rbi/lib/finch-api/transport/pooled_net_requester.rbi +64 -0
  118. data/rbi/lib/finch-api/type/array_of.rbi +82 -0
  119. data/rbi/lib/finch-api/type/base_model.rbi +191 -0
  120. data/rbi/lib/finch-api/type/base_page.rbi +38 -0
  121. data/rbi/lib/finch-api/type/boolean_model.rbi +41 -0
  122. data/rbi/lib/finch-api/type/converter.rbi +101 -0
  123. data/rbi/lib/finch-api/type/enum.rbi +58 -0
  124. data/rbi/lib/finch-api/type/hash_of.rbi +86 -0
  125. data/rbi/lib/finch-api/type/request_parameters.rbi +20 -0
  126. data/rbi/lib/finch-api/type/union.rbi +66 -0
  127. data/rbi/lib/finch-api/type/unknown.rbi +37 -0
  128. data/rbi/lib/finch-api/type.rbi +23 -0
  129. data/rbi/lib/finch-api/version.rbi +1 -1
  130. data/sig/finch-api/client.rbs +1 -1
  131. data/sig/finch-api/individuals_page.rbs +1 -1
  132. data/sig/finch-api/models/access_token_create_params.rbs +1 -1
  133. data/sig/finch-api/models/account_disconnect_params.rbs +1 -1
  134. data/sig/finch-api/models/account_introspect_params.rbs +1 -1
  135. data/sig/finch-api/models/connect/session_new_params.rbs +1 -1
  136. data/sig/finch-api/models/connect/session_reauthenticate_params.rbs +1 -1
  137. data/sig/finch-api/models/hris/benefit_create_params.rbs +1 -1
  138. data/sig/finch-api/models/hris/benefit_list_params.rbs +1 -1
  139. data/sig/finch-api/models/hris/benefit_list_supported_benefits_params.rbs +1 -1
  140. data/sig/finch-api/models/hris/benefit_retrieve_params.rbs +1 -1
  141. data/sig/finch-api/models/hris/benefit_update_params.rbs +1 -1
  142. data/sig/finch-api/models/hris/benefits/individual_enroll_many_params.rbs +1 -1
  143. data/sig/finch-api/models/hris/benefits/individual_enrolled_ids_params.rbs +1 -1
  144. data/sig/finch-api/models/hris/benefits/individual_retrieve_many_benefits_params.rbs +1 -1
  145. data/sig/finch-api/models/hris/benefits/individual_unenroll_many_params.rbs +1 -1
  146. data/sig/finch-api/models/hris/company_retrieve_params.rbs +1 -1
  147. data/sig/finch-api/models/hris/directory_list_individuals_params.rbs +1 -1
  148. data/sig/finch-api/models/hris/directory_list_params.rbs +1 -1
  149. data/sig/finch-api/models/hris/document_list_params.rbs +1 -1
  150. data/sig/finch-api/models/hris/document_retreive_params.rbs +1 -1
  151. data/sig/finch-api/models/hris/employment_retrieve_many_params.rbs +1 -1
  152. data/sig/finch-api/models/hris/individual_retrieve_many_params.rbs +1 -1
  153. data/sig/finch-api/models/hris/pay_statement_retrieve_many_params.rbs +1 -1
  154. data/sig/finch-api/models/hris/payment_list_params.rbs +1 -1
  155. data/sig/finch-api/models/jobs/automated_create_params.rbs +1 -1
  156. data/sig/finch-api/models/jobs/automated_list_params.rbs +1 -1
  157. data/sig/finch-api/models/jobs/automated_retrieve_params.rbs +1 -1
  158. data/sig/finch-api/models/jobs/manual_retrieve_params.rbs +1 -1
  159. data/sig/finch-api/models/payroll/pay_group_list_params.rbs +1 -1
  160. data/sig/finch-api/models/payroll/pay_group_retrieve_params.rbs +1 -1
  161. data/sig/finch-api/models/provider_list_params.rbs +1 -1
  162. data/sig/finch-api/models/request_forwarding_forward_params.rbs +1 -1
  163. data/sig/finch-api/models/sandbox/company_update_params.rbs +1 -1
  164. data/sig/finch-api/models/sandbox/connection_create_params.rbs +1 -1
  165. data/sig/finch-api/models/sandbox/connections/account_create_params.rbs +1 -1
  166. data/sig/finch-api/models/sandbox/connections/account_update_params.rbs +1 -1
  167. data/sig/finch-api/models/sandbox/directory_create_params.rbs +1 -1
  168. data/sig/finch-api/models/sandbox/employment_update_params.rbs +1 -1
  169. data/sig/finch-api/models/sandbox/individual_update_params.rbs +1 -1
  170. data/sig/finch-api/models/sandbox/job_create_params.rbs +1 -1
  171. data/sig/finch-api/models/sandbox/jobs/configuration_retrieve_params.rbs +1 -1
  172. data/sig/finch-api/models/sandbox/jobs/configuration_update_params.rbs +1 -1
  173. data/sig/finch-api/models/sandbox/payment_create_params.rbs +1 -1
  174. data/sig/finch-api/page.rbs +1 -1
  175. data/sig/finch-api/request_options.rbs +0 -10
  176. data/sig/finch-api/responses_page.rbs +1 -1
  177. data/sig/finch-api/single_page.rbs +1 -1
  178. data/sig/finch-api/transport/base_client.rbs +110 -0
  179. data/sig/finch-api/transport/pooled_net_requester.rbs +39 -0
  180. data/sig/finch-api/type/array_of.rbs +36 -0
  181. data/sig/finch-api/type/base_model.rbs +73 -0
  182. data/sig/finch-api/type/base_page.rbs +22 -0
  183. data/sig/finch-api/type/boolean_model.rbs +18 -0
  184. data/sig/finch-api/type/converter.rbs +36 -0
  185. data/sig/finch-api/type/enum.rbs +22 -0
  186. data/sig/finch-api/type/hash_of.rbs +36 -0
  187. data/sig/finch-api/type/request_parameters.rbs +13 -0
  188. data/sig/finch-api/type/union.rbs +37 -0
  189. data/sig/finch-api/type/unknown.rbs +18 -0
  190. data/sig/finch-api/type.rbs +22 -0
  191. data/sig/finch-api/version.rbs +1 -1
  192. metadata +40 -13
  193. data/lib/finch-api/base_client.rb +0 -457
  194. data/lib/finch-api/base_model.rb +0 -1174
  195. data/lib/finch-api/base_page.rb +0 -59
  196. data/lib/finch-api/pooled_net_requester.rb +0 -180
  197. data/rbi/lib/finch-api/base_client.rbi +0 -196
  198. data/rbi/lib/finch-api/base_model.rbi +0 -609
  199. data/rbi/lib/finch-api/base_page.rbi +0 -36
  200. data/rbi/lib/finch-api/pooled_net_requester.rbi +0 -59
  201. data/sig/finch-api/base_client.rbs +0 -106
  202. data/sig/finch-api/base_model.rbs +0 -248
  203. data/sig/finch-api/base_page.rbs +0 -20
  204. data/sig/finch-api/pooled_net_requester.rbs +0 -37
@@ -0,0 +1,459 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FinchAPI
4
+ module Transport
5
+ # @api private
6
+ #
7
+ # @abstract
8
+ class BaseClient
9
+ # from whatwg fetch spec
10
+ MAX_REDIRECTS = 20
11
+
12
+ # rubocop:disable Style/MutableConstant
13
+ PLATFORM_HEADERS =
14
+ {
15
+ "x-stainless-arch" => FinchAPI::Util.arch,
16
+ "x-stainless-lang" => "ruby",
17
+ "x-stainless-os" => FinchAPI::Util.os,
18
+ "x-stainless-package-version" => FinchAPI::VERSION,
19
+ "x-stainless-runtime" => ::RUBY_ENGINE,
20
+ "x-stainless-runtime-version" => ::RUBY_ENGINE_VERSION
21
+ }
22
+ # rubocop:enable Style/MutableConstant
23
+
24
+ class << self
25
+ # @api private
26
+ #
27
+ # @param req [Hash{Symbol=>Object}]
28
+ #
29
+ # @raise [ArgumentError]
30
+ def validate!(req)
31
+ keys = [:method, :path, :query, :headers, :body, :unwrap, :page, :stream, :model, :options]
32
+ case req
33
+ in Hash
34
+ req.each_key do |k|
35
+ unless keys.include?(k)
36
+ raise ArgumentError.new("Request `req` keys must be one of #{keys}, got #{k.inspect}")
37
+ end
38
+ end
39
+ else
40
+ raise ArgumentError.new("Request `req` must be a Hash or RequestOptions, got #{req.inspect}")
41
+ end
42
+ end
43
+
44
+ # @api private
45
+ #
46
+ # @param status [Integer]
47
+ # @param headers [Hash{String=>String}, Net::HTTPHeader]
48
+ #
49
+ # @return [Boolean]
50
+ def should_retry?(status, headers:)
51
+ coerced = FinchAPI::Util.coerce_boolean(headers["x-should-retry"])
52
+ case [coerced, status]
53
+ in [true | false, _]
54
+ coerced
55
+ in [_, 408 | 409 | 429 | (500..)]
56
+ # retry on:
57
+ # 408: timeouts
58
+ # 409: locks
59
+ # 429: rate limits
60
+ # 500+: unknown errors
61
+ true
62
+ else
63
+ false
64
+ end
65
+ end
66
+
67
+ # @api private
68
+ #
69
+ # @param request [Hash{Symbol=>Object}] .
70
+ #
71
+ # @option request [Symbol] :method
72
+ #
73
+ # @option request [URI::Generic] :url
74
+ #
75
+ # @option request [Hash{String=>String}] :headers
76
+ #
77
+ # @option request [Object] :body
78
+ #
79
+ # @option request [Integer] :max_retries
80
+ #
81
+ # @option request [Float] :timeout
82
+ #
83
+ # @param status [Integer]
84
+ #
85
+ # @param response_headers [Hash{String=>String}, Net::HTTPHeader]
86
+ #
87
+ # @return [Hash{Symbol=>Object}]
88
+ def follow_redirect(request, status:, response_headers:)
89
+ method, url, headers = request.fetch_values(:method, :url, :headers)
90
+ location =
91
+ Kernel.then do
92
+ URI.join(url, response_headers["location"])
93
+ rescue ArgumentError
94
+ message = "Server responded with status #{status} but no valid location header."
95
+ raise FinchAPI::APIConnectionError.new(url: url, message: message)
96
+ end
97
+
98
+ request = {**request, url: location}
99
+
100
+ case [url.scheme, location.scheme]
101
+ in ["https", "http"]
102
+ message = "Tried to redirect to a insecure URL"
103
+ raise FinchAPI::APIConnectionError.new(url: url, message: message)
104
+ else
105
+ nil
106
+ end
107
+
108
+ # from whatwg fetch spec
109
+ case [status, method]
110
+ in [301 | 302, :post] | [303, _]
111
+ drop = %w[content-encoding content-language content-length content-location content-type]
112
+ request = {
113
+ **request,
114
+ method: method == :head ? :head : :get,
115
+ headers: headers.except(*drop),
116
+ body: nil
117
+ }
118
+ else
119
+ end
120
+
121
+ # from undici
122
+ if FinchAPI::Util.uri_origin(url) != FinchAPI::Util.uri_origin(location)
123
+ drop = %w[authorization cookie host proxy-authorization]
124
+ request = {**request, headers: request.fetch(:headers).except(*drop)}
125
+ end
126
+
127
+ request
128
+ end
129
+
130
+ # @api private
131
+ #
132
+ # @param status [Integer, FinchAPI::APIConnectionError]
133
+ # @param stream [Enumerable, nil]
134
+ def reap_connection!(status, stream:)
135
+ case status
136
+ in (..199) | (300..499)
137
+ stream&.each { next }
138
+ in FinchAPI::APIConnectionError | (500..)
139
+ FinchAPI::Util.close_fused!(stream)
140
+ else
141
+ end
142
+ end
143
+ end
144
+
145
+ # @api private
146
+ # @return [FinchAPI::Transport::PooledNetRequester]
147
+ attr_accessor :requester
148
+
149
+ # @api private
150
+ #
151
+ # @param base_url [String]
152
+ # @param timeout [Float]
153
+ # @param max_retries [Integer]
154
+ # @param initial_retry_delay [Float]
155
+ # @param max_retry_delay [Float]
156
+ # @param headers [Hash{String=>String, Integer, Array<String, Integer, nil>, nil}]
157
+ # @param idempotency_header [String, nil]
158
+ def initialize(
159
+ base_url:,
160
+ timeout: 0.0,
161
+ max_retries: 0,
162
+ initial_retry_delay: 0.0,
163
+ max_retry_delay: 0.0,
164
+ headers: {},
165
+ idempotency_header: nil
166
+ )
167
+ @requester = FinchAPI::Transport::PooledNetRequester.new
168
+ @headers = FinchAPI::Util.normalized_headers(
169
+ self.class::PLATFORM_HEADERS,
170
+ {
171
+ "accept" => "application/json",
172
+ "content-type" => "application/json"
173
+ },
174
+ headers
175
+ )
176
+ @base_url = FinchAPI::Util.parse_uri(base_url)
177
+ @idempotency_header = idempotency_header&.to_s&.downcase
178
+ @max_retries = max_retries
179
+ @timeout = timeout
180
+ @initial_retry_delay = initial_retry_delay
181
+ @max_retry_delay = max_retry_delay
182
+ end
183
+
184
+ # @api private
185
+ #
186
+ # @return [Hash{String=>String}]
187
+ private def auth_headers = {}
188
+
189
+ # @api private
190
+ #
191
+ # @return [String]
192
+ private def generate_idempotency_key = "stainless-ruby-retry-#{SecureRandom.uuid}"
193
+
194
+ # @api private
195
+ #
196
+ # @param req [Hash{Symbol=>Object}] .
197
+ #
198
+ # @option req [Symbol] :method
199
+ #
200
+ # @option req [String, Array<String>] :path
201
+ #
202
+ # @option req [Hash{String=>Array<String>, String, nil}, nil] :query
203
+ #
204
+ # @option req [Hash{String=>String, Integer, Array<String, Integer, nil>, nil}, nil] :headers
205
+ #
206
+ # @option req [Object, nil] :body
207
+ #
208
+ # @option req [Symbol, nil] :unwrap
209
+ #
210
+ # @option req [Class, nil] :page
211
+ #
212
+ # @option req [Class, nil] :stream
213
+ #
214
+ # @option req [FinchAPI::Type::Converter, Class, nil] :model
215
+ #
216
+ # @param opts [Hash{Symbol=>Object}] .
217
+ #
218
+ # @option opts [String, nil] :idempotency_key
219
+ #
220
+ # @option opts [Hash{String=>Array<String>, String, nil}, nil] :extra_query
221
+ #
222
+ # @option opts [Hash{String=>String, nil}, nil] :extra_headers
223
+ #
224
+ # @option opts [Object, nil] :extra_body
225
+ #
226
+ # @option opts [Integer, nil] :max_retries
227
+ #
228
+ # @option opts [Float, nil] :timeout
229
+ #
230
+ # @return [Hash{Symbol=>Object}]
231
+ private def build_request(req, opts)
232
+ method, uninterpolated_path = req.fetch_values(:method, :path)
233
+
234
+ path = FinchAPI::Util.interpolate_path(uninterpolated_path)
235
+
236
+ query = FinchAPI::Util.deep_merge(req[:query].to_h, opts[:extra_query].to_h)
237
+
238
+ headers = FinchAPI::Util.normalized_headers(
239
+ @headers,
240
+ auth_headers,
241
+ req[:headers].to_h,
242
+ opts[:extra_headers].to_h
243
+ )
244
+
245
+ if @idempotency_header &&
246
+ !headers.key?(@idempotency_header) &&
247
+ !Net::HTTP::IDEMPOTENT_METHODS_.include?(method.to_s.upcase)
248
+ headers[@idempotency_header] = opts.fetch(:idempotency_key) { generate_idempotency_key }
249
+ end
250
+
251
+ unless headers.key?("x-stainless-retry-count")
252
+ headers["x-stainless-retry-count"] = "0"
253
+ end
254
+
255
+ timeout = opts.fetch(:timeout, @timeout).to_f.clamp((0..))
256
+ unless headers.key?("x-stainless-timeout") || timeout.zero?
257
+ headers["x-stainless-timeout"] = timeout.to_s
258
+ end
259
+
260
+ headers.reject! { |_, v| v.to_s.empty? }
261
+
262
+ body =
263
+ case method
264
+ in :get | :head | :options | :trace
265
+ nil
266
+ else
267
+ FinchAPI::Util.deep_merge(*[req[:body], opts[:extra_body]].compact)
268
+ end
269
+
270
+ headers, encoded = FinchAPI::Util.encode_content(headers, body)
271
+ {
272
+ method: method,
273
+ url: FinchAPI::Util.join_parsed_uri(@base_url, {**req, path: path, query: query}),
274
+ headers: headers,
275
+ body: encoded,
276
+ max_retries: opts.fetch(:max_retries, @max_retries),
277
+ timeout: timeout
278
+ }
279
+ end
280
+
281
+ # @api private
282
+ #
283
+ # @param headers [Hash{String=>String}]
284
+ # @param retry_count [Integer]
285
+ #
286
+ # @return [Float]
287
+ private def retry_delay(headers, retry_count:)
288
+ # Non-standard extension
289
+ span = Float(headers["retry-after-ms"], exception: false)&.then { _1 / 1000 }
290
+ return span if span
291
+
292
+ retry_header = headers["retry-after"]
293
+ return span if (span = Float(retry_header, exception: false))
294
+
295
+ span = retry_header&.then do
296
+ Time.httpdate(_1) - Time.now
297
+ rescue ArgumentError
298
+ nil
299
+ end
300
+ return span if span
301
+
302
+ scale = retry_count**2
303
+ jitter = 1 - (0.25 * rand)
304
+ (@initial_retry_delay * scale * jitter).clamp(0, @max_retry_delay)
305
+ end
306
+
307
+ # @api private
308
+ #
309
+ # @param request [Hash{Symbol=>Object}] .
310
+ #
311
+ # @option request [Symbol] :method
312
+ #
313
+ # @option request [URI::Generic] :url
314
+ #
315
+ # @option request [Hash{String=>String}] :headers
316
+ #
317
+ # @option request [Object] :body
318
+ #
319
+ # @option request [Integer] :max_retries
320
+ #
321
+ # @option request [Float] :timeout
322
+ #
323
+ # @param redirect_count [Integer]
324
+ #
325
+ # @param retry_count [Integer]
326
+ #
327
+ # @param send_retry_header [Boolean]
328
+ #
329
+ # @raise [FinchAPI::APIError]
330
+ # @return [Array(Integer, Net::HTTPResponse, Enumerable)]
331
+ private def send_request(request, redirect_count:, retry_count:, send_retry_header:)
332
+ url, headers, max_retries, timeout = request.fetch_values(:url, :headers, :max_retries, :timeout)
333
+ input = {**request.except(:timeout), deadline: FinchAPI::Util.monotonic_secs + timeout}
334
+
335
+ if send_retry_header
336
+ headers["x-stainless-retry-count"] = retry_count.to_s
337
+ end
338
+
339
+ begin
340
+ status, response, stream = @requester.execute(input)
341
+ rescue FinchAPI::APIConnectionError => e
342
+ status = e
343
+ end
344
+
345
+ case status
346
+ in ..299
347
+ [status, response, stream]
348
+ in 300..399 if redirect_count >= self.class::MAX_REDIRECTS
349
+ self.class.reap_connection!(status, stream: stream)
350
+
351
+ message = "Failed to complete the request within #{self.class::MAX_REDIRECTS} redirects."
352
+ raise FinchAPI::APIConnectionError.new(url: url, message: message)
353
+ in 300..399
354
+ self.class.reap_connection!(status, stream: stream)
355
+
356
+ request = self.class.follow_redirect(request, status: status, response_headers: response)
357
+ send_request(
358
+ request,
359
+ redirect_count: redirect_count + 1,
360
+ retry_count: retry_count,
361
+ send_retry_header: send_retry_header
362
+ )
363
+ in FinchAPI::APIConnectionError if retry_count >= max_retries
364
+ raise status
365
+ in (400..) if retry_count >= max_retries || !self.class.should_retry?(status, headers: response)
366
+ decoded = Kernel.then do
367
+ FinchAPI::Util.decode_content(response, stream: stream, suppress_error: true)
368
+ ensure
369
+ self.class.reap_connection!(status, stream: stream)
370
+ end
371
+
372
+ raise FinchAPI::APIStatusError.for(
373
+ url: url,
374
+ status: status,
375
+ body: decoded,
376
+ request: nil,
377
+ response: response
378
+ )
379
+ in (400..) | FinchAPI::APIConnectionError
380
+ self.class.reap_connection!(status, stream: stream)
381
+
382
+ delay = retry_delay(response, retry_count: retry_count)
383
+ sleep(delay)
384
+
385
+ send_request(
386
+ request,
387
+ redirect_count: redirect_count,
388
+ retry_count: retry_count + 1,
389
+ send_retry_header: send_retry_header
390
+ )
391
+ end
392
+ end
393
+
394
+ # Execute the request specified by `req`. This is the method that all resource
395
+ # methods call into.
396
+ #
397
+ # @param req [Hash{Symbol=>Object}] .
398
+ #
399
+ # @option req [Symbol] :method
400
+ #
401
+ # @option req [String, Array<String>] :path
402
+ #
403
+ # @option req [Hash{String=>Array<String>, String, nil}, nil] :query
404
+ #
405
+ # @option req [Hash{String=>String, Integer, Array<String, Integer, nil>, nil}, nil] :headers
406
+ #
407
+ # @option req [Object, nil] :body
408
+ #
409
+ # @option req [Symbol, nil] :unwrap
410
+ #
411
+ # @option req [Class, nil] :page
412
+ #
413
+ # @option req [Class, nil] :stream
414
+ #
415
+ # @option req [FinchAPI::Type::Converter, Class, nil] :model
416
+ #
417
+ # @option req [FinchAPI::RequestOptions, Hash{Symbol=>Object}, nil] :options
418
+ #
419
+ # @raise [FinchAPI::APIError]
420
+ # @return [Object]
421
+ def request(req)
422
+ self.class.validate!(req)
423
+ model = req.fetch(:model) { FinchAPI::Unknown }
424
+ opts = req[:options].to_h
425
+ FinchAPI::RequestOptions.validate!(opts)
426
+ request = build_request(req.except(:options), opts)
427
+ url = request.fetch(:url)
428
+
429
+ # Don't send the current retry count in the headers if the caller modified the header defaults.
430
+ send_retry_header = request.fetch(:headers)["x-stainless-retry-count"] == "0"
431
+ status, response, stream = send_request(
432
+ request,
433
+ redirect_count: 0,
434
+ retry_count: 0,
435
+ send_retry_header: send_retry_header
436
+ )
437
+
438
+ decoded = FinchAPI::Util.decode_content(response, stream: stream)
439
+ case req
440
+ in { stream: Class => st }
441
+ st.new(model: model, url: url, status: status, response: response, stream: decoded)
442
+ in { page: Class => page }
443
+ page.new(client: self, req: req, headers: response, page_data: decoded)
444
+ else
445
+ unwrapped = FinchAPI::Util.dig(decoded, req[:unwrap])
446
+ FinchAPI::Type::Converter.coerce(model, unwrapped)
447
+ end
448
+ end
449
+
450
+ # @return [String]
451
+ def inspect
452
+ # rubocop:disable Layout/LineLength
453
+ base_url = FinchAPI::Util.unparse_uri(@base_url)
454
+ "#<#{self.class.name}:0x#{object_id.to_s(16)} base_url=#{base_url} max_retries=#{@max_retries} timeout=#{@timeout}>"
455
+ # rubocop:enable Layout/LineLength
456
+ end
457
+ end
458
+ end
459
+ end
@@ -0,0 +1,182 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FinchAPI
4
+ module Transport
5
+ # @api private
6
+ class PooledNetRequester
7
+ # from the golang stdlib
8
+ # https://github.com/golang/go/blob/c8eced8580028328fde7c03cbfcb720ce15b2358/src/net/http/transport.go#L49
9
+ KEEP_ALIVE_TIMEOUT = 30
10
+
11
+ class << self
12
+ # @api private
13
+ #
14
+ # @param url [URI::Generic]
15
+ #
16
+ # @return [Net::HTTP]
17
+ def connect(url)
18
+ port =
19
+ case [url.port, url.scheme]
20
+ in [Integer, _]
21
+ url.port
22
+ in [nil, "http" | "ws"]
23
+ Net::HTTP.http_default_port
24
+ in [nil, "https" | "wss"]
25
+ Net::HTTP.https_default_port
26
+ end
27
+
28
+ Net::HTTP.new(url.host, port).tap do
29
+ _1.use_ssl = %w[https wss].include?(url.scheme)
30
+ _1.max_retries = 0
31
+ end
32
+ end
33
+
34
+ # @api private
35
+ #
36
+ # @param conn [Net::HTTP]
37
+ # @param deadline [Float]
38
+ def calibrate_socket_timeout(conn, deadline)
39
+ timeout = deadline - FinchAPI::Util.monotonic_secs
40
+ conn.open_timeout = conn.read_timeout = conn.write_timeout = conn.continue_timeout = timeout
41
+ end
42
+
43
+ # @api private
44
+ #
45
+ # @param request [Hash{Symbol=>Object}] .
46
+ #
47
+ # @option request [Symbol] :method
48
+ #
49
+ # @option request [URI::Generic] :url
50
+ #
51
+ # @option request [Hash{String=>String}] :headers
52
+ #
53
+ # @param blk [Proc]
54
+ #
55
+ # @yieldparam [String]
56
+ # @return [Net::HTTPGenericRequest]
57
+ def build_request(request, &)
58
+ method, url, headers, body = request.fetch_values(:method, :url, :headers, :body)
59
+ req = Net::HTTPGenericRequest.new(
60
+ method.to_s.upcase,
61
+ !body.nil?,
62
+ method != :head,
63
+ url.to_s
64
+ )
65
+
66
+ headers.each { req[_1] = _2 }
67
+
68
+ case body
69
+ in nil
70
+ nil
71
+ in String
72
+ req["content-length"] ||= body.bytesize.to_s unless req["transfer-encoding"]
73
+ req.body_stream = FinchAPI::Util::ReadIOAdapter.new(body, &)
74
+ in StringIO
75
+ req["content-length"] ||= body.size.to_s unless req["transfer-encoding"]
76
+ req.body_stream = FinchAPI::Util::ReadIOAdapter.new(body, &)
77
+ in IO | Enumerator
78
+ req["transfer-encoding"] ||= "chunked" unless req["content-length"]
79
+ req.body_stream = FinchAPI::Util::ReadIOAdapter.new(body, &)
80
+ end
81
+
82
+ req
83
+ end
84
+ end
85
+
86
+ # @api private
87
+ #
88
+ # @param url [URI::Generic]
89
+ # @param deadline [Float]
90
+ # @param blk [Proc]
91
+ #
92
+ # @raise [Timeout::Error]
93
+ # @yieldparam [Net::HTTP]
94
+ private def with_pool(url, deadline:, &blk)
95
+ origin = FinchAPI::Util.uri_origin(url)
96
+ timeout = deadline - FinchAPI::Util.monotonic_secs
97
+ pool =
98
+ @mutex.synchronize do
99
+ @pools[origin] ||= ConnectionPool.new(size: @size) do
100
+ self.class.connect(url)
101
+ end
102
+ end
103
+
104
+ pool.with(timeout: timeout, &blk)
105
+ end
106
+
107
+ # @api private
108
+ #
109
+ # @param request [Hash{Symbol=>Object}] .
110
+ #
111
+ # @option request [Symbol] :method
112
+ #
113
+ # @option request [URI::Generic] :url
114
+ #
115
+ # @option request [Hash{String=>String}] :headers
116
+ #
117
+ # @option request [Object] :body
118
+ #
119
+ # @option request [Float] :deadline
120
+ #
121
+ # @return [Array(Integer, Net::HTTPResponse, Enumerable)]
122
+ def execute(request)
123
+ url, deadline = request.fetch_values(:url, :deadline)
124
+
125
+ eof = false
126
+ finished = false
127
+ enum = Enumerator.new do |y|
128
+ with_pool(url, deadline: deadline) do |conn|
129
+ next if finished
130
+
131
+ req = self.class.build_request(request) do
132
+ self.class.calibrate_socket_timeout(conn, deadline)
133
+ end
134
+
135
+ self.class.calibrate_socket_timeout(conn, deadline)
136
+ unless conn.started?
137
+ conn.keep_alive_timeout = self.class::KEEP_ALIVE_TIMEOUT
138
+ conn.start
139
+ end
140
+
141
+ self.class.calibrate_socket_timeout(conn, deadline)
142
+ conn.request(req) do |rsp|
143
+ y << [conn, req, rsp]
144
+ break if finished
145
+
146
+ rsp.read_body do |bytes|
147
+ y << bytes
148
+ break if finished
149
+
150
+ self.class.calibrate_socket_timeout(conn, deadline)
151
+ end
152
+ eof = true
153
+ end
154
+ end
155
+ rescue Timeout::Error
156
+ raise FinchAPI::APITimeoutError
157
+ end
158
+
159
+ conn, _, response = enum.next
160
+ body = FinchAPI::Util.fused_enum(enum, external: true) do
161
+ finished = true
162
+ tap do
163
+ enum.next
164
+ rescue StopIteration
165
+ nil
166
+ end
167
+ conn.finish if !eof && conn&.started?
168
+ end
169
+ [Integer(response.code), response, (response.body = body)]
170
+ end
171
+
172
+ # @api private
173
+ #
174
+ # @param size [Integer]
175
+ def initialize(size: Etc.nprocessors)
176
+ @mutex = Mutex.new
177
+ @size = size
178
+ @pools = {}
179
+ end
180
+ end
181
+ end
182
+ end