hakanensari/peddler

View on GitHub
lib/peddler/client.rb

Summary

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

require 'forwardable'
require 'jeff'
require 'peddler/errors/builder'
require 'peddler/marketplace'
require 'peddler/operation'
require 'peddler/parser'

module Peddler
  # An abstract client
  #
  # Subclass this to implement an MWS API section.
  class Client
    extend Forwardable
    include Jeff

    class << self
      # @!visibility private
      attr_accessor :parser, :path, :version

      private

      def inherited(base)
        super
        base.parser = parser
        base.params params
      end
    end

    params 'SellerId' => -> { merchant_id },
           'MWSAuthToken' => -> { auth_token },
           'Version' => -> { version }
    self.parser = Parser

    def_delegators :marketplace, :host, :encoding
    def_delegators :'self.class', :parser, :version

    # Creates a new client
    # @param [Hash] opts
    # @option opts [String] :aws_access_key_id
    # @option opts [String] :aws_secret_access_key
    # @option opts [String, Peddler::Marketplace] :marketplace
    # @option opts [String] :merchant_id
    # @option opts [String] :auth_token
    def initialize(opts = {})
      opts.each { |k, v| send("#{k}=", v) }
    end

    # The MWS Auth Token for a seller's account
    # @note You can omit this if you are accessing your own seller account
    # @return [String]
    attr_accessor :auth_token

    # The seller's Merchant ID
    # @note Amazon also refers to this as Seller ID or Merchant Token
    # @return [String]
    attr_accessor :merchant_id

    # The marketplace where you signed up as application developer
    # @note You can pass the two-letter country code of the marketplace as
    #   shorthand when setting
    # @return [Peddler::Marketplace]
    attr_reader :marketplace

    # @!parse attr_writer :marketplace
    def marketplace=(marketplace)
      @marketplace =
        if marketplace.is_a?(Marketplace)
          marketplace
        else
          Marketplace.find(marketplace)
        end
    end

    # The body of the HTTP request
    # @return [String]
    attr_reader :body

    # @!parse attr_writer :body
    def body=(str)
      str ? add_content(str) : clear_content!
    end

    # @!visibility private
    attr_writer :path

    # @!visibility private
    def path
      @path ||= self.class.path
    end

    # @!visibility private
    def defaults
      @defaults ||= { expects: 200 }
    end

    # @!visibility private
    def headers
      @headers ||= {}
    end

    # @!visibility private
    def aws_endpoint
      "https://#{host}#{path}"
    end

    # @!visibility private
    def operation(action = nil)
      action ? @operation = Operation.new(action) : @operation
    end

    # @!visibility private
    def run(&block)
      opts = build_options
      opts.store(:response_block, block) if block
      res = post(opts)
      self.body = nil if res.status == 200

      parser.new(res, encoding)
    rescue ::Excon::Error::HTTPStatus => e
      handle_http_status_error(e)
    end

    private

    def clear_content!
      headers.delete('Content-Type')
      @body = nil
    end

    def add_content(content)
      @body = content
      if content.encoding.names.include?('BINARY')
        headers['Content-Type'] = 'application/octet-stream'
      elsif content.start_with?('<?xml')
        headers['Content-Type'] = 'text/xml'
      else
        headers['Content-Type'] =
          "text/tab-separated-values; charset=#{encoding}"
        @body = @body.encode(encoding)
      end
    end

    def extract_options(args)
      args.last.is_a?(Hash) ? args.pop : {}
    end

    def build_options
      opts = defaults.merge(query: operation, headers: headers)
      body ? opts.update(body: body) : opts
    end

    def handle_http_status_error(error)
      new_error = Errors::Builder.call(error)
      raise new_error || error
    end
  end
end