require 'psych' require_relative 'client' module RestDSL ## # Base Class for defining a rest service DSL Object. # Will look in a config file contained in the same directory as the service defining its headers and/or creds # by environment. This file should be named such that it matches the service file name with a .yml ending. class ServiceBase def self.inherited(clazz) clazz.class_eval do @file_name = caller_locations[2].path @config_file = @file_name.gsub('.rb', '.yml') end end class << self attr_reader :environment, :file_name, :client, :config_file, :last_response attr_writer :headers, :authentication # Initializes the singleton def environment=(environment) @environment = environment @client = Client.new(environment) self end def service_name=(name) @service_name = name end def rest_call(method, name, url_schema) self.class.define_method("#{method}_#{name}") do |*args, headers: nil, payload: nil, params: nil, url_args: nil, **hash_args| execute_request(method, url_schema.dup, *args, **hash_args, headers: headers, payload: payload, params: params, url_args: url_args) end end # The method wrapped by the methods generated by rest_call, these methods all follow this blueprint # Can by wrapped manually to create more complicated logic than what's supported by the default generators def execute_request(method, rest_method_call, *args, headers: nil, payload: nil, params: nil, url_args: nil, **hash_args, &block) headers ||= self.headers params ||= {} url_args ||= {} params = "?#{params.map{|key,value| "#{key}=#{value}"}.join('&')}" service_name = "#{@service_name}/" unless @service_name&.empty? hash_args.merge!(auth) hash_args.merge!(payload: payload) if payload sub_url_args!(url_args, rest_method_call) arg_list = [method, "#{service_name}#{rest_method_call}#{params}", headers] response = @client.execute(*arg_list, *args, **hash_args, &block) @last_response = response[:response] response[:parsed] end def sub_url_args!(arg_list, rest_method_call) # Given the protocol is handled by the client and not service_base, this should be a safe enough pattern in most cases arg_list.each{ |arg_name, value| rest_method_call.gsub!(":#{arg_name}", value) } end def config @config || reload_config end def config_file=(file_name) @config_file = file_name reload_config end def reload_config @config = Psych.load_file(@config_file)[@environment] if File.exist?(@config_file) @config = {} unless File.exist?(@config_file) end def auth @authentication || config&.[](:credentials) || {} end def headers @headers ||= config&.[](:headers) || {} end end def service_name self.class.instance_variable_get(:@service_name) end end end