lib/carsensor/api.rb in carsensor-api-0.0.1.alpha.1 vs lib/carsensor/api.rb in carsensor-api-0.0.1.alpha.2

- old
+ new

@@ -1,21 +1,27 @@ # frozen_string_literal: true +require 'logger' require 'uri' require 'open-uri' require 'carsensor/api/version' require 'rack/utils' +require 'retriable' module Carsensor - # A thin wrapper for Carsensor Web API at (https://webservice.recruit.co.jp/carsensor/reference.html + # A thin wrapper for Carsensor Web API at https://webservice.recruit.co.jp/carsensor/reference.html class API # Exception class for errors which would occur during invocation of Carsensor API class Error < StandardError # @param message [String] Message for the exception # @param code [Integer,String] Error code of API itself or error status of HTTP - def initialize(message:, code:) - super('%s(code: %s)' % [message, code]) + def initialize(message:, code: nil) + if code + super('%s(code: %s)' % [message, code]) + else + super(message) + end end end ENDPOINT_BASE_URI = URI('http://webservice.recruit.co.jp') @@ -23,14 +29,16 @@ module Metadata attr_accessor :metadata end # @param key [String] The API key - def initialize(key:) + def initialize(key:, logger: Logger.new(STDOUT), tries: 3) raise ArgumentError, 'key: must be a String' unless key.is_a?(String) @key = key + @logger = logger + @tries = tries end # Returns bodies for given criterion # # @param query [Hash] options to specify criterion @@ -95,31 +103,55 @@ end private def call_api(root, path, **query) - (method(:build_uri) >> method(:read_uri) >> method(:parse_body) >> method(:extract_result).curry[root]).(path, query) + Retriable.retriable(tries: @tries, on: [Error], on_retry: on_retry_proc(path, query)) do + (method(:build_uri) >> method(:read_uri) >> method(:parse_body) >> method(:extract_result).curry[root]).(path, query) + end end + def on_retry_proc(path, **query) + lambda do |exception, _try, _elapsed_time, _next_interval| + return unless @logger + + @logger.info(retry_message(exception, path, query)) + end + end + + def retry_message(exception, path, **query) + query_string = build_query_string(query) + request = query_string.length.zero? ? path : path + '?' + query_string + '%s %s %s' % [exception.class, exception.message, request] + end + + def build_query_string(**query) + Rack::Utils.build_query(key: @key, format: 'json', **query.transform_values {|v| v.is_a?(Array) ? v.join(',') : v }) + end + def build_uri(path, **query) ENDPOINT_BASE_URI.dup.tap do |u| u.path = path - u.query = Rack::Utils.build_query(key: @key, format: 'json', **query.transform_values {|v| v.is_a?(Array) ? v.join(',') : v }) + u.query = build_query_string(query) end end def read_uri(uri) uri.read rescue OpenURI::HTTPError => e status = e.io.status raise Error.new(message: status[1], code: status[0]) + rescue SocketError => e + raise Error.new(message: e.message) end def parse_body(body) JSON.parse(body, symbolize_names: true).tap do |data| api_error = data.dig(:results, :error, 0) raise Error.new(**api_error) if api_error end + rescue JSON::ParserError => e + raise Error.new(message: e.message) end def extract_result(root, data) (data.dig(:results, root) || []).tap do |result| result.extend(Metadata)