Add HTTP core: Faraday connection, error mapping, response wrapper, pagination, and OAuth2
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "faraday"
|
||||
require "faraday/retry"
|
||||
require "faraday/multipart"
|
||||
|
||||
module Fiken
|
||||
# Wraps a Faraday connection: bearer auth, JSON encode/decode, retry on
|
||||
# rate-limit/server errors, and mapping of HTTP error statuses to Fiken errors.
|
||||
class Connection
|
||||
DEFAULT_BASE_URL = "https://api.fiken.no/api/v2"
|
||||
|
||||
Result = Struct.new(:status, :body, :headers, keyword_init: true)
|
||||
|
||||
DEFAULT_RETRY = {
|
||||
max: 2,
|
||||
interval: 0.5,
|
||||
backoff_factor: 2,
|
||||
retry_statuses: [429, 500, 502, 503, 504],
|
||||
methods: [] # retry regardless of method; Fiken's 429 means "not processed"
|
||||
}.freeze
|
||||
|
||||
def initialize(token:, base_url: DEFAULT_BASE_URL, user_agent: nil,
|
||||
retry_options: {}, logger: nil, adapter: Faraday.default_adapter)
|
||||
@token = token
|
||||
# Faraday joins relative paths against the base URL, which only behaves
|
||||
# predictably when the base ends in "/" and request paths do not start with one.
|
||||
@base_url = base_url.end_with?("/") ? base_url : "#{base_url}/"
|
||||
@user_agent = user_agent || "fiken-ruby/#{Fiken::VERSION}"
|
||||
@retry_options = DEFAULT_RETRY.merge(retry_options)
|
||||
@logger = logger
|
||||
@adapter = adapter
|
||||
end
|
||||
|
||||
def get(path, params = nil, headers = nil)
|
||||
run(:get, path, params: params, headers: headers)
|
||||
end
|
||||
|
||||
def post(path, body = nil, headers = nil)
|
||||
run(:post, path, body: body, headers: headers)
|
||||
end
|
||||
|
||||
def put(path, body = nil, headers = nil)
|
||||
run(:put, path, body: body, headers: headers)
|
||||
end
|
||||
|
||||
def patch(path, body = nil, headers = nil)
|
||||
run(:patch, path, body: body, headers: headers)
|
||||
end
|
||||
|
||||
def delete(path, params = nil, headers = nil)
|
||||
run(:delete, path, params: params, headers: headers)
|
||||
end
|
||||
|
||||
# Multipart upload (attachments). `parts` is a hash of field name => value,
|
||||
# where a value may be a Faraday::Multipart::FilePart for the file itself.
|
||||
def post_multipart(path, parts)
|
||||
response = multipart_faraday.post(path.sub(%r{\A/}, ""), parts)
|
||||
raise_on_error(response)
|
||||
Result.new(status: response.status, body: response.body, headers: response.headers)
|
||||
rescue Faraday::Error => e
|
||||
raise ConnectionError, e.message
|
||||
end
|
||||
|
||||
def faraday
|
||||
@faraday ||= Faraday.new(url: @base_url) do |f|
|
||||
f.request :authorization, "Bearer", @token
|
||||
f.request :json
|
||||
f.request :retry, @retry_options
|
||||
f.response :json, content_type: /\bjson$/, parser_options: { symbolize_names: false }
|
||||
f.headers["User-Agent"] = @user_agent
|
||||
f.headers["Accept"] = "application/json"
|
||||
f.response :logger, @logger if @logger
|
||||
f.adapter @adapter
|
||||
end
|
||||
end
|
||||
|
||||
def multipart_faraday
|
||||
@multipart_faraday ||= Faraday.new(url: @base_url) do |f|
|
||||
f.request :authorization, "Bearer", @token
|
||||
f.request :multipart
|
||||
f.request :url_encoded
|
||||
f.response :json, content_type: /\bjson$/
|
||||
f.headers["User-Agent"] = @user_agent
|
||||
f.headers["Accept"] = "application/json"
|
||||
f.adapter @adapter
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def run(method, path, params: nil, body: nil, headers: nil)
|
||||
response = faraday.public_send(method, path.sub(%r{\A/}, "")) do |req|
|
||||
req.params.update(params) if params
|
||||
req.body = body unless body.nil?
|
||||
req.headers.update(headers) if headers
|
||||
end
|
||||
raise_on_error(response)
|
||||
Result.new(status: response.status, body: response.body, headers: response.headers)
|
||||
rescue Faraday::Error => e
|
||||
raise ConnectionError, e.message
|
||||
end
|
||||
|
||||
def raise_on_error(response)
|
||||
return if response.status < 400
|
||||
|
||||
raise Error.from_response(response.status, response.body, response)
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user