require 'http' module Ecoportal module API module Common # @note # - You can see the documentation of the `HTTP` module in [the repository](https://github.com/httprb/http) # - it does `extend` the module `Chainable` ([chainable.rb](https://github.com/httprb/http/blob/master/lib/http/chainable.rb)), # - where all the http requests are dev by using `HTTP::Client#request` ([client.rb](https://github.com/httprb/http/blob/master/lib/http/client.rb)) # - which calls `build_request` (new `HTTP::Request`) and `perform` (new `HTTP::Connection`) # - to return `HTTP::Response` ([response.rb](https://github.com/httprb/http/blob/master/lib/http/response.rb)) # @attr_reader logger [Logger] the logger. class Client attr_accessor :logger # @note the `api_key` will be automatically added as parameter `X-ApiKey` in the header of the http requests. # @param api_key [String] the key version to stablish the api connection. # @param version [String] it is part of the base url and will determine the api version we query against. # @param host [String] api server domain. # @param logger [Logger] an object with `Logger` interface to generate logs. # @return [Client] an object that holds the configuration of the api connection. def initialize(api_key:, version: "v1", host: "live.ecoportal.com", logger: nil) @version = version @api_key = api_key @logger = logger if host.match(/^localhost|^127\.0\.0\.1/) @base_uri = "http://#{host}/api/" else @base_uri = "https://#{host}/api/" end log(:info) { "#{version} client initialized pointing at #{host}" } if @api_key.nil? || @api_key.match(/\A\W*\z/) log(:error) { "Api-key missing!" } end @response_logging_enabled = true end # Logger interface. # @example: # log(:info) {"General information on what's going on"} # log(:warn) {"This is a warning that something is likely to have gone amiss"} # log(:error) {"Something went wrong"} # log(:fatal) {"An unrecoverable error has happend"} # @param level [Symbol] the level that the message should be logged. # @yield [] generates the message. # @yieldreturn [String] the generated message. def log(level, &block) logger.send(level, &block) if logger end # Sends an http `GET` request against the api version using `path` to complete the base url, # and adding the key_value pairs of `params` in the http _header_. # @param path [String] the tail that completes the url of the request. # @param params [Hash] the header paramters of the http request (not including the api key). # @option params [String] :page the current page we are requesting with given the `:per_page` offset. # @option params [String] :per_page the offset or the number of entries you get per request. # @option params [String] :q some text to search. Omit this parameter to target all the entries. # @return [Common::Reponse] the basic custom response object. def get(path, params: {}) instrument("GET", path, params) do request do |http| http.get(url_for(path), params: params) end end end # Sends an http `POST` request against the api version using `path` to complete the base url, # and the `data` as a body of the http request. # @note it automatically adds the http header param `Content-Type` as `application/json` # @param path [String] the tail that completes the url of the request. # @param data [String] the body of the query in json format. # @return [Common::Reponse] the basic custom response object. def post(path, data:) instrument("POST", path, data) do request do |http| http.post(url_for(path), json: data) end end end # Sends an http `PATCH` request against the api version using `path` to complete the base url, # and the `data` as a body of the http request. # @note it automatically adds the http header param `Content-Type` as `application/json` # @param path [String] the tail that completes the url of the request. # @param data [String] the body of the query in json format. # @return [Common::Reponse] the basic custom response object. def patch(path, data:) instrument("PATCH", path, data) do request do |http| http.patch(url_for(path), json: data) end end end # Sends an http `DELETE` request against the api version using `path` to complete the base url. # @param path [String] the tail that completes the url of the request. # @return [Common::Reponse] the basic custom response object. def delete(path) instrument("DELETE", path) do request do |http| http.delete(url_for(path)) end end end # Allows to launch a different operation via `block`, providing the # basic HTTP connection to the block. # @yield [http] launch specific http request. # @yieldparam http [HTTP] the http connection. # @yieldreturn [Common::Response] the basic custom reponse object. # @return [Common::Reponse] the basic custom response object. def request wrap_response yield(base_request) end # Wrap with basic custom object of the gem for responses. # @param response [HTTP::Response] # @return [Common::Reponse] the basic custom response object. def wrap_response(response) Ecoportal::API::Common::Response.new(response) end # Creates a HTTP object adding the `X-ApiKey` param to the header. # @note It configures HTTP so it only allows body data in json format. # @return [HTTP] HTTP object. def base_request @base_request ||= HTTP.headers("X-ApiKey" => @api_key).accept(:json) end # Full URl builder of the request # @param path [String] the tail that completes the url of the request. # @return [String] the final url. def url_for(path) @base_uri+@version+path end def without_response_logging(&block) begin @response_logging_enabled = false yield self ensure @response_logging_enabled = true end end private def instrument(method, path, data = nil) start_time = Time.now.to_f log(:info) { "#{method} #{url_for(path)}" } log(:debug) { "Data: #{JSON.pretty_generate(data)}" } yield.tap do |result| end_time = Time.now.to_f log(result.success?? :info : :warn) do "Took %.2fs, Status #{result.status}" % (end_time - start_time) end log(result.success?? :debug : :warn) do "Response: #{JSON.pretty_generate(result.body)}" end if @response_logging_enabled end end end end end end