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