Files

111 lines
3.7 KiB
Ruby

# 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