class SierraApiClient

Public Class Methods

new(config = {}) click to toggle source
# File lib/nypl_sierra_api_client.rb, line 10
def initialize(config = {})
  config_defaults = {
    env: {
      base_url: 'SIERRA_API_BASE_URL',
      client_id: 'SIERRA_OAUTH_ID',
      client_secret: 'SIERRA_OAUTH_SECRET',
      oauth_url: 'SIERRA_OAUTH_URL'
    },
    static: {
      log_level: 'info'
    }
  }

  @config = config_defaults[:env].map {|k,v| [k, ENV[v]]}.to_h
    .merge config_defaults[:static]
    .merge config

  config_defaults[:env].each do |key, value|
    raise SierraApiClientError.new "Missing config: neither config.#{key} nor ENV.#{value} are set" unless @config[key]
  end

  @retries = 0
end

Public Instance Methods

get(path, options = {}) click to toggle source
# File lib/nypl_sierra_api_client.rb, line 34
def get (path, options = {})
  options = parse_http_options options

  do_request 'get', path, options
end
post(path, body, options = {}) click to toggle source
# File lib/nypl_sierra_api_client.rb, line 40
def post (path, body, options = {})
  options = parse_http_options options

  # Default to POSTing JSON unless explicitly stated otherwise
  options[:headers]['Content-Type'] = 'application/json' unless options[:headers]['Content-Type']

  do_request 'post', path, options do |request|
    request.body = body
    request.body = request.body.to_json unless options[:headers]['Content-Type'] != 'application/json'
  end
end

Private Instance Methods

authenticate!() click to toggle source

Authorizes the request.

# File lib/nypl_sierra_api_client.rb, line 139
def authenticate!
  # NOOP if we've already authenticated
  return nil if ! @access_token.nil?

  logger.debug "SierraApiClient: Authenticating with client_id #{@config[:client_id]}"

  uri = URI.parse("#{@config[:oauth_url]}")
  request = Net::HTTP::Post.new(uri)
  request.basic_auth(@config[:client_id], @config[:client_secret])
  request.set_form_data(
    "grant_type" => "client_credentials"
  )

  req_options = {
    use_ssl: uri.scheme == "https",
    request_timeout: 500
  }

  response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
    http.request(request)
  end

  if response.code == '200'
    @access_token = JSON.parse(response.body)["access_token"]
  else
    nil
  end
end
do_request(method, path, options = {}) { |request| ... } click to toggle source
# File lib/nypl_sierra_api_client.rb, line 54
def do_request (method, path, options = {})
  # For now, these are the methods we support:
  raise SierraApiClientError, "Unsupported method: #{method}" unless ['get', 'post'].include? method.downcase

  authenticate! if options[:authenticated]

  @uri = URI.parse("#{@config[:base_url]}#{path}")

  # Build request headers:
  request_headers = {}
  request_headers['Content-Type'] = options[:headers]['Content-Type'] unless options.dig(:headers, 'Content-Type').nil?

  # Create HTTP::Get or HTTP::Post
  request =  Net::HTTP.const_get(method.capitalize).new(@uri, request_headers)

  # Add bearer token header
  request['Authorization'] = "Bearer #{@access_token}" if options[:authenticated]

  # Allow caller to modify the request before we send it off:
  yield request if block_given?

  logger.debug "SierraApiClient: #{method} to Sierra api", { uri: @uri, body: request.body }

  execute request, options
end
execute(request, options) click to toggle source
# File lib/nypl_sierra_api_client.rb, line 80
def execute (request, options)
  http = Net::HTTP.new(@uri.host, @uri.port)
  http.use_ssl = @uri.scheme === 'https'

  begin
    response = http.request(request)
    logger.debug "SierraApiClient: Got Sierra api response", { code: response.code, body: response.body }
  rescue => e
    raise SierraApiClientError.new "Failed to #{request.method} to #{request.path}: #{e.message}"
  end

  handle_response response, request, options
end
handle_response(response, request, options) click to toggle source
# File lib/nypl_sierra_api_client.rb, line 94
def handle_response (response, request, options)
  if response.code == "401"
    # Likely an expired access-token; Wipe it for next run
    @access_token = nil
    if @retries < 3
      if options[:authenticated]
        logger.debug "SierraApiClient: Refreshing oauth token for 401", { code: 401, body: response.body, retry: @retries }

        return reauthenticate_and_reattempt request, options
      end
    else
      retries_exceeded = true
    end

    reset_retries
    message = "Got a 401: #{retries_exceeded ? "Maximum retries exceeded, " : ''}#{response.body}"
    raise SierraApiClientTokenError.new(message)
  end

  reset_retries if @retries > 0
  SierraApiResponse.new(response)
end
logger() click to toggle source
# File lib/nypl_sierra_api_client.rb, line 168
def logger
  @logger ||= NyplLogFormatter.new(STDOUT, level: @config[:log_level])
end
parse_http_options(_options) click to toggle source
# File lib/nypl_sierra_api_client.rb, line 126
def parse_http_options (_options)
  options = {
    authenticated: true
  }.merge _options

  options[:headers] = {
  }.merge(_options[:headers] || {})
    .transform_keys(&:to_s)

  options
end
reauthenticate_and_reattempt(request, options) click to toggle source
# File lib/nypl_sierra_api_client.rb, line 117
def reauthenticate_and_reattempt request, options
  @retries += 1
  authenticate!
  # Reset bearer token header
  request['Authorization'] = "Bearer #{@access_token}"

  execute request, options
end
reset_retries() click to toggle source
# File lib/nypl_sierra_api_client.rb, line 172
def reset_retries
  @retries = 0
end