lib/seam/request.rb in seam-2.0.0a2 vs lib/seam/request.rb in seam-2.0.0b0

- old
+ new

@@ -1,73 +1,104 @@ # frozen_string_literal: true -require "http" +require "faraday" +require "faraday/retry" module Seam - class Request - attr_reader :base_uri, :api_key, :debug + module Http + module Request + def self.create_faraday_client(endpoint, auth_headers, faraday_options = {}, faraday_retry_options = {}) + default_options = { + url: endpoint, + headers: auth_headers.merge(default_headers) + } - class Error < StandardError - attr_reader :status, :response + options = deep_merge(default_options, faraday_options) - def initialize(message, status, response) - super(message) - @status = status - @response = response + default_faraday_retry_options = { + max: 2, + backoff_factor: 2 + } + + faraday_retry_options = default_faraday_retry_options.merge(faraday_retry_options) + + Faraday.new(options) do |builder| + builder.request :json + builder.response :json + builder.use ResponseMiddleware + builder.request :retry, faraday_retry_options + end end - end - def initialize(api_key:, base_uri:, debug: false) - @api_key = api_key - @base_uri = base_uri - @debug = debug - end + def self.default_headers + { + "User-Agent" => "seam-ruby/#{Seam::VERSION}", + "Content-Type" => "application/json", + :"seam-sdk-name" => "seamapi/ruby", + :"seam-sdk-version" => Seam::VERSION, + :"seam-lts-version" => Seam::LTS_VERSION + } + end - def perform(method, uri, config = {}) - Logger.info("Request: #{method} #{uri} #{config}") if debug + class ResponseMiddleware < Faraday::Response::RaiseError + def on_complete(env) + return if env.success? - config[:body] = config[:body].to_json if config[:body] + status_code = env.status + request_id = env.response_headers["seam-request-id"] - response = HTTP.request( - method, - build_url(uri), - {headers: headers}.merge(config) - ) + raise Http::UnauthorizedError.new(request_id) if status_code == 401 - return response.parse if response.status.success? + if seam_api_error_response?(env) + body = JSON.parse(env.body) + error = body["error"] + error_details = { + type: error["type"] || "unknown_error", + message: error["message"] || "Unknown error", + data: error["data"] + } - handle_error_response(response, method, uri) - end + if error["type"] == "invalid_input" + error_details["validation_errors"] = error["validation_errors"] + raise Http::InvalidInputError.new(error_details, status_code, request_id) + end - protected + raise Http::ApiError.new(error_details, status_code, request_id) + end - def handle_error_response(response, method, uri) - msg = "Api Error #{response.status.code} #{method} #{uri}" - code = response.status.code + super + end - if code >= 400 && code < 500 && (err = response.parse["error"]) - msg = "Api Error #{err["type"]}\nrequest_id: #{err["request_id"]}\n#{err["message"]}" - end + def seam_api_error_response?(env) + return false unless env.response_headers - raise Error.new(msg, code, response) - end + content_type = env.response_headers["Content-Type"] + return false unless content_type&.start_with?("application/json") - def build_url(uri) - "#{base_uri}#{uri}" - end + begin + body = JSON.parse(env.body) + return false unless body.is_a?(Hash) && body["error"].is_a?(Hash) - def headers - { - "User-Agent" => user_agent, - "Content-Type" => "application/json", - "Authorization" => "Bearer #{api_key}", - :"seam-sdk-name" => "seamapi/ruby", - :"seam-sdk-version" => Seam::VERSION, - :"seam-lts-version" => Seam::LTS_VERSION - } - end + error = body["error"] + error["type"].is_a?(String) && error["message"].is_a?(String) + rescue JSON::ParserError + false + end + end + end - def user_agent - "seam-ruby/#{Seam::VERSION}" + def self.deep_merge(hash1, hash2) + result = hash1.dup + hash2.each do |key, value| + result[key] = if value.is_a?(Hash) && result[key].is_a?(Hash) + deep_merge(result[key], value) + else + value + end + end + result + end + + private_class_method :deep_merge end end end