lib/buckybox/api.rb in buckybox-api-1.6.2 vs lib/buckybox/api.rb in buckybox-api-1.7.0

- old
+ new

@@ -1,51 +1,65 @@ require "cgi" -require "httparty" require "crazy_money" -require "super_recursive_open_struct" +require "hashie/mash" +require "oj" +require "typhoeus" module BuckyBox class API ResponseError = Class.new(Exception) # generic error - NotFoundError = Class.new(Exception) + NotFoundError = Class.new(ResponseError) ENDPOINTS = { production: "https://api.buckybox.com/v1", staging: "https://api-staging.buckybox.com/v1", development: "http://api.buckybox.local:3000/v1", - test: "http://api.buckybox.local:3000/v1", + test: "https://api.buckybox.com/v1", }.freeze + class Response < Hashie::Mash + def initialize(hash) + unless hash.is_a?(Hash) + raise ArgumentError, "#{hash.inspect} must be a Hash" + end + + super(hash, nil) do |object, key| + raise NoMethodError, "undefined method `#{key}' for #{object}" + end + end + end + class CachedResponse - attr_reader :response, :cached_at + attr_reader :response def initialize(response) @response, @cached_at = response, epoch end def expired? - epoch - cached_at > 60 # NOTE: cache responses for 60 seconds + epoch - @cached_at > 60 # NOTE: cache responses for 60 seconds end private def epoch Time.now.utc.to_i end end - include HTTParty - format :json - base_uri ENDPOINTS.fetch(ENV.fetch("RAILS_ENV", "production").to_sym) + def self.fixtures_path + File.expand_path("../../../fixtures", __FILE__) + end def initialize(headers) - self.class.headers(headers) + @headers = headers.freeze + @endpoint = ENDPOINTS.fetch(ENV.fetch("RAILS_ENV", :production).to_sym) end - def boxes(params = {embed: "images"}, options = {}) + def boxes(params = { embed: "images" }, options = {}) query :get, "/boxes", params, options, price: CrazyMoney end - def box(id, params = {embed: "extras,images,box_items"}, options = {}) + def box(id, params = { embed: "extras,images,box_items" }, options = {}) query :get, "/boxes/#{id}", params, options, price: CrazyMoney end def delivery_services(params = {}, options = {}) query :get, "/delivery_services", params, options, fee: CrazyMoney @@ -70,13 +84,13 @@ def authenticate_customer(params = {}, options = {}) query :post, "/customers/sign_in", params, options end def create_or_update_customer(json_customer) - customer = JSON.parse(json_customer) + customer = Oj.load(json_customer) - if customer['id'] + if customer["id"] query :put, "/customers/#{customer['id']}", json_customer # TODO: replace by :patch else query :post, "/customers", json_customer end end @@ -89,51 +103,70 @@ @cache = nil end private - def check_response!(response) - unless [200, 201].include? response.code - message = response.parsed_response["message"] || response.parsed_response - message = "Error #{response.code} - #{message}" - - raise exception_type(response.code), message + def handle_error!(response) + parsed_response = parse_response(response) + message = if parsed_response + parsed_response["message"] || parsed_response + else + "Empty response" end + + message = "Error #{response.code} - #{message}" + + raise exception_type(response.code), message end + def parse_response(response) + Oj.load(response.body) + end + def exception_type(http_code) { 404 => NotFoundError, }.fetch(http_code, ResponseError) end - def query(type, uri, params = {}, options = {}, types = {}) - options = { - as_object: true - }.merge(options) + def query(method, path, params = {}, options = {}, types = {}) + options = { as_object: true }.merge(options) + hash = query_cache(method, path, params, types) - hash = query_cache(type, uri, params, types) - if options[:as_object] - SuperRecursiveOpenStruct.new(hash) + if hash.is_a?(Array) + hash.map { |item| Response.new(item) } + else + Response.new(hash) + end else hash end.freeze end - def query_cache(type, uri, params, types) - query_fresh = -> { - params_key = (type == :get ? :query : :body) - response = self.class.public_send(type, uri, params_key => params) - check_response!(response) - parsed_response = response.parsed_response + def query_cache(method, path, params, types) + uri = [@endpoint, path].join + + query_fresh = lambda do + params_key = (method == :get ? :params : :body) + + response = Typhoeus::Request.new( + uri, + headers: @headers, + method: method, + params_key => params, + accept_encoding: "gzip", + ).run + + handle_error!(response) unless response.success? + parsed_response = parse_response(response) add_types(parsed_response, types) - } + end - if type == :get # NOTE: only cache GET method + if method == :get # NOTE: only cache GET method @cache ||= {} - cache_key = [self.class.headers.hash, uri, to_query(params)].join + cache_key = [@headers.hash, uri, to_query(params)].join cached_response = @cache[cache_key] if cached_response && !cached_response.expired? cached_response.response else @@ -161,10 +194,10 @@ end end def to_query(hash) if hash.empty? - '' + "" else hash.map do |key, value| "#{CGI.escape(key.to_s)}=#{CGI.escape(value)}" end.sort!.join("&") end