sul-dlss/preservation-client

View on GitHub
lib/preservation/client/versioned_api_service.rb

Summary

Maintainability
A
0 mins
Test Coverage
A
96%
# frozen_string_literal: true

module Preservation
  class Client
    # @abstract API calls to a versioned endpoint
    class VersionedApiService
      def initialize(connection:, api_version: DEFAULT_API_VERSION)
        @connection = connection
        @api_version = api_version
      end

      private

      attr_reader :connection, :api_version

      # @param path [String] path to be appended to connection url (no leading slash)
      def get_json(path, object_id)
        req_url = "#{api_version}/#{path}"
        resp = connection.get do |req|
          req.url req_url
          req.headers['Content-Type'] = 'application/json'
          req.headers['Accept'] = 'application/json'
        end
        return with_indifferent_access_for(JSON.parse(resp.body)) if resp.success?

        errmsg = ResponseErrorFormatter
                 .format(response: resp, object_id: object_id, client_method_name: caller_locations.first.label)
        raise UnexpectedResponseError, errmsg
      rescue Faraday::ResourceNotFound
        errmsg = "#{object_id} not found in Preservation at #{connection.url_prefix}#{req_url}"
        raise NotFoundError, errmsg
      rescue Faraday::Error => e
        errmsg = "Preservation::Client.#{caller_locations.first.label} for #{object_id} " \
                 "got #{e.response[:status]} from Preservation at #{req_url}: #{e.response[:body]}"
        raise UnexpectedResponseError, errmsg
      end

      # @param path [String] path to be appended to connection url (no leading slash)
      # @param params [Hash] optional request parameters
      # @param on_data [Proc] a callback to use when a streaming response is desired.
      def get(path, params, on_data:)
        return http_response(:get, path, params) unless on_data

        req_url = "#{api_version}/#{path}"
        connection.get("#{api_version}/#{path}", params) do |req|
          req.options.on_data = proc do |chunk, size, env|
            if env.status >= 300
              errmsg = "Preservation::Client.#{caller_locations.first.label} " \
                       "got #{env.status} from Preservation at #{req_url}"
              raise http_exception_class(env.status), errmsg
            end
            on_data.call(chunk, size, env)
          end
        end
      end

      # @param path [String] path to be appended to connection url (no leading slash)
      # @param params [Hash] optional request parameters
      def post(path, params)
        http_response(:post, path, params)
      end

      # @param path [String] path to be appended to connection url (no leading slash)
      # @param params [Hash] optional request parameters
      def patch(path, params)
        http_response(:patch, path, params)
      end

      # @param path [String] path to be appended to connection url (no leading slash)
      # @param params [Hash] optional request parameters
      def put(path, params)
        http_response(:put, path, params)
      end

      # @param path [String] path to be appended to connection url (no leading slash)
      # @param params [Hash] optional request parameters
      def delete(path, params)
        http_response(:delete, path, params)
      end

      # @param method [Symbol] a symbol representing an HTTP method: :get, :post, :patch, :put, :delete
      # @param path [String] path to be appended to connection url (no leading slash)
      # @param params [Hash] optional request parameters
      def http_response(method, path, params)
        req_url = "#{api_version}/#{path}"
        resp =
          case method
          when :delete, :get
            connection.public_send(method, req_url, params)
          when :patch, :post, :put
            request_json = params.to_json if params&.any?
            connection.public_send(method, req_url, request_json, 'Content-Type' => 'application/json')
          end

        return resp.body if resp.success?

        errmsg = ResponseErrorFormatter.format(response: resp, client_method_name: caller_locations.first.label)
        raise UnexpectedResponseError, errmsg
      rescue Faraday::ResourceNotFound => e
        errmsg = "Preservation::Client.#{caller_locations.first.label} " \
                 "got #{e.response[:status]} from Preservation at #{req_url}: #{e.response[:body]}"
        raise NotFoundError, errmsg
      rescue Faraday::Error => e
        errmsg = "Preservation::Client.#{caller_locations.first.label} " \
                 "got #{e.response[:status]} from Preservation at #{req_url}: #{e.response[:body]}"
        raise http_exception_class(e.response[:status]), errmsg
      end

      # @param status_code [Integer] the HTTP status code to translate to an exception class
      def http_exception_class(status_code)
        case status_code
        when 404
          NotFoundError
        when 423
          LockedError
        when 409
          ConflictError
        else
          UnexpectedResponseError
        end
      end

      def with_indifferent_access_for(obj)
        if obj.is_a?(Array)
          obj.map { |member| with_indifferent_access_for(member) }
        elsif obj.is_a?(Hash)
          obj.with_indifferent_access
        else
          obj
        end
      end
    end
  end
end