98 lines
2.7 KiB
Ruby
98 lines
2.7 KiB
Ruby
|
class API::Client
|
||
|
include Dry::Monads[:result]
|
||
|
|
||
|
TIMEOUT = 10
|
||
|
|
||
|
def call(url:, params: nil, body: nil, json: nil, headers: nil, method: :get, authorization_token: nil, schema: nil, timeout: TIMEOUT)
|
||
|
response = case method
|
||
|
when :get
|
||
|
Typhoeus.get(url,
|
||
|
headers: headers_with_authorization(headers, authorization_token),
|
||
|
params:,
|
||
|
timeout: TIMEOUT)
|
||
|
when :post
|
||
|
Typhoeus.post(url,
|
||
|
headers: headers_with_authorization(headers, json, authorization_token),
|
||
|
body: json.nil? ? body : json.to_json,
|
||
|
timeout: TIMEOUT)
|
||
|
end
|
||
|
handle_response(response, schema:)
|
||
|
end
|
||
|
|
||
|
private
|
||
|
|
||
|
def headers_with_authorization(headers, json, authorization_token)
|
||
|
headers = headers || {}
|
||
|
headers['authorization'] = "Bearer #{authorization_token}" if authorization_token.present?
|
||
|
headers['content-type'] = 'application/json' if json.present?
|
||
|
headers
|
||
|
end
|
||
|
|
||
|
OK = Data.define(:body, :response)
|
||
|
Error = Data.define(:type, :code, :retryable, :reason)
|
||
|
|
||
|
def handle_response(response, schema:)
|
||
|
if response.success?
|
||
|
scope = Sentry.get_current_scope
|
||
|
if scope.extra.key?(:external_id)
|
||
|
scope.set_extras(raw_body: response.body.to_s)
|
||
|
end
|
||
|
body = parse_body(response.body)
|
||
|
case body
|
||
|
in Success(body)
|
||
|
if !schema || schema.valid?(body)
|
||
|
Success(OK[body.deep_symbolize_keys, response])
|
||
|
else
|
||
|
Failure(Error[:schema, response.code, false, SchemaError.new(schema.validate(body))])
|
||
|
end
|
||
|
in Failure(reason)
|
||
|
Failure(Error[:json, response.code, false, reason])
|
||
|
end
|
||
|
elsif response.timed_out?
|
||
|
Failure(Error[:timeout, response.code, true, HTTPError.new(response)])
|
||
|
elsif response.code != 0
|
||
|
Failure(Error[:http, response.code, true, HTTPError.new(response)])
|
||
|
else
|
||
|
Failure(Error[:network, response.code, true, HTTPError.new(response)])
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def parse_body(body)
|
||
|
Success(JSON.parse(body))
|
||
|
rescue JSON::ParserError => error
|
||
|
Failure(error)
|
||
|
end
|
||
|
|
||
|
class SchemaError < StandardError
|
||
|
attr_reader :errors
|
||
|
|
||
|
def initialize(errors)
|
||
|
@errors = errors.to_a
|
||
|
|
||
|
super(@errors.map(&:to_json).join("\n"))
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class HTTPError < StandardError
|
||
|
attr_reader :response
|
||
|
|
||
|
def initialize(response)
|
||
|
@response = response
|
||
|
|
||
|
uri = URI.parse(response.effective_url)
|
||
|
|
||
|
msg = <<~TEXT
|
||
|
url: #{uri.host}#{uri.path}
|
||
|
HTTP error code: #{response.code}
|
||
|
body: #{CGI.escape(response.body)}
|
||
|
curl message: #{response.return_message}
|
||
|
total time: #{response.total_time}
|
||
|
connect time: #{response.connect_time}
|
||
|
response headers: #{response.headers}
|
||
|
TEXT
|
||
|
|
||
|
super(msg)
|
||
|
end
|
||
|
end
|
||
|
end
|