module Soaspec # Enables extracting a response according to type / path module ResponseExtractor # Convert XML or JSON response into a Hash. Doesn't accept empty body # @param [String] response Response as a String (either in XML or JSON) # @return [Hash] Extracted Hash from response def extract_hash(response) raise NoElementAtPath, "Empty Body. Can't assert on it" if response.body.empty? case Interpreter.response_type_for response when :xml then parse_xml(response.body.to_s) 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 else raise "Neither XML nor JSON detected. It is #{type}. Don't know how to parse It is #{response.body}" end end # @param [Object] response Response object # @return [Hash] Hash representing response body def to_hash(response) case Interpreter.response_type_for(response) when :xml then IndifferentHash.new(parse_xml(response.body.to_s)) when :json IndifferentHash.new(JSON.parse(response.body.to_s)) else raise "Unable to interpret type of '#{response.body}'. Could be because of: #{Interpreter.diagnose_error}" end end private # @param [String] xml XML to convert # @return [Hash] Hash representing XML def parse_xml(xml) parser = Nori.new(strip_namespaces: strip_namespaces?, convert_tags_to: ->(tag) { tag.snakecase.to_sym }) parser.parse(xml) end # This enables shortcut xpaths to be used. If no '/' is given, one is appended to the start of the path # If attribute value is set then this is also adjusted # @return [String] New Xpath adjusted according to any add ons def prefix_xpath(xpath, attribute) xpath = "//*[@#{attribute}]" unless attribute.nil? if xpath[0] != '/' xpath = convert_to_pascal_case(xpath) if pascal_keys? xpath = '//' + xpath end xpath end # Convert snakecase to PascalCase # @return [String] PascalCase converted path 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 # Find element with name specified or pascal case equivalent # @return [String] String to find paths with pascal converted or given path def add_pascal_path(json_path) return json_path unless pascal_keys? json_path.split(',').collect do |sub_path| "#{sub_path},#{convert_to_pascal_case(sub_path)}" end.join(',') end # @param [String] json_path Path set from Exchange # @return [String] JSON Path def prefix_json_path(json_path) return json_path if json_path[0] == '$' "$..#{json_path}" end end end