2 Faraday.require_libs 'parameters'
8 # Adapted from Rack::Utils::HeaderHash
14 def initialize(hash = nil)
17 self.update(hash || {})
20 # need to synchronize concurrent writes to the shared KeyMap
21 keymap_mutex = Mutex.new
23 # symbol -> string mapper + cache
24 KeyMap = Hash.new do |map, key|
25 value = if key.respond_to?(:to_str)
28 key.to_s.split('_'). # :user_agent => %w(user agent)
29 each { |w| w.capitalize! }. # => %w(User Agent)
30 join('-') # => "User-Agent"
32 keymap_mutex.synchronize { map[key] = value }
34 KeyMap[:etag] = "ETag"
38 super(k) || super(@names[k.downcase])
43 k = (@names[k.downcase] ||= k)
44 # join multiple values with a comma
45 v = v.to_ary.join(', ') if v.respond_to? :to_ary
49 def fetch(k, *args, &block)
51 key = @names.fetch(k.downcase, k)
52 super(key, *args, &block)
57 if k = @names[k.downcase]
58 @names.delete k.downcase
64 @names.include? k.downcase
67 alias_method :has_key?, :include?
68 alias_method :member?, :include?
69 alias_method :key?, :include?
72 other.each { |k, v| self[k] = v }
75 alias_method :update, :merge!
88 def to_hash() ::Hash.new.update(self) end
90 def parse(header_string)
91 return unless header_string && !header_string.empty?
92 header_string.split(/\r\n/).
93 tap { |a| a.shift if a.first.index('HTTP/') == 0 }. # drop the HTTP status line
94 map { |h| h.split(/:\s+/, 2) }.reject { |p| p[0].nil? }. # split key and value, ignore blank lines
96 # join multiple values with a comma
98 self[key] << ', ' << value
106 # hash with stringified keys
107 class ParamsHash < Hash
109 super(convert_key(key))
113 super(convert_key(key), value)
117 super(convert_key(key))
121 super(convert_key(key))
124 alias_method :has_key?, :include?
125 alias_method :member?, :include?
126 alias_method :key?, :include?
129 params.each do |key, value|
134 alias_method :merge!, :update
145 def merge_query(query, encoder = nil)
146 if query && !query.empty?
147 update((encoder || Utils.default_params_encoder).decode(query))
152 def to_query(encoder = nil)
153 (encoder || Utils.default_params_encoder).encode(self)
163 def build_query(params)
164 FlatParamsEncoder.encode(params)
167 def build_nested_query(params)
168 NestedParamsEncoder.encode(params)
171 ESCAPE_RE = /[^a-zA-Z0-9 .~_-]/
174 s.to_s.gsub(ESCAPE_RE) {|match|
175 '%' + match.unpack('H2' * match.bytesize).join('%').upcase
179 def unescape(s) CGI.unescape s.to_s end
181 DEFAULT_SEP = /[&;] */n
184 def parse_query(query)
185 FlatParamsEncoder.decode(query)
188 def parse_nested_query(query)
189 NestedParamsEncoder.decode(query)
192 def default_params_encoder
193 @default_params_encoder ||= NestedParamsEncoder
197 attr_writer :default_params_encoder
201 def normalize_params(params, name, v = nil)
202 name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
210 params[k] = Array[params[k]] unless params[k].kind_of?(Array)
217 raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
219 elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$)
222 raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
223 if params[k].last.is_a?(Hash) && !params[k].last.key?(child_key)
224 normalize_params(params[k].last, child_key, v)
226 params[k] << normalize_params({}, child_key, v)
230 raise TypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Hash)
231 params[k] = normalize_params(params[k], after, v)
237 # Normalize URI() behavior across Ruby versions
239 # url - A String or URI.
241 # Returns a parsed URI.
243 if url.respond_to?(:host)
245 elsif url.respond_to?(:to_str)
246 default_uri_parser.call(url)
248 raise ArgumentError, "bad argument (expected URI object or URI string)"
252 def default_uri_parser
253 @default_uri_parser ||= begin
259 def default_uri_parser=(parser)
260 @default_uri_parser = if parser.respond_to?(:call) || parser.nil?
263 parser.method(:parse)
267 # Receives a String or URI and returns just the path with the query string sorted.
268 def normalize_path(url)
270 (url.path.start_with?('/') ? url.path : '/' + url.path) +
271 (url.query ? "?#{sort_query_params(url.query)}" : "")
274 # Recursive hash update
275 def deep_merge!(target, hash)
276 hash.each do |key, value|
277 if Hash === value and Hash === target[key]
278 target[key] = deep_merge(target[key], value)
286 # Recursive hash merge
287 def deep_merge(source, hash)
288 deep_merge!(source.dup, hash)
293 def sort_query_params(query)
294 query.split('&').sort.join('&')