iZettle/api-model

View on GitHub
lib/api_model/response.rb

Summary

Maintainability
A
0 mins
Test Coverage
module ApiModel
  class Response
    FALL_THROUGH_METHODS = [
      :class, :nil?, :empty?, :acts_like?, :as_json, :blank?, :duplicable?,
      :eval_js, :html_safe?, :in?, :presence, :present?, :psych_to_yaml, :to_json,
      :to_param, :to_query, :to_yaml, :to_yaml_properties, :with_options, :is_a?,
      :respond_to?, :kind_of?
    ]

    attr_accessor :http_response, :objects

    def initialize(http_response, config)
      @http_response = http_response
      @_config = config || Configuration.new
    end

    def metadata
      @metadata ||= OpenStruct.new
    end

    def build_objects
      handle_response_errors
      return self if response_body.nil?

      if response_build_hash.is_a? Array
        self.objects = response_build_hash.collect{ |hash| build http_response.builder, hash }
      else
        self.objects = self.build http_response.builder, response_build_hash
      end

      self
    end

    def build(builder, hash)
      if builder.respond_to? :build
        if builder.method(:build).arity == 2
          builder.build self, hash
        else
          builder.build hash
        end
      else
        builder.new hash
      end
    end

    def response_body
      return @response_body if @response_body.present?

      if @_config.parser.method(:parse).arity == 2
        @response_body = @_config.parser.parse self, http_response.api_call.body
      else
        @response_body = @_config.parser.parse http_response.api_call.body
      end
    end

    def successful?
      http_response.api_call.success?
    end

    def response_cookies
      return @cookies if @cookies.present?
      jar = HTTP::CookieJar.new

      set_cookie = http_response.api_call.headers_hash["Set-Cookie"]
      set_cookie = set_cookie.split(", ") unless set_cookie.is_a?(Array)

      set_cookie.each do |cookie|
        jar.parse cookie, http_response.api_call.request.base_url
      end

      @cookies = jar.cookies
    end

    # Define common methods which should never be called on this abstract class, and should always be
    # passed down to the #objects
    FALL_THROUGH_METHODS.each do |transparent_method|
      class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
        def #{transparent_method}(*args, &block)
          objects.send '#{transparent_method}', *args, &block
        end
      RUBY_EVAL
    end

    # Pass though any method which is not defined to the built objects. This makes the response class
    # quite transparent, and keeps the response acting like the built object, or array of objects.
    def method_missing(method_name, *args, &block)
      objects.send method_name, *args, &block
    end

    # Uses a string notation split by colons to fetch nested keys from a hash. For example, if you have a hash
    # which looks like:
    #
    #   { foo: { bar: { baz: "Hello world" } } }
    #
    # Then calling ++fetch_from_body("foo.bar.baz")++ would return "Hello world"
    def fetch_from_body(key_reference)
      key_reference.split(".").inject(response_body) do |hash,key|
        begin
          hash.fetch(key, nil)
        rescue NoMethodError
          Log.error "Could not set #{key_reference} on #{hash}"
        end
      end
    end

    # If the model config defines a json root, use it on the response_body
    # to dig down in to the hash.
    def response_build_hash
      if @_config.json_root.present?
        begin
          fetch_from_body @_config.json_root
        rescue
          raise ResponseBuilderError, "Could not find key #{@_config.json_root} in:\n#{response_body}"
        end
      else
        response_body
      end
    end

    private

    def handle_response_errors
      if @_config.raise_on_unauthenticated && http_response.api_call.response_code == 401
        raise UnauthenticatedError, @_config.error_message_formatter.format(http_response)
      end

      if @_config.raise_on_not_found && http_response.api_call.response_code == 404
        raise NotFoundError, @_config.error_message_formatter.format(http_response)
      end

      if @_config.raise_on_server_error && http_response.api_call.response_code.between?(500, 599)
        raise ServerError, @_config.error_message_formatter.format(http_response)
      end
    end

  end
end