class Ecertic::Client

Attributes

apikey[RW]
connection[RW]
secret[RW]
user_agent[RW]

Public Class Methods

default_connection() click to toggle source
# File lib/ecertic/client.rb, line 17
def self.default_connection
  Thread.current[:ecertic_client_default_connection] ||= begin
    Faraday.new do |builder|
      builder.use Faraday::Response::RaiseError
      builder.response :json,
                       content_type: /\bjson$/,
                       preserve_raw: true, parser_options: { symbolize_names: true }
      builder.adapter :net_http_persistent
    end
  end
end
new(options = {}) click to toggle source
# File lib/ecertic/client.rb, line 8
def initialize(options = {})
  defaults = Ecertic::Default.options
  Ecertic::Default.keys.each do |key|
    instance_variable_set(:"@#{key}", options[key] || defaults[key])
  end
  @connection = connection || self.class.default_connection
  @services = {}
end

Public Instance Methods

base_url() click to toggle source
# File lib/ecertic/client.rb, line 29
def base_url
  @base_url.chomp("/")
end
execute(method, path, data = nil, options = {}) click to toggle source
# File lib/ecertic/client.rb, line 41
def execute(method, path, data = nil, options = {})
  request(method, path, data, options)
end
get(path, options = {}) click to toggle source
# File lib/ecertic/client.rb, line 33
def get(path, options = {})
  execute :get, path, nil, options.to_h
end
post(path, data = nil, options = {}) click to toggle source
# File lib/ecertic/client.rb, line 37
def post(path, data = nil, options = {})
  execute :post, path, data, options
end
request(method, path, data = nil, options = {}) click to toggle source
# File lib/ecertic/client.rb, line 45
def request(method, path, data = nil, options = {})
  request_options = request_options(method, options, data)
  uri = "#{base_url}#{path}"

  begin
    request_start = Time.now
    log_request(method, path, request_options[:body], request_options[:headers])
    response = connection.run_request(method, uri, request_options[:body], request_options[:headers]) do |req|
      # req.options.open_timeout = Ecertic.open_timeout
      # req.options.timeout =  Ecertic.read_timeout
    end
    log_response(request_start, method, path, response.status, response.body)
    response
  rescue StandardError => e
    log_response_error(request_start, e, method, path)
    case e
    when Faraday::ClientError
      if e.response
        handle_error_response(e.response)
      else
        handle_network_error(e)
      end
    else
      raise
    end
  end
  Ecertic::Response.from_faraday_response(response)
end

Private Instance Methods

add_auth_options!(method, options, data) click to toggle source
# File lib/ecertic/client.rb, line 151
        def add_auth_options!(method, options, data)
  timestamp = (Time.now.to_f * 1000).to_i.to_s
  hmac = hmac(method, timestamp, options[:body], options[:headers]["Content-Type"])
  options[:headers]["Date"] = timestamp
  options[:headers]["Authorization"] = "Hmac #{apikey}:#{hmac}"
end
add_body!(options, data) click to toggle source
# File lib/ecertic/client.rb, line 146
        def add_body!(options, data)
  options[:headers]["Content-Type"] = content_type(options[:headers])
  options[:body] = content_data(options[:headers], data)
end
base_options() click to toggle source
# File lib/ecertic/client.rb, line 130
        def base_options
  {
    headers: {
      "Accept" => "application/json",
      "User-Agent" => format_user_agent,
    },
  }
end
content_data(headers, data) click to toggle source
# File lib/ecertic/client.rb, line 176
        def content_data(headers, data)
  headers["Content-Type"] == "application/json" ? data.to_json : data
end
content_type(headers) click to toggle source
# File lib/ecertic/client.rb, line 172
        def content_type(headers)
  headers["Content-Type"] || "application/json"
end
format_user_agent() click to toggle source
# File lib/ecertic/client.rb, line 164
        def format_user_agent
  if user_agent.to_s.empty?
    Ecertic::Default::USER_AGENT
  else
    "#{Ecertic::Default::USER_AGENT} #{user_agent}"
  end
end
general_api_error(status, body) click to toggle source
# File lib/ecertic/client.rb, line 110
        def general_api_error(status, body)
  APIError.new("Invalid response object from API: #{body.inspect} " \
               "(HTTP response code was #{status})",
               http_status: status, http_body: body)
end
handle_error_response(http_response) click to toggle source
# File lib/ecertic/client.rb, line 100
        def handle_error_response(http_response)
  begin
    response = Ecertic::Response.new(http_response)
  rescue JSON::ParserError
    raise general_api_error(http_response[:status], http_response[:body])
  end

  raise specific_api_error(response)
end
handle_network_error(error) click to toggle source
# File lib/ecertic/client.rb, line 74
        def handle_network_error(error)
  Ecertic::Utils.log_error("Ecertic network error", error_message: error.message)

  message = case error
            when Faraday::ConnectionFailed
              "Unexpected error communicating when trying to connect to " \
              "Ecertic. You may be seeing this message because your DNS is not " \
              "working. To check, try running `host http://api.otpsecure.net` from the " \
              "command line."

            when Faraday::SSLError
              "Could not establish a secure connection to Ecertic, you " \
              "may need to upgrade your OpenSSL version. To check, try running " \
              "`openssl s_client -connect api.otpsecure.net:443` from the command " \
              "line."

            when Faraday::TimeoutError
              "Could not connect to Ecertic (#{ Ecertic.api_base}). " \
              "Please check your internet connection and try again."

            else
              "Unexpected error communicating with Ecertic."
            end
  raise APIConnectionError, message + "\n\n(Network error: #{error.message})"
end
hmac(method, timestamp, data, content_type) click to toggle source
# File lib/ecertic/client.rb, line 158
        def hmac(method, timestamp, data, content_type)
  md5 = Digest::MD5.hexdigest(data)
  signature = "#{method.upcase}\n#{md5}\n#{content_type}\n#{timestamp}"
  OpenSSL::HMAC.hexdigest("SHA1", secret, signature)
end
log_request(method, path, body, headers) click to toggle source
# File lib/ecertic/client.rb, line 180
        def log_request(method, path, body, headers)
  Ecertic::Utils.log_info("Request to Ecertic API", method: method, path: path)
  Ecertic::Utils.log_debug("Request details", body: body, headers: headers)
end
log_response(request_start, method, path, status, body) click to toggle source
# File lib/ecertic/client.rb, line 185
        def log_response(request_start, method, path, status, body)
  Ecertic::Utils.log_info("Response from Ecertic API",
                          elapsed: Time.now - request_start,
                          method: method,
                          path: path,
                          status: status)
  Ecertic::Utils.log_debug("Response details", body: body)
end
log_response_error(request_start, error, method, path) click to toggle source
# File lib/ecertic/client.rb, line 194
        def log_response_error(request_start, error, method, path)
  Ecertic::Utils.log_error("Request error",
                           elapsed: Time.now - request_start,
                           error_message: error.message,
                           method: method,
                           path: path)
end
request_options(method, options = {}, data = nil) click to toggle source
# File lib/ecertic/client.rb, line 139
        def request_options(method, options = {}, data = nil)
  base_options.tap do |options|
    add_body!(options, data) if data
    add_auth_options!(method, options, data) if data
  end
end
specific_api_error(response) click to toggle source
# File lib/ecertic/client.rb, line 116
        def specific_api_error(response)
  Ecertic::Utils.log_error("Ecertic API error", status: response.status)

  error = case response.status
          when 400, 404
            InvalidRequestError
          when 401
            AuthenticationError
          else
            APIError
          end
  error.new(response.body, response: response)
end