1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
|
# frozen_string_literal: true
module HTTParty
# The default parser used by HTTParty, supports xml, json, html, csv and
# plain text.
#
# == Custom Parsers
#
# If you'd like to do your own custom parsing, subclassing HTTParty::Parser
# will make that process much easier. There are a few different ways you can
# utilize HTTParty::Parser as a superclass.
#
# @example Intercept the parsing for all formats
# class SimpleParser < HTTParty::Parser
# def parse
# perform_parsing
# end
# end
#
# @example Add the atom format and parsing method to the default parser
# class AtomParsingIncluded < HTTParty::Parser
# SupportedFormats.merge!(
# {"application/atom+xml" => :atom}
# )
#
# def atom
# perform_atom_parsing
# end
# end
#
# @example Only support the atom format
# class ParseOnlyAtom < HTTParty::Parser
# SupportedFormats = {"application/atom+xml" => :atom}
#
# def atom
# perform_atom_parsing
# end
# end
#
# @abstract Read the Custom Parsers section for more information.
class Parser
SupportedFormats = {
'text/xml' => :xml,
'application/xml' => :xml,
'application/json' => :json,
'application/vnd.api+json' => :json,
'application/hal+json' => :json,
'text/json' => :json,
'application/javascript' => :plain,
'text/javascript' => :plain,
'text/html' => :html,
'text/plain' => :plain,
'text/csv' => :csv,
'application/csv' => :csv,
'text/comma-separated-values' => :csv
}
# The response body of the request
# @return [String]
attr_reader :body
# The intended parsing format for the request
# @return [Symbol] e.g. :json
attr_reader :format
# Instantiate the parser and call {#parse}.
# @param [String] body the response body
# @param [Symbol] format the response format
# @return parsed response
def self.call(body, format)
new(body, format).parse
end
# @return [Hash] the SupportedFormats hash
def self.formats
const_get(:SupportedFormats)
end
# @param [String] mimetype response MIME type
# @return [Symbol]
# @return [nil] mime type not supported
def self.format_from_mimetype(mimetype)
formats[formats.keys.detect {|k| mimetype.include?(k)}]
end
# @return [Array<Symbol>] list of supported formats
def self.supported_formats
formats.values.uniq
end
# @param [Symbol] format e.g. :json, :xml
# @return [Boolean]
def self.supports_format?(format)
supported_formats.include?(format)
end
def initialize(body, format)
@body = body
@format = format
end
# @return [Object] the parsed body
# @return [nil] when the response body is nil, an empty string, spaces only or "null"
def parse
return nil if body.nil?
return nil if body == 'null'
return nil if body.valid_encoding? && body.strip.empty?
if body.valid_encoding? && body.encoding == Encoding::UTF_8
@body = body.gsub(/\A#{UTF8_BOM}/, '')
end
if supports_format?
parse_supported_format
else
body
end
end
protected
def xml
MultiXml.parse(body)
end
UTF8_BOM = "\xEF\xBB\xBF"
def json
JSON.parse(body, :quirks_mode => true, :allow_nan => true)
end
def csv
CSV.parse(body)
end
def html
body
end
def plain
body
end
def supports_format?
self.class.supports_format?(format)
end
def parse_supported_format
if respond_to?(format, true)
send(format)
else
raise NotImplementedError, "#{self.class.name} has not implemented a parsing method for the #{format.inspect} format."
end
end
end
end
|