avdi/leadlight

View on GitHub
lib/leadlight/request.rb

Summary

Maintainability
A
45 mins
Test Coverage
require 'ostruct'
require 'monitor'
require 'fattr'
require 'forwardable'
require 'hookr'
require 'leadlight/errors'
require 'leadlight/blank'
require 'leadlight/hyperlinkable'
require 'leadlight/representation'
require 'leadlight/type_map'
require 'leadlight/header_helpers'
require 'leadlight/null_link'

module Leadlight
  class Request
    include HookR::Hooks
    include MonitorMixin
    extend Forwardable
    include HeaderHelpers

    fattr(:http_method)
    fattr(:url)
    fattr(:connection)
    fattr(:body)
    fattr(:service)
    fattr(:codec)
    fattr(:type_map) { service.type_map || TypeMap.new }

    attr_reader :response, :options, :link

    define_hook :on_prepare_request, :request
    define_hook :on_complete,        :response

    def_delegator :service, :service_options

    def initialize(service, connection, url, method, body=nil, options={})
      @options         = options
      self.connection  = connection
      self.url         = url
      self.http_method = method
      self.body        = body
      self.service     = service
      @completed       = new_cond
      @state           = :initialized
      @env             = nil
      @response        = nil
      @link            = options.fetch(:link) { NullLink.new(url) }
      super()
    end

    def completed?
      :completed == @state
    end

    def submit
      entity = type_map.to_entity_body(body)
      entity_body = entity.body
      content_type = entity.content_type
      connection.run_request(http_method, url.to_s, entity_body, {}) do
        |request|

        request.headers['Content-Type'] = content_type if content_type
        request.options[:leadlight_request] = self
        execute_hook(:on_prepare_request, request)
      end.on_complete do |env|
        synchronize do
          @response = env.fetch(:response)
          execute_hook :on_complete, @response
          @env = env
          @state = :completed
          @completed.broadcast
        end
      end
    end

    def wait
      synchronize do
        @completed.wait_until{completed?}
      end
      yield(@env.fetch(:leadlight_representation)) if block_given?
      self
    end

    def submit_and_wait(&block)
      submit
      wait(&block)
    end
    alias_method :then, :submit_and_wait

    def on_error
      on_or_after_complete do |response|
        unless response.success?
          yield(response.env.fetch(:leadlight_representation))
        end
      end
      self
    end

    def raise_on_error
      on_error do |representation|
        raise representation
      end
    end

    def on_or_after_complete(&block)
      synchronize do
        if completed?
          block.call(response)
        else
          on_complete(&block)
        end
      end
    end

    def represent(env)
      content_type = env[:response_headers]['Content-Type']
      content_type = clean_content_type(content_type)
      representation = type_map.to_native(content_type, env[:body])
      representation.
        extend(Representation).
        initialize_representation(env[:leadlight_service], location(env), env[:response], self).
        extend(Hyperlinkable).
        apply_all_tints
    end

    def params
      link_params.merge(request_params)
    end

    def location(env=@env)
      env ||= {}
      url = env.fetch(:response_headers){{}}.fetch('location'){ env.fetch(:url){ self.url } }
      Addressable::URI.parse(url)
    end

    private

    fattr(:faraday_request) {
      env.fetch(:request) { OpenStruct.new(params: {}) }
    }

    def env
      if completed?
        @env
      else
        {}
      end
    end

    def representation
      raise "No representation until complete" unless completed?
      @env.fetch(:leadlight_representation)
    end

    def request_params
      faraday_request.params
    end

    def link_params
      link.params
    end
  end
end