This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Faraday
|
||||
# FlatParamsEncoder manages URI params as a flat hash. Any Array values repeat
|
||||
# the parameter multiple times.
|
||||
module FlatParamsEncoder
|
||||
class << self
|
||||
extend Forwardable
|
||||
def_delegators :'Faraday::Utils', :escape, :unescape
|
||||
end
|
||||
|
||||
# Encode converts the given param into a URI querystring. Keys and values
|
||||
# will converted to strings and appropriately escaped for the URI.
|
||||
#
|
||||
# @param params [Hash] query arguments to convert.
|
||||
#
|
||||
# @example
|
||||
#
|
||||
# encode({a: %w[one two three], b: true, c: "C"})
|
||||
# # => 'a=one&a=two&a=three&b=true&c=C'
|
||||
#
|
||||
# @return [String] the URI querystring (without the leading '?')
|
||||
def self.encode(params)
|
||||
return nil if params.nil?
|
||||
|
||||
unless params.is_a?(Array)
|
||||
unless params.respond_to?(:to_hash)
|
||||
raise TypeError,
|
||||
"Can't convert #{params.class} into Hash."
|
||||
end
|
||||
params = params.to_hash
|
||||
params = params.map do |key, value|
|
||||
key = key.to_s if key.is_a?(Symbol)
|
||||
[key, value]
|
||||
end
|
||||
|
||||
# Only to be used for non-Array inputs. Arrays should preserve order.
|
||||
params.sort! if @sort_params
|
||||
end
|
||||
|
||||
# The params have form [['key1', 'value1'], ['key2', 'value2']].
|
||||
buffer = +''
|
||||
params.each do |key, value|
|
||||
encoded_key = escape(key)
|
||||
if value.nil?
|
||||
buffer << "#{encoded_key}&"
|
||||
elsif value.is_a?(Array)
|
||||
if value.empty?
|
||||
buffer << "#{encoded_key}=&"
|
||||
else
|
||||
value.each do |sub_value|
|
||||
encoded_value = escape(sub_value)
|
||||
buffer << "#{encoded_key}=#{encoded_value}&"
|
||||
end
|
||||
end
|
||||
else
|
||||
encoded_value = escape(value)
|
||||
buffer << "#{encoded_key}=#{encoded_value}&"
|
||||
end
|
||||
end
|
||||
buffer.chop
|
||||
end
|
||||
|
||||
# Decode converts the given URI querystring into a hash.
|
||||
#
|
||||
# @param query [String] query arguments to parse.
|
||||
#
|
||||
# @example
|
||||
#
|
||||
# decode('a=one&a=two&a=three&b=true&c=C')
|
||||
# # => {"a"=>["one", "two", "three"], "b"=>"true", "c"=>"C"}
|
||||
#
|
||||
# @return [Hash] parsed keys and value strings from the querystring.
|
||||
def self.decode(query)
|
||||
return nil if query.nil?
|
||||
|
||||
empty_accumulator = {}
|
||||
|
||||
split_query = (query.split('&').map do |pair|
|
||||
pair.split('=', 2) if pair && !pair.empty?
|
||||
end).compact
|
||||
split_query.each_with_object(empty_accumulator.dup) do |pair, accu|
|
||||
pair[0] = unescape(pair[0])
|
||||
pair[1] = true if pair[1].nil?
|
||||
if pair[1].respond_to?(:to_str)
|
||||
pair[1] = unescape(pair[1].to_str.tr('+', ' '))
|
||||
end
|
||||
if accu[pair[0]].is_a?(Array)
|
||||
accu[pair[0]] << pair[1]
|
||||
elsif accu[pair[0]]
|
||||
accu[pair[0]] = [accu[pair[0]], pair[1]]
|
||||
else
|
||||
accu[pair[0]] = pair[1]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class << self
|
||||
attr_accessor :sort_params
|
||||
end
|
||||
|
||||
# Useful default for OAuth and caching.
|
||||
@sort_params = true
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,183 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Faraday
|
||||
# Sub-module for encoding parameters into query-string.
|
||||
module EncodeMethods
|
||||
# @param params [nil, Array, #to_hash] parameters to be encoded
|
||||
#
|
||||
# @return [String] the encoded params
|
||||
#
|
||||
# @raise [TypeError] if params can not be converted to a Hash
|
||||
def encode(params)
|
||||
return nil if params.nil?
|
||||
|
||||
unless params.is_a?(Array)
|
||||
unless params.respond_to?(:to_hash)
|
||||
raise TypeError, "Can't convert #{params.class} into Hash."
|
||||
end
|
||||
|
||||
params = params.to_hash
|
||||
params = params.map do |key, value|
|
||||
key = key.to_s if key.is_a?(Symbol)
|
||||
[key, value]
|
||||
end
|
||||
|
||||
# Only to be used for non-Array inputs. Arrays should preserve order.
|
||||
params.sort! if @sort_params
|
||||
end
|
||||
|
||||
# The params have form [['key1', 'value1'], ['key2', 'value2']].
|
||||
buffer = +''
|
||||
params.each do |parent, value|
|
||||
encoded_parent = escape(parent)
|
||||
buffer << "#{encode_pair(encoded_parent, value)}&"
|
||||
end
|
||||
buffer.chop
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def encode_pair(parent, value)
|
||||
if value.is_a?(Hash)
|
||||
encode_hash(parent, value)
|
||||
elsif value.is_a?(Array)
|
||||
encode_array(parent, value)
|
||||
elsif value.nil?
|
||||
parent
|
||||
else
|
||||
encoded_value = escape(value)
|
||||
"#{parent}=#{encoded_value}"
|
||||
end
|
||||
end
|
||||
|
||||
def encode_hash(parent, value)
|
||||
value = value.map { |key, val| [escape(key), val] }.sort
|
||||
|
||||
buffer = +''
|
||||
value.each do |key, val|
|
||||
new_parent = "#{parent}%5B#{key}%5D"
|
||||
buffer << "#{encode_pair(new_parent, val)}&"
|
||||
end
|
||||
buffer.chop
|
||||
end
|
||||
|
||||
def encode_array(parent, value)
|
||||
return "#{parent}%5B%5D" if value.empty?
|
||||
|
||||
buffer = +''
|
||||
value.each_with_index do |val, index|
|
||||
new_parent = if @array_indices
|
||||
"#{parent}%5B#{index}%5D"
|
||||
else
|
||||
"#{parent}%5B%5D"
|
||||
end
|
||||
buffer << "#{encode_pair(new_parent, val)}&"
|
||||
end
|
||||
buffer.chop
|
||||
end
|
||||
end
|
||||
|
||||
# Sub-module for decoding query-string into parameters.
|
||||
module DecodeMethods
|
||||
# @param query [nil, String]
|
||||
#
|
||||
# @return [Array<Array, String>] the decoded params
|
||||
#
|
||||
# @raise [TypeError] if the nesting is incorrect
|
||||
def decode(query)
|
||||
return nil if query.nil?
|
||||
|
||||
params = {}
|
||||
query.split('&').each do |pair|
|
||||
next if pair.empty?
|
||||
|
||||
key, value = pair.split('=', 2)
|
||||
key = unescape(key)
|
||||
value = unescape(value.tr('+', ' ')) if value
|
||||
decode_pair(key, value, params)
|
||||
end
|
||||
|
||||
dehash(params, 0)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
SUBKEYS_REGEX = /[^\[\]]+(?:\]?\[\])?/
|
||||
|
||||
def decode_pair(key, value, context)
|
||||
subkeys = key.scan(SUBKEYS_REGEX)
|
||||
subkeys.each_with_index do |subkey, i|
|
||||
is_array = subkey =~ /[\[\]]+\Z/
|
||||
subkey = Regexp.last_match.pre_match if is_array
|
||||
last_subkey = i == subkeys.length - 1
|
||||
|
||||
context = prepare_context(context, subkey, is_array, last_subkey)
|
||||
add_to_context(is_array, context, value, subkey) if last_subkey
|
||||
end
|
||||
end
|
||||
|
||||
def prepare_context(context, subkey, is_array, last_subkey)
|
||||
if !last_subkey || is_array
|
||||
context = new_context(subkey, is_array, context)
|
||||
end
|
||||
if context.is_a?(Array) && !is_array
|
||||
context = match_context(context, subkey)
|
||||
end
|
||||
context
|
||||
end
|
||||
|
||||
def new_context(subkey, is_array, context)
|
||||
value_type = is_array ? Array : Hash
|
||||
if context[subkey] && !context[subkey].is_a?(value_type)
|
||||
raise TypeError, "expected #{value_type.name} " \
|
||||
"(got #{context[subkey].class.name}) for param `#{subkey}'"
|
||||
end
|
||||
|
||||
context[subkey] ||= value_type.new
|
||||
end
|
||||
|
||||
def match_context(context, subkey)
|
||||
context << {} if !context.last.is_a?(Hash) || context.last.key?(subkey)
|
||||
context.last
|
||||
end
|
||||
|
||||
def add_to_context(is_array, context, value, subkey)
|
||||
is_array ? context << value : context[subkey] = value
|
||||
end
|
||||
|
||||
# Internal: convert a nested hash with purely numeric keys into an array.
|
||||
# FIXME: this is not compatible with Rack::Utils.parse_nested_query
|
||||
# @!visibility private
|
||||
def dehash(hash, depth)
|
||||
hash.each do |key, value|
|
||||
hash[key] = dehash(value, depth + 1) if value.is_a?(Hash)
|
||||
end
|
||||
|
||||
if depth.positive? && !hash.empty? && hash.keys.all? { |k| k =~ /^\d+$/ }
|
||||
hash.sort.map(&:last)
|
||||
else
|
||||
hash
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# This is the default encoder for Faraday requests.
|
||||
# Using this encoder, parameters will be encoded respecting their structure,
|
||||
# so you can send objects such as Arrays or Hashes as parameters
|
||||
# for your requests.
|
||||
module NestedParamsEncoder
|
||||
class << self
|
||||
attr_accessor :sort_params, :array_indices
|
||||
|
||||
extend Forwardable
|
||||
def_delegators :'Faraday::Utils', :escape, :unescape
|
||||
end
|
||||
|
||||
# Useful default for OAuth and caching.
|
||||
@sort_params = true
|
||||
@array_indices = false
|
||||
|
||||
extend EncodeMethods
|
||||
extend DecodeMethods
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user