Class: Inttegro::HTTPClient
- Inherits:
-
Object
- Object
- Inttegro::HTTPClient
- Extended by:
- T::Sig
- Defined in:
- lib/inttegro/http_client.rb
Overview
Minimal HTTP client that wraps Net::HTTP with JSON parsing and API-friendly errors.
Constant Summary collapse
- USER_AGENT =
T.let("inttegro-sdk-ruby/#{Inttegro::VERSION}", String)
- ResponseValue =
T.type_alias do T.any(T::Struct, ResponseObject, Types::Scalar, T::Array[Object]) end
- ErrorPayload =
T.type_alias { T::Hash[Symbol, T.nilable(String)] }
Instance Method Summary collapse
- #get(path, headers: {}, query: nil) ⇒ Object
- #get_binary_public(url) ⇒ Object
-
#initialize(api_key:, base_url: DEFAULT_BASE_URL, read_timeout: 30, open_timeout: 10, adapter: nil, telemetry_enabled: true, tracer_provider: nil, error_reporter: nil, error_reporting_policy: :unexpected) ⇒ HTTPClient
constructor
A new instance of HTTPClient.
- #perform_multipart(path, fields, files, headers, authenticated, model, operation = nil) ⇒ Object
- #post(path, body = nil, headers: {}, query: nil, **body_keywords) ⇒ Object
- #post_binary_json(path, body) ⇒ Object
- #post_model(path, model, body, headers: {}, query: nil) ⇒ Object
- #post_multipart(path, fields: {}, files: {}, headers: {}, authenticated: true) ⇒ Object
- #post_multipart_model(path, model, fields: {}, files: {}, headers: {}, authenticated: true, operation: nil) ⇒ Object
- #post_multipart_resource(path, model, field, fields: {}, files: {}, headers: {}, authenticated: true) ⇒ Object
- #post_object(path, body, headers: {}, query: nil) ⇒ Object
- #post_resource(path, model, field, body, headers: {}, query: nil) ⇒ Object
- #post_with_headers(path, body, headers: {}) ⇒ Object
- #request(method, path, body: nil, headers: {}, query: nil, response_model: nil) ⇒ Object
Constructor Details
#initialize(api_key:, base_url: DEFAULT_BASE_URL, read_timeout: 30, open_timeout: 10, adapter: nil, telemetry_enabled: true, tracer_provider: nil, error_reporter: nil, error_reporting_policy: :unexpected) ⇒ HTTPClient
Returns a new instance of HTTPClient.
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/inttegro/http_client.rb', line 45 def initialize( api_key:, base_url: DEFAULT_BASE_URL, read_timeout: 30, open_timeout: 10, adapter: nil, telemetry_enabled: true, tracer_provider: nil, error_reporter: nil, error_reporting_policy: :unexpected ) @api_key = T.let(api_key || "", String) raise ArgumentError, "api_key is required" if @api_key.strip.empty? normalized_base_url = T.let(base_url || DEFAULT_BASE_URL, String) end_index = T.let(normalized_base_url.length, Integer) end_index -= 1 while end_index.positive? && normalized_base_url.getbyte(end_index - 1) == 47 @base_url = T.let(T.must(normalized_base_url[0, end_index]), String) @read_timeout = T.let(read_timeout, T.nilable(Numeric)) @open_timeout = T.let(open_timeout, T.nilable(Numeric)) @adapter = T.let(adapter, T.nilable(Types::Adapter)) @telemetry = T.let( Telemetry.new( Inttegro::VERSION, enabled: telemetry_enabled, tracer_provider: tracer_provider, error_reporter: error_reporter, error_reporting_policy: error_reporting_policy ), Telemetry ) end |
Instance Method Details
#get(path, headers: {}, query: nil) ⇒ Object
79 80 81 |
# File 'lib/inttegro/http_client.rb', line 79 def get(path, headers: {}, query: nil) request(:get, path, headers: headers, query: query) end |
#get_binary_public(url) ⇒ Object
304 305 306 307 308 309 310 311 312 313 314 315 316 317 |
# File 'lib/inttegro/http_client.rb', line 304 def get_binary_public(url) @telemetry.in_span(url, :get, @base_url, Inttegro::VERSION, "file_links.download") do |span| uri = build_uri(url, nil) request = Net::HTTP::Get.new(uri) request["User-Agent"] = USER_AGENT @telemetry.prepare(span, request) raw_response = @adapter ? @adapter.call(uri, request) : build_http(uri).request(request) response = TransportResponse.new(raw_response) @telemetry.response(span, response, decoded: false) return FileDownload.new(response.body, response.headers) if response.code.to_i < 400 raise_response_error(response) end end |
#perform_multipart(path, fields, files, headers, authenticated, model, operation = nil) ⇒ Object
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 |
# File 'lib/inttegro/http_client.rb', line 251 def perform_multipart(path, fields, files, headers, authenticated, model, operation = nil) @telemetry.in_span(path, :post, @base_url, Inttegro::VERSION, operation) do |span| uri = build_uri(path, nil) request = Net::HTTP::Post.new(uri) request["Accept"] = "application/json" request["User-Agent"] = USER_AGENT request["Authorization"] = "Bearer #{@api_key}" if authenticated headers = headers.dup if authenticated && idempotent_mutation_path?(path) && !header_present?(headers, "Idempotency-Key") headers["Idempotency-Key"] = generate_idempotency_key end headers.each { |key, value| request[key] = value } form = [] fields.each do |key, value| next if value.nil? form << [key.to_s, value.is_a?(Hash) || value.is_a?(Array) ? JSON.generate(value) : value.to_s] end files.each do |key, file_path| raise ArgumentError, "file path for #{key} must be a String" unless file_path.is_a?(String) form << [key.to_s, File.open(file_path)] end request.set_form(form, "multipart/form-data") @telemetry.prepare(span, request) raw_response = @adapter ? @adapter.call(uri, request) : build_http(uri).request(request) response = TransportResponse.new(raw_response) @telemetry.response(span, response, decoded: false) result = handle_response(path, response, model) @telemetry.decoded(span) result end end |
#post(path, body = nil, headers: {}, query: nil, **body_keywords) ⇒ Object
92 93 94 95 |
# File 'lib/inttegro/http_client.rb', line 92 def post(path, body = nil, headers: {}, query: nil, **body_keywords) body = body_keywords unless body_keywords.empty? request(:post, path, body: body, headers: headers, query: query) end |
#post_binary_json(path, body) ⇒ Object
288 289 290 291 292 293 294 295 296 297 298 299 300 301 |
# File 'lib/inttegro/http_client.rb', line 288 def post_binary_json(path, body) @telemetry.in_span(path, :post, @base_url, Inttegro::VERSION) do |span| payload = (:post, path, coerce_body(body), {}) response = raw_request(:post, path, JSON.dump(payload), { "Accept" => "application/octet-stream", "Content-Type" => "application/json", "Authorization" => "Bearer #{@api_key}" }, span) @telemetry.response(span, response, decoded: false) return FileDownload.new(response.body, response.headers) if response.code.to_i < 400 raise_response_error(response) end end |
#post_model(path, model, body, headers: {}, query: nil) ⇒ Object
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/inttegro/http_client.rb', line 108 def post_model(path, model, body, headers: {}, query: nil) unless model <= T::Struct raise TypeError, "response model for #{path} must inherit from T::Struct" end response = request( :post, path, body: body, headers: headers, query: query, response_model: model ) return T.cast(response, T.type_parameter(:Model)) if response.is_a?(model) raise TypeError, "expected #{model} from #{path}, got #{response.class}" end |
#post_multipart(path, fields: {}, files: {}, headers: {}, authenticated: true) ⇒ Object
178 179 180 181 182 183 |
# File 'lib/inttegro/http_client.rb', line 178 def post_multipart(path, fields: {}, files: {}, headers: {}, authenticated: true) expect_response_object( perform_multipart(path, fields, files, headers, authenticated, nil), path ) end |
#post_multipart_model(path, model, fields: {}, files: {}, headers: {}, authenticated: true, operation: nil) ⇒ Object
198 199 200 201 202 203 204 205 206 207 |
# File 'lib/inttegro/http_client.rb', line 198 def post_multipart_model(path, model, fields: {}, files: {}, headers: {}, authenticated: true, operation: nil) unless model <= T::Struct raise TypeError, "response model for #{path} must inherit from T::Struct" end response = perform_multipart(path, fields, files, headers, authenticated, model, operation) return T.cast(response, T.type_parameter(:Model)) if response.is_a?(model) raise TypeError, "expected #{model} from #{path}, got #{response.class}" end |
#post_multipart_resource(path, model, field, fields: {}, files: {}, headers: {}, authenticated: true) ⇒ Object
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/inttegro/http_client.rb', line 222 def post_multipart_resource(path, model, field, fields: {}, files: {}, headers: {}, authenticated: true) envelope = expect_response_object(perform_multipart( path, fields, files, headers, authenticated, nil ), path) value = envelope.public_send(field) return value if value.is_a?(model) decoded = Inttegro.deserialize(value, T.cast(model, T::Class[T::Struct])) return decoded if decoded.is_a?(model) raise TypeError, "expected #{model} in #{field} from #{path}, got #{decoded.class}" end |
#post_object(path, body, headers: {}, query: nil) ⇒ Object
158 159 160 |
# File 'lib/inttegro/http_client.rb', line 158 def post_object(path, body, headers: {}, query: nil) expect_response_object(request(:post, path, body: body, headers: headers, query: query), path) end |
#post_resource(path, model, field, body, headers: {}, query: nil) ⇒ Object
138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/inttegro/http_client.rb', line 138 def post_resource(path, model, field, body, headers: {}, query: nil) envelope = post_object(path, body, headers: headers, query: query) value = envelope.public_send(field) value = value.to_h if value.is_a?(ResponseObject) return value if value.is_a?(model) decoded = Inttegro.deserialize(value, T.cast(model, T::Class[T::Struct])) return decoded if decoded.is_a?(model) raise TypeError, "expected #{model} in #{field} from #{path}, got #{decoded.class}" end |
#post_with_headers(path, body, headers: {}) ⇒ Object
165 166 167 |
# File 'lib/inttegro/http_client.rb', line 165 def post_with_headers(path, body, headers: {}) post_object(path, body, headers: headers) end |
#request(method, path, body: nil, headers: {}, query: nil, response_model: nil) ⇒ Object
329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 |
# File 'lib/inttegro/http_client.rb', line 329 def request(method, path, body: nil, headers: {}, query: nil, response_model: nil) @telemetry.in_span(path, method, @base_url, Inttegro::VERSION) do |span| uri = build_uri(path, query) body = validate_body(path, coerce_body(body)) body = (method, path, body, headers) request = build_request(method, uri, body, headers) @telemetry.prepare(span, request) raw_response = if @adapter @adapter.call(uri, request) else http = build_http(uri) http.request(request) end response = TransportResponse.new(raw_response) @telemetry.response(span, response, decoded: false) result = handle_response(path, response, response_model) @telemetry.decoded(span) result end rescue Timeout::Error, Errno::ETIMEDOUT => e raise TimeoutError.new("Request timed out", e) rescue SocketError, Errno::ECONNREFUSED, Errno::ECONNRESET => e raise NetworkError.new("Network request failed", e) end |