lib/soaspec/exchange_handlers/rest_handler.rb in soaspec-0.2.8 vs lib/soaspec/exchange_handlers/rest_handler.rb in soaspec-0.2.9

- old
+ new

@@ -4,18 +4,20 @@ require_relative 'rest_exchanger_factory' require_relative '../core_ext/hash' require_relative '../not_found_errors' require_relative 'handler_accessors' require_relative '../interpreter' +require_relative 'response_extractor' require 'json' require 'jsonpath' require 'nori' require 'erb' module Soaspec # Wraps around Savon client defining default values dependent on the soap request class RestHandler < ExchangeHandler + include ResponseExtractor extend Soaspec::RestParameters include Soaspec::RestParametersDefaults extend Soaspec::RestExchangeFactory # User used in making API calls @@ -23,10 +25,11 @@ # Setup object to handle communicating with a particular SOAP WSDL # @param [Hash] options Options defining REST request. base_url, default_hash def initialize(name = self.class.to_s, options = {}) raise "Base URL not set! Please set in class with 'base_url' method" unless base_url_value + if name.is_a?(Hash) && options == {} # If name is not set, use first parameter as the options hash options = name name = self.class.to_s end super @@ -85,30 +88,30 @@ # Perform ERB on each header value # @return [Hash] Hash from 'rest_client_headers' passed through ERB def parse_headers Hash[rest_client_headers.map do |header_name, header_value| raise ArgumentError, "Header '#{header_name}' is null. Headers are #{rest_client_headers}" if header_value.nil? + [header_name, ERB.new(header_value).result(binding)] end] end # Convert snakecase to PascalCase def convert_to_pascal_case(key) return key if /[[:upper:]]/ =~ key[0] # If first character already capital, don't do conversion + key.split('_').map(&:capitalize).join end # Initialize value of merged options # @return [Hash] Hash of merged options def init_merge_options options = rest_resource_options options.merge! basic_auth_params if respond_to? :basic_auth_params options[:headers] ||= {} options[:headers].merge! parse_headers - if Soaspec.auto_oauth && respond_to?(:access_token) - options[:headers][:authorization] ||= ERB.new('Bearer <%= access_token %>').result(binding) - end + options[:headers][:authorization] ||= ERB.new('Bearer <%= access_token %>').result(binding) if Soaspec.auto_oauth && respond_to?(:access_token) options.merge(@init_options) end # @param [Hash] format Format of expected result. # @return [Object] Generic body to be displayed in error messages @@ -141,20 +144,18 @@ response.code end # Returns the value at the provided xpath # @param [RestClient::Response] response - # @param [String] xpath + # @param [String] xpath Path to find elements from + # @param [String] attribute Attribute to find path for # @return [Enumerable] Value inside element found through Xpath def xpath_elements_for(response: nil, xpath: nil, attribute: nil) raise ArgumentError unless response && xpath raise "Can't perform XPATH if response is not XML" unless Interpreter.response_type_for(response) == :xml - xpath = "//*[@#{attribute}]" unless attribute.nil? - if xpath[0] != '/' - xpath = convert_to_pascal_case(xpath) if pascal_keys? - xpath = '//' + xpath - end + + xpath = prefix_xpath(xpath, attribute) temp_doc = Nokogiri.parse(response.body).dup if strip_namespaces? && !xpath.include?(':') temp_doc.remove_namespaces! temp_doc.xpath(xpath) else @@ -163,10 +164,11 @@ end # @return [Enumerable] List of values matching JSON path def json_path_values_for(response, path, attribute: nil) raise 'JSON does not support attributes' if attribute + if path[0] != '$' path = convert_to_pascal_case(path) if pascal_keys? path = '$..' + path end JsonPath.on(response.body, path) @@ -181,24 +183,27 @@ def value_from_path(response, path, attribute: nil) path = path.to_s case Interpreter.response_type_for(response) when :xml result = xpath_elements_for(response: response, xpath: path, attribute: attribute).first - raise NoElementAtPath, "No value at Xpath '#{path}'" unless result + raise NoElementAtPath, "No value at Xpath '#{prefix_xpath(path, attribute)}'" unless result return result.inner_text if attribute.nil? + return result.attributes[attribute].inner_text when :json paths_to_check = path.split(',') matching_values = paths_to_check.collect do |path_to_check| json_path_values_for(response, path_to_check, attribute: attribute) end.reject(&:empty?) raise NoElementAtPath, "Path '#{path}' not found in '#{response.body}'" if matching_values.empty? + matching_values.first.first when :hash response.dig(path.split('.')) # Use path as Hash dig expression separating params via '.' TODO: Unit test else raise NoElementAtPath, 'Response is empty' if response.to_s.empty? + response.to_s[/path/] # Perform regular expression using path if not XML nor JSON TODO: Unit test end end # @return [Enumerable] List of values returned from path @@ -212,45 +217,13 @@ else raise "Unable to interpret type of #{response.body}" end end - # TODO: This and 'to_hash' method should be merged - # Convert XML or JSON response into a Hash - # @param [String] response Response as a String (either in XML or JSON) - # @return [Hash] - def extract_hash(response) - raise NoElementAtPath, "Empty Body. Can't assert on it" if response.body.empty? - case Interpreter.response_type_for response - when :json - converted = JSON.parse(response.body) - return converted.transform_keys_to_symbols if converted.is_a? Hash - return converted.map!(&:transform_keys_to_symbols) if converted.is_a? Array - raise 'Incorrect Type produced ' + converted.class - when :xml - parser = Nori.new(convert_tags_to: lambda { |tag| tag.snakecase.to_sym }) - parser.parse(response.body) - else - raise "Neither XML nor JSON detected. It is #{type}. Don't know how to parse It is #{response.body}" - end - end - - # @return [Hash] Hash representing response body - def to_hash(response) - case Interpreter.response_type_for(response) - when :xml - parser = Nori.new(strip_namespaces: strip_namespaces?, convert_tags_to: ->(tag) { tag.snakecase.to_sym }) - parser.parse(response.body.to_s) - when :json - JSON.parse(response.body.to_s) - else - raise "Unable to interpret type of #{response.body}" - end - end - # @response [RestClient::Request] Request of API call. Either intended request or actual request def request(response) return 'Request not yet sent' if response.nil? + response.request end private