rack 2.1.2 → 2.2.2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of rack might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +622 -1
- data/CONTRIBUTING.md +136 -0
- data/README.rdoc +83 -39
- data/Rakefile +14 -7
- data/{SPEC → SPEC.rdoc} +26 -1
- data/lib/rack.rb +7 -16
- data/lib/rack/auth/abstract/request.rb +0 -2
- data/lib/rack/auth/basic.rb +3 -3
- data/lib/rack/auth/digest/md5.rb +4 -4
- data/lib/rack/auth/digest/request.rb +3 -3
- data/lib/rack/body_proxy.rb +13 -9
- data/lib/rack/builder.rb +77 -8
- data/lib/rack/cascade.rb +23 -8
- data/lib/rack/chunked.rb +48 -23
- data/lib/rack/common_logger.rb +25 -18
- data/lib/rack/conditional_get.rb +18 -16
- data/lib/rack/content_length.rb +6 -7
- data/lib/rack/content_type.rb +3 -4
- data/lib/rack/deflater.rb +45 -35
- data/lib/rack/directory.rb +77 -60
- data/lib/rack/etag.rb +2 -3
- data/lib/rack/events.rb +15 -18
- data/lib/rack/file.rb +1 -1
- data/lib/rack/files.rb +96 -56
- data/lib/rack/handler/cgi.rb +1 -4
- data/lib/rack/handler/fastcgi.rb +1 -3
- data/lib/rack/handler/lsws.rb +1 -3
- data/lib/rack/handler/scgi.rb +1 -3
- data/lib/rack/handler/thin.rb +1 -3
- data/lib/rack/handler/webrick.rb +12 -5
- data/lib/rack/head.rb +0 -2
- data/lib/rack/lint.rb +57 -14
- data/lib/rack/lobster.rb +3 -5
- data/lib/rack/lock.rb +0 -1
- data/lib/rack/mock.rb +22 -4
- data/lib/rack/multipart.rb +1 -1
- data/lib/rack/multipart/generator.rb +11 -6
- data/lib/rack/multipart/parser.rb +7 -15
- data/lib/rack/multipart/uploaded_file.rb +13 -7
- data/lib/rack/query_parser.rb +7 -8
- data/lib/rack/recursive.rb +1 -1
- data/lib/rack/reloader.rb +1 -3
- data/lib/rack/request.rb +182 -76
- data/lib/rack/response.rb +62 -19
- data/lib/rack/rewindable_input.rb +0 -1
- data/lib/rack/runtime.rb +3 -3
- data/lib/rack/sendfile.rb +0 -3
- data/lib/rack/server.rb +9 -8
- data/lib/rack/session/abstract/id.rb +21 -18
- data/lib/rack/session/cookie.rb +1 -3
- data/lib/rack/session/pool.rb +1 -1
- data/lib/rack/show_exceptions.rb +6 -8
- data/lib/rack/show_status.rb +5 -7
- data/lib/rack/static.rb +13 -6
- data/lib/rack/tempfile_reaper.rb +0 -2
- data/lib/rack/urlmap.rb +1 -4
- data/lib/rack/utils.rb +58 -54
- data/lib/rack/version.rb +29 -0
- data/rack.gemspec +31 -29
- metadata +11 -12
data/lib/rack/head.rb
CHANGED
data/lib/rack/lint.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'rack/utils'
|
4
3
|
require 'forwardable'
|
5
4
|
|
6
5
|
module Rack
|
@@ -48,13 +47,24 @@ module Rack
|
|
48
47
|
env[RACK_ERRORS] = ErrorWrapper.new(env[RACK_ERRORS])
|
49
48
|
|
50
49
|
## and returns an Array of exactly three values:
|
51
|
-
|
50
|
+
ary = @app.call(env)
|
51
|
+
assert("response #{ary.inspect} is not an Array , but #{ary.class}") {
|
52
|
+
ary.kind_of? Array
|
53
|
+
}
|
54
|
+
assert("response array #{ary.inspect} has #{ary.size} elements instead of 3") {
|
55
|
+
ary.size == 3
|
56
|
+
}
|
57
|
+
|
58
|
+
status, headers, @body = ary
|
52
59
|
## The *status*,
|
53
60
|
check_status status
|
54
61
|
## the *headers*,
|
55
62
|
check_headers headers
|
56
63
|
|
57
|
-
check_hijack_response headers, env
|
64
|
+
hijack_proc = check_hijack_response headers, env
|
65
|
+
if hijack_proc && headers.is_a?(Hash)
|
66
|
+
headers[RACK_HIJACK] = hijack_proc
|
67
|
+
end
|
58
68
|
|
59
69
|
## and the *body*.
|
60
70
|
check_content_type status, headers
|
@@ -65,12 +75,15 @@ module Rack
|
|
65
75
|
|
66
76
|
## == The Environment
|
67
77
|
def check_env(env)
|
68
|
-
## The environment must be an instance of Hash that includes
|
78
|
+
## The environment must be an unfrozen instance of Hash that includes
|
69
79
|
## CGI-like headers. The application is free to modify the
|
70
80
|
## environment.
|
71
81
|
assert("env #{env.inspect} is not a Hash, but #{env.class}") {
|
72
82
|
env.kind_of? Hash
|
73
83
|
}
|
84
|
+
assert("env should not be frozen, but is") {
|
85
|
+
!env.frozen?
|
86
|
+
}
|
74
87
|
|
75
88
|
##
|
76
89
|
## The environment is required to include these variables
|
@@ -104,17 +117,19 @@ module Rack
|
|
104
117
|
## follows the <tt>?</tt>, if any. May be
|
105
118
|
## empty, but is always required!
|
106
119
|
|
107
|
-
## <tt>SERVER_NAME</tt
|
108
|
-
## When combined with <tt>SCRIPT_NAME</tt> and
|
120
|
+
## <tt>SERVER_NAME</tt>:: When combined with <tt>SCRIPT_NAME</tt> and
|
109
121
|
## <tt>PATH_INFO</tt>, these variables can be
|
110
122
|
## used to complete the URL. Note, however,
|
111
123
|
## that <tt>HTTP_HOST</tt>, if present,
|
112
124
|
## should be used in preference to
|
113
125
|
## <tt>SERVER_NAME</tt> for reconstructing
|
114
126
|
## the request URL.
|
115
|
-
## <tt>SERVER_NAME</tt>
|
116
|
-
##
|
117
|
-
|
127
|
+
## <tt>SERVER_NAME</tt> can never be an empty
|
128
|
+
## string, and so is always required.
|
129
|
+
|
130
|
+
## <tt>SERVER_PORT</tt>:: An optional +Integer+ which is the port the
|
131
|
+
## server is running on. Should be specified if
|
132
|
+
## the server is running on a non-standard port.
|
118
133
|
|
119
134
|
## <tt>HTTP_</tt> Variables:: Variables corresponding to the
|
120
135
|
## client-supplied HTTP request
|
@@ -198,6 +213,11 @@ module Rack
|
|
198
213
|
assert("session #{session.inspect} must respond to clear") {
|
199
214
|
session.respond_to?(:clear)
|
200
215
|
}
|
216
|
+
|
217
|
+
## to_hash (returning unfrozen Hash instance);
|
218
|
+
assert("session #{session.inspect} must respond to to_hash and return unfrozen Hash instance") {
|
219
|
+
session.respond_to?(:to_hash) && session.to_hash.kind_of?(Hash) && !session.to_hash.frozen?
|
220
|
+
}
|
201
221
|
end
|
202
222
|
|
203
223
|
## <tt>rack.logger</tt>:: A common object interface for logging messages.
|
@@ -253,13 +273,28 @@ module Rack
|
|
253
273
|
## accepted specifications and must not be used otherwise.
|
254
274
|
##
|
255
275
|
|
256
|
-
%w[REQUEST_METHOD SERVER_NAME
|
257
|
-
QUERY_STRING
|
276
|
+
%w[REQUEST_METHOD SERVER_NAME QUERY_STRING
|
258
277
|
rack.version rack.input rack.errors
|
259
278
|
rack.multithread rack.multiprocess rack.run_once].each { |header|
|
260
279
|
assert("env missing required key #{header}") { env.include? header }
|
261
280
|
}
|
262
281
|
|
282
|
+
## The <tt>SERVER_PORT</tt> must be an Integer if set.
|
283
|
+
assert("env[SERVER_PORT] is not an Integer") do
|
284
|
+
server_port = env["SERVER_PORT"]
|
285
|
+
server_port.nil? || (Integer(server_port) rescue false)
|
286
|
+
end
|
287
|
+
|
288
|
+
## The <tt>SERVER_NAME</tt> must be a valid authority as defined by RFC7540.
|
289
|
+
assert("#{env[SERVER_NAME]} must be a valid authority") do
|
290
|
+
URI.parse("http://#{env[SERVER_NAME]}/") rescue false
|
291
|
+
end
|
292
|
+
|
293
|
+
## The <tt>HTTP_HOST</tt> must be a valid authority as defined by RFC7540.
|
294
|
+
assert("#{env[HTTP_HOST]} must be a valid authority") do
|
295
|
+
URI.parse("http://#{env[HTTP_HOST]}/") rescue false
|
296
|
+
end
|
297
|
+
|
263
298
|
## The environment must not contain the keys
|
264
299
|
## <tt>HTTP_CONTENT_TYPE</tt> or <tt>HTTP_CONTENT_LENGTH</tt>
|
265
300
|
## (use the versions without <tt>HTTP_</tt>).
|
@@ -270,11 +305,17 @@ module Rack
|
|
270
305
|
}
|
271
306
|
|
272
307
|
## The CGI keys (named without a period) must have String values.
|
308
|
+
## If the string values for CGI keys contain non-ASCII characters,
|
309
|
+
## they should use ASCII-8BIT encoding.
|
273
310
|
env.each { |key, value|
|
274
311
|
next if key.include? "." # Skip extensions
|
275
312
|
assert("env variable #{key} has non-string value #{value.inspect}") {
|
276
313
|
value.kind_of? String
|
277
314
|
}
|
315
|
+
next if value.encoding == Encoding::ASCII_8BIT
|
316
|
+
assert("env variable #{key} has value containing non-ASCII characters and has non-ASCII-8BIT encoding #{value.inspect} encoding: #{value.encoding}") {
|
317
|
+
value.b !~ /[\x80-\xff]/n
|
318
|
+
}
|
278
319
|
}
|
279
320
|
|
280
321
|
## There are the following restrictions:
|
@@ -337,7 +378,7 @@ module Rack
|
|
337
378
|
## When applicable, its external encoding must be "ASCII-8BIT" and it
|
338
379
|
## must be opened in binary mode, for Ruby 1.9 compatibility.
|
339
380
|
assert("rack.input #{input} does not have ASCII-8BIT as its external encoding") {
|
340
|
-
input.external_encoding
|
381
|
+
input.external_encoding == Encoding::ASCII_8BIT
|
341
382
|
} if input.respond_to?(:external_encoding)
|
342
383
|
assert("rack.input #{input} is not opened in binary mode") {
|
343
384
|
input.binmode?
|
@@ -569,7 +610,7 @@ module Rack
|
|
569
610
|
|
570
611
|
# this check uses headers like a hash, but the spec only requires
|
571
612
|
# headers respond to #each
|
572
|
-
headers = Rack::Utils::HeaderHash
|
613
|
+
headers = Rack::Utils::HeaderHash[headers]
|
573
614
|
|
574
615
|
## In order to do this, an application may set the special header
|
575
616
|
## <tt>rack.hijack</tt> to an object that responds to <tt>call</tt>
|
@@ -593,7 +634,7 @@ module Rack
|
|
593
634
|
headers[RACK_HIJACK].respond_to? :call
|
594
635
|
}
|
595
636
|
original_hijack = headers[RACK_HIJACK]
|
596
|
-
|
637
|
+
proc do |io|
|
597
638
|
original_hijack.call HijackWrapper.new(io)
|
598
639
|
end
|
599
640
|
else
|
@@ -603,6 +644,8 @@ module Rack
|
|
603
644
|
assert('rack.hijack header must not be present if server does not support hijacking') {
|
604
645
|
headers[RACK_HIJACK].nil?
|
605
646
|
}
|
647
|
+
|
648
|
+
nil
|
606
649
|
end
|
607
650
|
end
|
608
651
|
## ==== Conventions
|
data/lib/rack/lobster.rb
CHANGED
@@ -2,9 +2,6 @@
|
|
2
2
|
|
3
3
|
require 'zlib'
|
4
4
|
|
5
|
-
require 'rack/request'
|
6
|
-
require 'rack/response'
|
7
|
-
|
8
5
|
module Rack
|
9
6
|
# Paste has a Pony, Rack has a Lobster!
|
10
7
|
class Lobster
|
@@ -64,9 +61,10 @@ module Rack
|
|
64
61
|
end
|
65
62
|
|
66
63
|
if $0 == __FILE__
|
67
|
-
|
68
|
-
|
64
|
+
# :nocov:
|
65
|
+
require_relative '../rack'
|
69
66
|
Rack::Server.start(
|
70
67
|
app: Rack::ShowExceptions.new(Rack::Lint.new(Rack::Lobster.new)), Port: 9292
|
71
68
|
)
|
69
|
+
# :nocov:
|
72
70
|
end
|
data/lib/rack/lock.rb
CHANGED
data/lib/rack/mock.rb
CHANGED
@@ -2,10 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'uri'
|
4
4
|
require 'stringio'
|
5
|
-
|
6
|
-
require 'rack/lint'
|
7
|
-
require 'rack/utils'
|
8
|
-
require 'rack/response'
|
5
|
+
require_relative '../rack'
|
9
6
|
require 'cgi/cookie'
|
10
7
|
|
11
8
|
module Rack
|
@@ -56,14 +53,24 @@ module Rack
|
|
56
53
|
@app = app
|
57
54
|
end
|
58
55
|
|
56
|
+
# Make a GET request and return a MockResponse. See #request.
|
59
57
|
def get(uri, opts = {}) request(GET, uri, opts) end
|
58
|
+
# Make a POST request and return a MockResponse. See #request.
|
60
59
|
def post(uri, opts = {}) request(POST, uri, opts) end
|
60
|
+
# Make a PUT request and return a MockResponse. See #request.
|
61
61
|
def put(uri, opts = {}) request(PUT, uri, opts) end
|
62
|
+
# Make a PATCH request and return a MockResponse. See #request.
|
62
63
|
def patch(uri, opts = {}) request(PATCH, uri, opts) end
|
64
|
+
# Make a DELETE request and return a MockResponse. See #request.
|
63
65
|
def delete(uri, opts = {}) request(DELETE, uri, opts) end
|
66
|
+
# Make a HEAD request and return a MockResponse. See #request.
|
64
67
|
def head(uri, opts = {}) request(HEAD, uri, opts) end
|
68
|
+
# Make an OPTIONS request and return a MockResponse. See #request.
|
65
69
|
def options(uri, opts = {}) request(OPTIONS, uri, opts) end
|
66
70
|
|
71
|
+
# Make a request using the given request method for the given
|
72
|
+
# uri to the rack application and return a MockResponse.
|
73
|
+
# Options given are passed to MockRequest.env_for.
|
67
74
|
def request(method = GET, uri = "", opts = {})
|
68
75
|
env = self.class.env_for(uri, opts.merge(method: method))
|
69
76
|
|
@@ -88,6 +95,13 @@ module Rack
|
|
88
95
|
end
|
89
96
|
|
90
97
|
# Return the Rack environment used for a request to +uri+.
|
98
|
+
# All options that are strings are added to the returned environment.
|
99
|
+
# Options:
|
100
|
+
# :fatal :: Whether to raise an exception if request outputs to rack.errors
|
101
|
+
# :input :: The rack.input to set
|
102
|
+
# :method :: The HTTP request method to use
|
103
|
+
# :params :: The params to use
|
104
|
+
# :script_name :: The SCRIPT_NAME to set
|
91
105
|
def self.env_for(uri = "", opts = {})
|
92
106
|
uri = parse_uri_rfc2396(uri)
|
93
107
|
uri.path = "/#{uri.path}" unless uri.path[0] == ?/
|
@@ -157,6 +171,10 @@ module Rack
|
|
157
171
|
# MockRequest.
|
158
172
|
|
159
173
|
class MockResponse < Rack::Response
|
174
|
+
class << self
|
175
|
+
alias [] new
|
176
|
+
end
|
177
|
+
|
160
178
|
# Headers
|
161
179
|
attr_reader :original_headers, :cookies
|
162
180
|
|
data/lib/rack/multipart.rb
CHANGED
@@ -17,9 +17,13 @@ module Rack
|
|
17
17
|
|
18
18
|
flattened_params.map do |name, file|
|
19
19
|
if file.respond_to?(:original_filename)
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
if file.path
|
21
|
+
::File.open(file.path, 'rb') do |f|
|
22
|
+
f.set_encoding(Encoding::BINARY)
|
23
|
+
content_for_tempfile(f, file, name)
|
24
|
+
end
|
25
|
+
else
|
26
|
+
content_for_tempfile(file, file, name)
|
23
27
|
end
|
24
28
|
else
|
25
29
|
content_for_other(file, name)
|
@@ -69,12 +73,13 @@ module Rack
|
|
69
73
|
end
|
70
74
|
|
71
75
|
def content_for_tempfile(io, file, name)
|
76
|
+
length = ::File.stat(file.path).size if file.path
|
77
|
+
filename = "; filename=\"#{Utils.escape(file.original_filename)}\"" if file.original_filename
|
72
78
|
<<-EOF
|
73
79
|
--#{MULTIPART_BOUNDARY}\r
|
74
|
-
Content-Disposition: form-data; name="#{name}"
|
80
|
+
Content-Disposition: form-data; name="#{name}"#{filename}\r
|
75
81
|
Content-Type: #{file.content_type}\r
|
76
|
-
Content-Length: #{
|
77
|
-
\r
|
82
|
+
#{"Content-Length: #{length}\r\n" if length}\r
|
78
83
|
#{io.read}\r
|
79
84
|
EOF
|
80
85
|
end
|
@@ -1,15 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'rack/utils'
|
4
3
|
require 'strscan'
|
5
|
-
require 'rack/core_ext/regexp'
|
6
4
|
|
7
5
|
module Rack
|
8
6
|
module Multipart
|
9
7
|
class MultipartPartLimitError < Errno::EMFILE; end
|
10
8
|
|
11
9
|
class Parser
|
12
|
-
using ::Rack::RegexpExtensions
|
10
|
+
(require_relative '../core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
|
13
11
|
|
14
12
|
BUFSIZE = 1_048_576
|
15
13
|
TEXT_PLAIN = "text/plain"
|
@@ -101,12 +99,6 @@ module Rack
|
|
101
99
|
|
102
100
|
data = { filename: fn, type: content_type,
|
103
101
|
name: name, tempfile: body, head: head }
|
104
|
-
elsif !filename && content_type && body.is_a?(IO)
|
105
|
-
body.rewind
|
106
|
-
|
107
|
-
# Generic multipart cases, not coming from a form
|
108
|
-
data = { type: content_type,
|
109
|
-
name: name, tempfile: body, head: head }
|
110
102
|
end
|
111
103
|
|
112
104
|
yield data
|
@@ -125,7 +117,7 @@ module Rack
|
|
125
117
|
|
126
118
|
include Enumerable
|
127
119
|
|
128
|
-
def initialize
|
120
|
+
def initialize(tempfile)
|
129
121
|
@tempfile = tempfile
|
130
122
|
@mime_parts = []
|
131
123
|
@open_files = 0
|
@@ -135,7 +127,7 @@ module Rack
|
|
135
127
|
@mime_parts.each { |part| yield part }
|
136
128
|
end
|
137
129
|
|
138
|
-
def on_mime_head
|
130
|
+
def on_mime_head(mime_index, head, filename, content_type, name)
|
139
131
|
if filename
|
140
132
|
body = @tempfile.call(filename, content_type)
|
141
133
|
body.binmode if body.respond_to?(:binmode)
|
@@ -151,11 +143,11 @@ module Rack
|
|
151
143
|
check_open_files
|
152
144
|
end
|
153
145
|
|
154
|
-
def on_mime_body
|
146
|
+
def on_mime_body(mime_index, content)
|
155
147
|
@mime_parts[mime_index].body << content
|
156
148
|
end
|
157
149
|
|
158
|
-
def on_mime_finish
|
150
|
+
def on_mime_finish(mime_index)
|
159
151
|
end
|
160
152
|
|
161
153
|
private
|
@@ -190,7 +182,7 @@ module Rack
|
|
190
182
|
@head_regex = /(.*?#{EOL})#{EOL}/m
|
191
183
|
end
|
192
184
|
|
193
|
-
def on_read
|
185
|
+
def on_read(content)
|
194
186
|
handle_empty_content!(content)
|
195
187
|
@sbuf.concat content
|
196
188
|
run_parser
|
@@ -347,7 +339,7 @@ module Rack
|
|
347
339
|
type_subtype = list.first
|
348
340
|
type_subtype.strip!
|
349
341
|
if TEXT_PLAIN == type_subtype
|
350
|
-
rest
|
342
|
+
rest = list.drop 1
|
351
343
|
rest.each do |param|
|
352
344
|
k, v = param.split('=', 2)
|
353
345
|
k.strip!
|
@@ -9,17 +9,23 @@ module Rack
|
|
9
9
|
# The content type of the "uploaded" file
|
10
10
|
attr_accessor :content_type
|
11
11
|
|
12
|
-
def initialize(
|
13
|
-
|
12
|
+
def initialize(filepath = nil, ct = "text/plain", bin = false,
|
13
|
+
path: filepath, content_type: ct, binary: bin, filename: nil, io: nil)
|
14
|
+
if io
|
15
|
+
@tempfile = io
|
16
|
+
@original_filename = filename
|
17
|
+
else
|
18
|
+
raise "#{path} file does not exist" unless ::File.exist?(path)
|
19
|
+
@original_filename = filename || ::File.basename(path)
|
20
|
+
@tempfile = Tempfile.new([@original_filename, ::File.extname(path)], encoding: Encoding::BINARY)
|
21
|
+
@tempfile.binmode if binary
|
22
|
+
FileUtils.copy_file(path, @tempfile.path)
|
23
|
+
end
|
14
24
|
@content_type = content_type
|
15
|
-
@original_filename = ::File.basename(path)
|
16
|
-
@tempfile = Tempfile.new([@original_filename, ::File.extname(path)], encoding: Encoding::BINARY)
|
17
|
-
@tempfile.binmode if binary
|
18
|
-
FileUtils.copy_file(path, @tempfile.path)
|
19
25
|
end
|
20
26
|
|
21
27
|
def path
|
22
|
-
@tempfile.path
|
28
|
+
@tempfile.path if @tempfile.respond_to?(:path)
|
23
29
|
end
|
24
30
|
alias_method :local_path, :path
|
25
31
|
|
data/lib/rack/query_parser.rb
CHANGED
@@ -1,10 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative 'core_ext/regexp'
|
4
|
-
|
5
3
|
module Rack
|
6
4
|
class QueryParser
|
7
|
-
using ::Rack::RegexpExtensions
|
5
|
+
(require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
|
8
6
|
|
9
7
|
DEFAULT_SEP = /[&;] */n
|
10
8
|
COMMON_SEP = { ";" => /[;] */n, ";," => /[;,] */n, "&" => /[&] */n }
|
@@ -64,18 +62,19 @@ module Rack
|
|
64
62
|
# ParameterTypeError is raised. Users are encouraged to return a 400 in this
|
65
63
|
# case.
|
66
64
|
def parse_nested_query(qs, d = nil)
|
67
|
-
return {} if qs.nil? || qs.empty?
|
68
65
|
params = make_params
|
69
66
|
|
70
|
-
qs.
|
71
|
-
|
67
|
+
unless qs.nil? || qs.empty?
|
68
|
+
(qs || '').split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
|
69
|
+
k, v = p.split('=', 2).map! { |s| unescape(s) }
|
72
70
|
|
73
|
-
|
71
|
+
normalize_params(params, k, v, param_depth_limit)
|
72
|
+
end
|
74
73
|
end
|
75
74
|
|
76
75
|
return params.to_h
|
77
76
|
rescue ArgumentError => e
|
78
|
-
raise InvalidParameterError, e.message
|
77
|
+
raise InvalidParameterError, e.message, e.backtrace
|
79
78
|
end
|
80
79
|
|
81
80
|
# normalize_params recursively expands parameters into structural types. If
|