feat(api): HTTP API client
This commit is contained in:
parent
a9f431caa5
commit
c6d728d44f
3 changed files with 106 additions and 0 deletions
1
Gemfile
1
Gemfile
|
@ -30,6 +30,7 @@ gem 'devise-i18n'
|
|||
gem 'devise-two-factor'
|
||||
gem 'discard'
|
||||
gem 'dotenv-rails', require: 'dotenv/rails-now' # dotenv should always be loaded before rails
|
||||
gem 'dry-monads'
|
||||
gem 'elastic-apm'
|
||||
gem 'flipper'
|
||||
gem 'flipper-active_record'
|
||||
|
|
|
@ -218,7 +218,14 @@ GEM
|
|||
dotenv (= 2.7.6)
|
||||
railties (>= 3.2)
|
||||
dry-cli (1.0.0)
|
||||
dry-core (1.0.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
zeitwerk (~> 2.6)
|
||||
dry-inflector (0.2.0)
|
||||
dry-monads (1.6.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
dry-core (~> 1.0, < 2)
|
||||
zeitwerk (~> 2.6)
|
||||
dumb_delegator (1.0.0)
|
||||
ecma-re-validator (0.3.0)
|
||||
regexp_parser (~> 2.0)
|
||||
|
@ -828,6 +835,7 @@ DEPENDENCIES
|
|||
devise-two-factor
|
||||
discard
|
||||
dotenv-rails
|
||||
dry-monads
|
||||
elastic-apm
|
||||
factory_bot
|
||||
flipper
|
||||
|
|
97
app/lib/api/client.rb
Normal file
97
app/lib/api/client.rb
Normal file
|
@ -0,0 +1,97 @@
|
|||
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
|
Loading…
Reference in a new issue