ResultadosDigitais/gooder_data

View on GitHub
lib/gooder_data/api_client.rb

Summary

Maintainability
A
3 hrs
Test Coverage
require 'httparty'
require 'json'

module GooderData

  class ApiClient
    include HTTParty

    BAD_REQUEST = 400
    NOT_FOUND = 404
    UNAUTHORIZED = 401
    STILL_PROCESSING = 202
    NO_CONTENT = 204

    STATUS_WAIT = 'WAIT'

    def initialize(options = {})
      @super_secure_token = ""
      @temp_token = ""
      @options = GooderData.configuration.merge(options)
      self.class.base_uri(@options.require(:base_uri))
    end

    def super_secure_token=(super_secure_token)
      @super_secure_token = super_secure_token
    end

    def api_token=(api_token)
      @temp_token = api_token
    end

    def connect!(user = options[:user], password = options[:user_password])
      login!(user, password)
      api_token!
      self
    end

    def login!(user, password)
      self.super_secure_token = login(user, password)
    end

    def login(user, password)
      api_to("login the user '#{ user }'", :no_validations) do
        response = post("/account/login", {
          postUserLogin: {
            login: user,
            password: password,
            remember: 1
          }
        })
        raise "wrong user or password for user #{ user }" if response.code == UNAUTHORIZED
        response
      end.that_responds do |response|
        extract_cookie(response, 'GDCAuthSST')
      end
    end

    def api_token!
      self.api_token = api_token
    end

    def api_token
      api_to("get api_token", :needs_login) do
        get("/account/token")
      end.that_responds do |response|
        extract_cookie(response, 'GDCAuthTT')
      end
    end

    def api_to(description, pre_validation = :needs_api_token)
      send(pre_validation)
      response = yield options
      validate(response, "could not #{ description }")
    end

    def retry_api_to(description, pre_validation = :needs_api_token, &api_call_block)
      response = nil
      (0..options.require(:max_retries)).each do
        response = api_to(description, pre_validation, &api_call_block)
        break unless processing?(response)
        puts "Retrying #{description}: #{response.code}: #{response.task_status}"
        sleep options.require(:retry_delay_in_seconds).to_f
      end
      response
    end

    def post(path, data)
      options = basic_options
      options[:headers]["Content-Type"] = "application/json"
      options[:body] = data.to_json
      send_request(:post, path, options)
    end

    def get(path)
      send_request(:get, path)
    end

    def options
      @options
    end

    private

    def processing?(response)
      response.processing?
    end

    def to_json_hash_array(response, api_class, *array_path)
      elements = try_hash_chain(response, *array_path) || []
      JsonHashArray.from_haw_json(api_class, elements, options)
    end

    def no_validations; end

    def validate_api_token
      validate_login
      raise "api token is required at this point: please inform the api_token (temporary token - TT) or call api_token! first" if @temp_token.to_s.empty?
    end
    alias_method :needs_api_token, :validate_api_token

    def validate_login
      raise "login is required at this point: please inform the super_secure_token (SST) or call login! first" if @super_secure_token.to_s.empty?
    end
    alias_method :needs_login, :validate_login

    def validate(response, error_message)
      raise error_class(response), "#{ error_message }: #{ error_message(response) || response.parsed_response }" unless success?(response)
      response
    end

    def error_class(response)
      case response.code
      when BAD_REQUEST
        GooderData::ApiClient::BadRequestError
      else
        GooderData::ApiClient::Error
      end
    end

    def error_message(response)
      error = response['error'] || {}
      message = error['message'] || ""
      parameters = error['parameters'] || []

      message % parameters
    end

    def basic_options
      {
        headers: {
          "Accept" => "application/json",
          "Cookie" => "$Version=0; GDCAuthSST=#{ @super_secure_token }; $Path=/gdc/account; GDCAuthTT=#{ @temp_token }"
        }
      }
    end

    def send_request(method, path, options = nil)
      self.class.send(method, path, options || basic_options)
    end

    def self.try_hash_chain(hash, *key_chain)
      if hash.is_a?(Hash) || hash.is_a?(HTTParty::Response)
        key = key_chain.delete_at(0)
        value = hash.with_indifferent_access[key]
        return value if key_chain.empty?

        try_hash_chain(value, *key_chain)
      end
    end

    def try_hash_chain(hash, *key_chain)
      self.class.try_hash_chain(hash, *key_chain)
    end

    def success?(response)
      response.code.to_s.start_with?('2')
    end

    def extract_cookie(response, param_name)
      capture_match(response.headers['set-cookie'], /#{ param_name }=([^;]*);/)
    end

    def capture_match(string, regex, mismatch = '')
      match = string.match(regex)
      return mismatch unless match

      c = match.captures
      c.any? ? c.last : mismatch
    end

  end

end

module HTTParty
  class Response
    def that_responds
      yield self
    end
    alias_method :responds, :that_responds

    def processing?
      code == ::GooderData::ApiClient::STILL_PROCESSING || task_status == ::GooderData::ApiClient::STATUS_WAIT
    end

    def task_status
      ::GooderData::ApiClient.try_hash_chain(parsed_response, 'taskState', 'status')
    end
  end
end