forked from lostisland/faraday
-
Notifications
You must be signed in to change notification settings - Fork 0
/
nested_params_encoder.rb
144 lines (129 loc) · 4.23 KB
/
nested_params_encoder.rb
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
module Faraday
# 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
extend Forwardable
def_delegators :'Faraday::Utils', :escape, :unescape
end
# @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 self.encode(params)
return nil if params == nil
if !params.is_a?(Array)
if !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.kind_of?(Symbol)
[key, value]
end
# Useful default for OAuth and caching.
# Only to be used for non-Array inputs. Arrays should preserve order.
params.sort!
end
# Helper lambda
to_query = lambda do |parent, value|
if value.is_a?(Hash)
value = value.map do |key, val|
key = escape(key)
[key, val]
end
value.sort!
buffer = ""
value.each do |key, val|
new_parent = "#{parent}%5B#{key}%5D"
buffer << "#{to_query.call(new_parent, val)}&"
end
return buffer.chop
elsif value.is_a?(Array)
new_parent = "#{parent}%5B%5D"
return new_parent if value.empty?
buffer = ""
value.each_with_index do |val, i|
buffer << "#{to_query.call(new_parent, val)}&"
end
return buffer.chop
elsif value.nil?
return parent
else
encoded_value = escape(value)
return "#{parent}=#{encoded_value}"
end
end
# The params have form [['key1', 'value1'], ['key2', 'value2']].
buffer = ''
params.each do |parent, value|
encoded_parent = escape(parent)
buffer << "#{to_query.call(encoded_parent, value)}&"
end
return buffer.chop
end
# @param query [nil, String]
#
# @return [Array<Array, String>] the decoded params
#
# @raise [TypeError] if the nesting is incorrect
def self.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.gsub(/\+/, ' ')) if value
subkeys = key.scan(/[^\[\]]+(?:\]?\[\])?/)
context = params
subkeys.each_with_index do |subkey, i|
is_array = subkey =~ /[\[\]]+\Z/
subkey = $` if is_array
last_subkey = i == subkeys.length - 1
if !last_subkey || is_array
value_type = is_array ? Array : Hash
if context[subkey] && !context[subkey].is_a?(value_type)
raise TypeError, "expected %s (got %s) for param `%s'" % [
value_type.name,
context[subkey].class.name,
subkey
]
end
context = (context[subkey] ||= value_type.new)
end
if context.is_a?(Array) && !is_array
if !context.last.is_a?(Hash) || context.last.has_key?(subkey)
context << {}
end
context = context.last
end
if last_subkey
if is_array
context << value
else
context[subkey] = value
end
end
end
end
dehash(params, 0)
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 self.dehash(hash, depth)
hash.each do |key, value|
hash[key] = dehash(value, depth + 1) if value.kind_of?(Hash)
end
if depth > 0 && !hash.empty? && hash.keys.all? { |k| k =~ /^\d+$/ }
hash.keys.sort.inject([]) { |all, key| all << hash[key] }
else
hash
end
end
end
end