class BillForward::Client

Attributes

all_verbs[RW]
default_client[R]

default client is a singleton client

api_token[RW]
host[RW]
logger[RW]
use_logging[RW]

Public Class Methods

default_client=(default_client) click to toggle source
# File lib/bill_forward/client.rb, line 59
def default_client=(default_client)
  if (default_client == nil)
    # meaningless, but required for resetting this class after a test run
    @default_client = nil
    return
  end

  TypeCheck.verifyObj(Client, default_client, 'default_client')
  @default_client = default_client
end
make_default_client(options) click to toggle source

Constructs a client, and sets it to be used as the default client. @param options={} [Hash] Options with which to construct client

@return [Client] The constructed client

# File lib/bill_forward/client.rb, line 83
def self.make_default_client(options)
  constructedClient = self.new(options)
  self.default_client = constructedClient
end
new(options={}) click to toggle source
# File lib/bill_forward/client.rb, line 88
def initialize(options={})
  TypeCheck.verifyObj(Hash, options, 'options')
  @use_logging = options[:use_logging]
  @logger = options[:logger] || Logger.new(STDOUT)

  if options[:host]
    @host = options[:host]
  else
    raise ClientInstantiationException.new "Failed to initialize BillForward API Client\n" +
                                           "Required parameters: :host and either [:api_token] or all of [:client_id, :client_secret, :username, :password].\n" +
                                           "Supplied Parameters: #{options}"
  end

  if options[:use_proxy]
    @use_proxy = options[:use_proxy]
    @proxy_url = options[:proxy_url]
  end

  if options[:api_token]
    @api_token = options[:api_token]
  else
    @api_token = nil
    if options[:client_id] and options[:client_secret] and options[:username] and options[:password]
      @client_id = options[:client_id]
      @client_secret = options[:client_secret]
      @username = options[:username]
      @password = options[:password]
    else
      raise ClientException.new "Failed to initialize BillForward API Client\n"+
                                "Required parameters: :host and either [:api_token] or all of [:client_id, :client_secret, :username, :password].\n" +
                                "Supplied Parameters: #{options}"
    end
  end

  @authorization = nil
end

Public Instance Methods

execute_request(verb, url, token, payload=nil) click to toggle source
# File lib/bill_forward/client.rb, line 125
def execute_request(verb, url, token, payload=nil)
  # Enable Fiddler:
  if @use_proxy
    RestClient.proxy = @proxy_url
  end
  
  # content_type seems to be broken on generic execute.
  # darn.
  # RestClient::Request.execute(options)
  options = {
    :Authorization => "Bearer #{token}",
    :accept => 'application/json'
  }

  haspayload = @@payload_verbs.include?(verb)

  if (haspayload)
    options.update(:content_type => 'application/json')
  end

  args = [url, options]
  args.insert(1, payload) if haspayload

  log "#{verb.upcase} #{url}"
  log "headers: #{JSON.pretty_generate(options)}"
  log "payload: #{payload}" if haspayload

  RestClient.send(verb.intern, *args)
end

Private Instance Methods

get_token() click to toggle source
# File lib/bill_forward/client.rb, line 319
def get_token
  if @api_token
    @api_token
  else
    if @authorization and Time.now < @authorization["expires_at"]
      return @authorization["access_token"]
    end
    begin
      response = RestClient.get("#{@host}oauth/token", :params => {
          :username => @username,
          :password => @password,
          :client_id => @client_id,
          :client_secret => @client_secret,
          :grant_type => "password"
      }, :accept => :json)

      @authorization = JSON.parse(response.to_str)
      @authorization["expires_at"] = Time.now + @authorization["expires_in"]

      @authorization["access_token"]
    rescue => e
      if e.respond_to? "response"
        log "BILL FORWARD CLIENT ERROR", e.response
      else
        log "BILL FORWARD CLIENT ERROR", e, e.to_json
      end
      nil
    end
  end
