# frozen_string_literal: true module OpenRecycling class Resource attr_reader :api_url, :jwt_token, :root_node_singular, :root_node_plural def initialize(root_node_singular: nil, root_node_plural: nil, api_url: nil, jwt_token: nil) @api_url = api_url @jwt_token = jwt_token @root_node_singular = root_node_singular @root_node_plural = root_node_plural end def all(options = {}) ensure_supported_method(:all) uri = URI(build_uri_with_params(api_url, options)) response = execute_request(Net::HTTP::Get, uri) if response.is_a?(Net::HTTPSuccess) parse_resources(response.body) else handle_error(response) end end def find(id) ensure_supported_method(:find) uri = URI("#{api_url}/#{id}") response = execute_request(Net::HTTP::Get, uri) if response.is_a?(Net::HTTPSuccess) parse_resource(response.body) else handle_error(response) end end def create(attributes) ensure_supported_method(:create) uri = URI(api_url) response = execute_request(Net::HTTP::Post, uri, { root_node_singular => attributes }.to_json) if response.is_a?(Net::HTTPSuccess) parse_resource(response.body) else handle_error(response) end end def update(id, attributes) ensure_supported_method(:update) uri = URI("#{api_url}/#{id}") response = execute_request(Net::HTTP::Patch, uri, { root_node_singular => attributes }.to_json) if response.is_a?(Net::HTTPSuccess) parse_resource(response.body) else handle_error(response) end end def destroy(id) ensure_supported_method(:destroy) uri = URI("#{api_url}/#{id}") response = execute_request(Net::HTTP::Delete, uri) if response.is_a?(Net::HTTPSuccess) true else handle_error(response) end end def ensure_supported_method(method) return if self.class.supported_methods.include?(method) raise NotImplementedError.new("Method `#{method}` is not supported for this resource") end def self.only(*methods) @only = methods end def self.supported_methods @only ||= [:all, :find, :create, :update, :destroy] end private def execute_request(cls, uri, body = nil) request = cls.new(uri) request["Authorization"] = "Bearer #{jwt_token}" request["Content-Type"] = "application/json" request["Accept"] = "application/json" request.body = body OpenRecycling.logger.info("#{request.method} #{uri}") OpenRecycling.logger.debug(request.body) Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http| http.request(request) end end def build_uri_with_params(uri, params) [uri, URI.encode_www_form(params)].reject { |s| s == "" }.join("?") end def parse_resources(body) parsed_response = parse_body(body) { data: parsed_response[root_node_plural.to_sym] || [], } end def parse_resource(body) { data: parse_body(body)[root_node_singular.to_sym], } end def parse_body(body) underscored(JSON.parse(body)) end def underscored(hash_or_array) if hash_or_array.is_a?(Hash) hash_or_array.reduce({}) do |memo, (k, v)| memo.merge(underscore(k).to_sym => underscored(v)) end elsif hash_or_array.is_a?(Array) hash_or_array.map { |hash| underscored(hash) } else hash_or_array end end def underscore(s) s.to_s.gsub(/::/, '/'). gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). gsub(/([a-z\d])([A-Z])/,'\1_\2'). tr("-", "_"). downcase end def handle_error(response) OpenRecycling.logger.error("#{response.code} #{response.message}") if response.code == "404" raise NotFoundError.new( code: response.code, message: response.message, uri: response.uri ) end raise ClientError.new( code: response.code, message: response.message, uri: response.uri, body: response.body ) end end end