298 lines
7.0 KiB
Ruby
298 lines
7.0 KiB
Ruby
|
require 'thread'
|
||
|
Faraday.require_libs 'parameters'
|
||
|
|
||
|
module Faraday
|
||
|
module Utils
|
||
|
extend self
|
||
|
|
||
|
# Adapted from Rack::Utils::HeaderHash
|
||
|
class Headers < ::Hash
|
||
|
def self.from(value)
|
||
|
new(value)
|
||
|
end
|
||
|
|
||
|
def initialize(hash = nil)
|
||
|
super()
|
||
|
@names = {}
|
||
|
self.update(hash || {})
|
||
|
end
|
||
|
|
||
|
# need to synchronize concurrent writes to the shared KeyMap
|
||
|
keymap_mutex = Mutex.new
|
||
|
|
||
|
# symbol -> string mapper + cache
|
||
|
KeyMap = Hash.new do |map, key|
|
||
|
value = if key.respond_to?(:to_str)
|
||
|
key
|
||
|
else
|
||
|
key.to_s.split('_'). # :user_agent => %w(user agent)
|
||
|
each { |w| w.capitalize! }. # => %w(User Agent)
|
||
|
join('-') # => "User-Agent"
|
||
|
end
|
||
|
keymap_mutex.synchronize { map[key] = value }
|
||
|
end
|
||
|
KeyMap[:etag] = "ETag"
|
||
|
|
||
|
def [](k)
|
||
|
k = KeyMap[k]
|
||
|
super(k) || super(@names[k.downcase])
|
||
|
end
|
||
|
|
||
|
def []=(k, v)
|
||
|
k = KeyMap[k]
|
||
|
k = (@names[k.downcase] ||= k)
|
||
|
# join multiple values with a comma
|
||
|
v = v.to_ary.join(', ') if v.respond_to? :to_ary
|
||
|
super(k, v)
|
||
|
end
|
||
|
|
||
|
def fetch(k, *args, &block)
|
||
|
k = KeyMap[k]
|
||
|
key = @names.fetch(k.downcase, k)
|
||
|
super(key, *args, &block)
|
||
|
end
|
||
|
|
||
|
def delete(k)
|
||
|
k = KeyMap[k]
|
||
|
if k = @names[k.downcase]
|
||
|
@names.delete k.downcase
|
||
|
super(k)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def include?(k)
|
||
|
@names.include? k.downcase
|
||
|
end
|
||
|
|
||
|
alias_method :has_key?, :include?
|
||
|
alias_method :member?, :include?
|
||
|
alias_method :key?, :include?
|
||
|
|
||
|
def merge!(other)
|
||
|
other.each { |k, v| self[k] = v }
|
||
|
self
|
||
|
end
|
||
|
alias_method :update, :merge!
|
||
|
|
||
|
def merge(other)
|
||
|
hash = dup
|
||
|
hash.merge! other
|
||
|
end
|
||
|
|
||
|
def replace(other)
|
||
|
clear
|
||
|
self.update other
|
||
|
self
|
||
|
end
|
||
|
|
||
|
def to_hash() ::Hash.new.update(self) end
|
||
|
|
||
|
def parse(header_string)
|
||
|
return unless header_string && !header_string.empty?
|
||
|
header_string.split(/\r\n/).
|
||
|
tap { |a| a.shift if a.first.index('HTTP/') == 0 }. # drop the HTTP status line
|
||
|
map { |h| h.split(/:\s+/, 2) }.reject { |p| p[0].nil? }. # split key and value, ignore blank lines
|
||
|
each { |key, value|
|
||
|
# join multiple values with a comma
|
||
|
if self[key]
|
||
|
self[key] << ', ' << value
|
||
|
else
|
||
|
self[key] = value
|
||
|
end
|
||
|
}
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# hash with stringified keys
|
||
|
class ParamsHash < Hash
|
||
|
def [](key)
|
||
|
super(convert_key(key))
|
||
|
end
|
||
|
|
||
|
def []=(key, value)
|
||
|
super(convert_key(key), value)
|
||
|
end
|
||
|
|
||
|
def delete(key)
|
||
|
super(convert_key(key))
|
||
|
end
|
||
|
|
||
|
def include?(key)
|
||
|
super(convert_key(key))
|
||
|
end
|
||
|
|
||
|
alias_method :has_key?, :include?
|
||
|
alias_method :member?, :include?
|
||
|
alias_method :key?, :include?
|
||
|
|
||
|
def update(params)
|
||
|
params.each do |key, value|
|
||
|
self[key] = value
|
||
|
end
|
||
|
self
|
||
|
end
|
||
|
alias_method :merge!, :update
|
||
|
|
||
|
def merge(params)
|
||
|
dup.update(params)
|
||
|
end
|
||
|
|
||
|
def replace(other)
|
||
|
clear
|
||
|
update(other)
|
||
|
end
|
||
|
|
||
|
def merge_query(query, encoder = nil)
|
||
|
if query && !query.empty?
|
||
|
update((encoder || Utils.default_params_encoder).decode(query))
|
||
|
end
|
||
|
self
|
||
|
end
|
||
|
|
||
|
def to_query(encoder = nil)
|
||
|
(encoder || Utils.default_params_encoder).encode(self)
|
||
|
end
|
||
|
|
||
|
private
|
||
|
|
||
|
def convert_key(key)
|
||
|
key.to_s
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def build_query(params)
|
||
|
FlatParamsEncoder.encode(params)
|
||
|
end
|
||
|
|
||
|
def build_nested_query(params)
|
||
|
NestedParamsEncoder.encode(params)
|
||
|
end
|
||
|
|
||
|
ESCAPE_RE = /[^a-zA-Z0-9 .~_-]/
|
||
|
|
||
|
def escape(s)
|
||
|
s.to_s.gsub(ESCAPE_RE) {|match|
|
||
|
'%' + match.unpack('H2' * match.bytesize).join('%').upcase
|
||
|
}.tr(' ', '+')
|
||
|
end
|
||
|
|
||
|
def unescape(s) CGI.unescape s.to_s end
|
||
|
|
||
|
DEFAULT_SEP = /[&;] */n
|
||
|
|
||
|
# Adapted from Rack
|
||
|
def parse_query(query)
|
||
|
FlatParamsEncoder.decode(query)
|
||
|
end
|
||
|
|
||
|
def parse_nested_query(query)
|
||
|
NestedParamsEncoder.decode(query)
|
||
|
end
|
||
|
|
||
|
def default_params_encoder
|
||
|
@default_params_encoder ||= NestedParamsEncoder
|
||
|
end
|
||
|
|
||
|
class << self
|
||
|
attr_writer :default_params_encoder
|
||
|
end
|
||
|
|
||
|
# Stolen from Rack
|
||
|
def normalize_params(params, name, v = nil)
|
||
|
name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
|
||
|
k = $1 || ''
|
||
|
after = $' || ''
|
||
|
|
||
|
return if k.empty?
|
||
|
|
||
|
if after == ""
|
||
|
if params[k]
|
||
|
params[k] = Array[params[k]] unless params[k].kind_of?(Array)
|
||
|
params[k] << v
|
||
|
else
|
||
|
params[k] = v
|
||
|
end
|
||
|
elsif after == "[]"
|
||
|
params[k] ||= []
|
||
|
raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
|
||
|
params[k] << v
|
||
|
elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$)
|
||
|
child_key = $1
|
||
|
params[k] ||= []
|
||
|
raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
|
||
|
if params[k].last.is_a?(Hash) && !params[k].last.key?(child_key)
|
||
|
normalize_params(params[k].last, child_key, v)
|
||
|
else
|
||
|
params[k] << normalize_params({}, child_key, v)
|
||
|
end
|
||
|
else
|
||
|
params[k] ||= {}
|
||
|
raise TypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Hash)
|
||
|
params[k] = normalize_params(params[k], after, v)
|
||
|
end
|
||
|
|
||
|
return params
|
||
|
end
|
||
|
|
||
|
# Normalize URI() behavior across Ruby versions
|
||
|
#
|
||
|
# url - A String or URI.
|
||
|
#
|
||
|
# Returns a parsed URI.
|
||
|
def URI(url)
|
||
|
if url.respond_to?(:host)
|
||
|
url
|
||
|
elsif url.respond_to?(:to_str)
|
||
|
default_uri_parser.call(url)
|
||
|
else
|
||
|
raise ArgumentError, "bad argument (expected URI object or URI string)"
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def default_uri_parser
|
||
|
@default_uri_parser ||= begin
|
||
|
require 'uri'
|
||
|
Kernel.method(:URI)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def default_uri_parser=(parser)
|
||
|
@default_uri_parser = if parser.respond_to?(:call) || parser.nil?
|
||
|
parser
|
||
|
else
|
||
|
parser.method(:parse)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Receives a String or URI and returns just the path with the query string sorted.
|
||
|
def normalize_path(url)
|
||
|
url = URI(url)
|
||
|
(url.path.start_with?('/') ? url.path : '/' + url.path) +
|
||
|
(url.query ? "?#{sort_query_params(url.query)}" : "")
|
||
|
end
|
||
|
|
||
|
# Recursive hash update
|
||
|
def deep_merge!(target, hash)
|
||
|
hash.each do |key, value|
|
||
|
if Hash === value and Hash === target[key]
|
||
|
target[key] = deep_merge(target[key], value)
|
||
|
else
|
||
|
target[key] = value
|
||
|
end
|
||
|
end
|
||
|
target
|
||
|
end
|
||
|
|
||
|
# Recursive hash merge
|
||
|
def deep_merge(source, hash)
|
||
|
deep_merge!(source.dup, hash)
|
||
|
end
|
||
|
|
||
|
protected
|
||
|
|
||
|
def sort_query_params(query)
|
||
|
query.split('&').sort.join('&')
|
||
|
end
|
||
|
end
|
||
|
end
|