end
handle_api_error(rcode, rbody) click to toggle source
# File lib/bill_forward/client.rb, line 277
def handle_api_error(rcode, rbody)
  begin
    # Example error JSON:
    # {
    #    "errorType" : "ValidationError",
    #    "errorMessage" : "Validation Error - Entity: Subscription Field: type Value: null Message: may not be null\nValidation Error - Entity: Subscription Field: productID Value: null Message: may not be null\nValidation Error - Entity: Subscription Field: name Value: null Message: may not be null\n",
    #    "errorParameters" : [ "type", "productID", "name" ]
    # }

    error = JSON.parse(rbody)

    errorType = error['errorType']
    errorMessage = error['errorMessage']
    if (error.key? 'errorParameters')
      errorParameters = error['errorParameters']
      raise_message = "\n====\n#{rcode} API Error.\nType: #{errorType}\nMessage: #{errorMessage}\nParameters: #{errorParameters}\n====\n"  
    else
      if (errorType == 'Oauth')
        split = errorMessage.split(', ')

        error = split.first.split('=').last
        description = split.last.split('=').last

        raise_message = "\n====\n#{rcode} Authorization failed.\nType: #{errorType}\nError: #{error}\nDescription: #{description}\n====\n"

        raise ApiAuthorizationError.new(error, rbody), raise_message
      else
        raise_message = "\n====\n#{rcode} API Error.\nType: #{errorType}\nMessage: #{errorMessage}\n====\n"  
      end
    end
    
    raise ApiError.new(error, rbody), raise_message
  end

  raise_message = "\n====\n#{rcode} API Error.\n Response body: #{rbody}\n====\n"
  raise ApiError.new(nil, rbody), raise_message
end
handle_restclient_error(e) click to toggle source
# File lib/bill_forward/client.rb, line 258
def handle_restclient_error(e)
  connection_message = "Please check your internet connection and try again. "

  case e
  when RestClient::RequestTimeout
    message = "Could not connect to BillForward (#{@host}). #{connection_message}"
  when RestClient::ServerBrokeConnection
    message = "The connection to the server (#{@host}) broke before the " \
      "request completed. #{connection_message}"
  when SocketError
    message = "Unexpected error communicating when trying to connect to BillForward. " \
      "Please confirm that (#{@host}) is a BillForward API URL. "
  else
    message = "Unexpected error communicating with BillForward. "
  end

  raise ClientException.new(message + "\n\n(Network error: #{e.message})")
end
log(*args) click to toggle source
# File lib/bill_forward/client.rb, line 315
def log(*args)
  @logger.info *args if @use_logging
end
request(verb, url, params={}, payload=nil) click to toggle source
# File lib/bill_forward/client.rb, line 207
def request(verb, url, params={}, payload=nil)
  qualified_url = "#{@host}#{url}"

  split = qualified_url.split('?')
  distilled_url = split.first
  override_params = split.length > 1 \
  ? split[1] \
  : ''

  param_string = override_params.empty? \
  ? ((params && params.any?) \
    ? uri_encode(params) \
    : '') \
  : override_params

  # Make params into query parameters
  full_url = param_string.empty? \
  ? distilled_url \
  : [distilled_url, param_string].join('?')

  token = get_token

  begin
    response = execute_request(verb, full_url, token, payload)

    parsed = JSON.parse(response.to_str)
    pretty = JSON.pretty_generate(parsed)
    log "response: \n#{pretty}"

    return parsed
  rescue SocketError => e
    handle_restclient_error(e)
  rescue NoMethodError => e
    # Work around RestClient bug
    if e.message =~ /\WRequestFailed\W/
      e = APIConnectionError.new('Unexpected HTTP response code')
      handle_restclient_error(e)
    else
      raise
    end
  rescue RestClient::ExceptionWithResponse => e
    if rcode = e.http_code and rbody = e.http_body
      handle_api_error(rcode, rbody)
    else
      handle_restclient_error(e)
    end
  rescue RestClient::Exception, Errno::ECONNREFUSED => e
    handle_restclient_error(e)
  end
end
uri_encode(params = {}) click to toggle source
# File lib/bill_forward/client.rb, line 195
def uri_encode(params = {})
  TypeCheck.verifyObj(Hash, params, 'params')

  encoded_params = params.reduce([]) do |accumulator, (iterand_key, iterand_value)|
    encoded_key = ERB::Util.url_encode iterand_key
    encoded_value = ERB::Util.url_encode iterand_value
    accumulator + ["#{encoded_key}=#{encoded_value}"]
  end
  query = encoded_params.join '&'
  
end