grape 0.17.0 → 0.18.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of grape might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -3
- data/Dangerfile +1 -80
- data/Gemfile +1 -1
- data/Gemfile.lock +69 -51
- data/README.md +42 -1
- data/UPGRADING.md +2 -2
- data/grape.gemspec +1 -1
- data/lib/grape/api.rb +5 -15
- data/lib/grape/cookies.rb +1 -1
- data/lib/grape/dsl/inside_route.rb +2 -3
- data/lib/grape/dsl/request_response.rb +1 -1
- data/lib/grape/endpoint.rb +19 -7
- data/lib/grape/error_formatter.rb +2 -2
- data/lib/grape/exceptions/base.rb +18 -18
- data/lib/grape/exceptions/validation.rb +6 -7
- data/lib/grape/exceptions/validation_errors.rb +5 -5
- data/lib/grape/formatter.rb +2 -2
- data/lib/grape/locale/en.yml +1 -0
- data/lib/grape/middleware/auth/base.rb +2 -2
- data/lib/grape/middleware/base.rb +2 -2
- data/lib/grape/middleware/error.rb +1 -1
- data/lib/grape/middleware/stack.rb +1 -1
- data/lib/grape/middleware/versioner/header.rb +1 -1
- data/lib/grape/namespace.rb +1 -1
- data/lib/grape/parser.rb +2 -2
- data/lib/grape/presenters/presenter.rb +1 -1
- data/lib/grape/router.rb +6 -6
- data/lib/grape/router/pattern.rb +7 -7
- data/lib/grape/router/route.rb +3 -3
- data/lib/grape/util/env.rb +1 -1
- data/lib/grape/validations/params_scope.rb +15 -7
- data/lib/grape/validations/validators/allow_blank.rb +0 -13
- data/lib/grape/validations/validators/base.rb +8 -1
- data/lib/grape/validations/validators/default.rb +1 -3
- data/lib/grape/validations/validators/presence.rb +0 -5
- data/lib/grape/validations/validators/values.rb +17 -3
- data/lib/grape/version.rb +1 -1
- data/pkg/grape-0.18.0.gem +0 -0
- data/spec/grape/api_spec.rb +41 -0
- data/spec/grape/exceptions/validation_spec.rb +1 -1
- data/spec/grape/middleware/stack_spec.rb +20 -0
- data/spec/grape/validations/params_scope_spec.rb +190 -58
- data/spec/grape/validations/validators/allow_blank_spec.rb +22 -7
- data/spec/grape/validations/validators/coerce_spec.rb +6 -6
- data/spec/grape/validations/validators/values_spec.rb +146 -0
- data/spec/grape/validations_spec.rb +28 -0
- metadata +6 -6
data/lib/grape/router/pattern.rb
CHANGED
@@ -14,9 +14,9 @@ module Grape
|
|
14
14
|
def_delegators :@regexp, :===
|
15
15
|
alias match? ===
|
16
16
|
|
17
|
-
def initialize(pattern, options
|
17
|
+
def initialize(pattern, **options)
|
18
18
|
@origin = pattern
|
19
|
-
@path = build_path(pattern, options)
|
19
|
+
@path = build_path(pattern, **options)
|
20
20
|
@capture = extract_capture(options)
|
21
21
|
@pattern = Mustermann.new(@path, pattern_options)
|
22
22
|
@regexp = to_regexp
|
@@ -34,13 +34,13 @@ module Grape
|
|
34
34
|
options
|
35
35
|
end
|
36
36
|
|
37
|
-
def build_path(pattern,
|
38
|
-
pattern << '*path' unless
|
39
|
-
pattern +
|
37
|
+
def build_path(pattern, anchor: false, suffix: nil, **_options)
|
38
|
+
pattern << '*path' unless anchor || pattern.end_with?('*path')
|
39
|
+
pattern + suffix.to_s
|
40
40
|
end
|
41
41
|
|
42
|
-
def extract_capture(
|
43
|
-
requirements = {}.merge(
|
42
|
+
def extract_capture(requirements: {}, **options)
|
43
|
+
requirements = {}.merge(requirements)
|
44
44
|
supported_capture.each_with_object(requirements) do |field, capture|
|
45
45
|
option = Array(options[field])
|
46
46
|
capture[field] = option.map(&:to_s) if option.present?
|
data/lib/grape/router/route.rb
CHANGED
@@ -57,11 +57,11 @@ module Grape
|
|
57
57
|
pattern.path
|
58
58
|
end
|
59
59
|
|
60
|
-
def initialize(method, pattern, options
|
60
|
+
def initialize(method, pattern, **options)
|
61
61
|
@suffix = options[:suffix]
|
62
62
|
@options = options.merge(method: method.to_s.upcase)
|
63
|
-
@pattern = Pattern.new(pattern, options)
|
64
|
-
@translator = AttributeTranslator.new(options
|
63
|
+
@pattern = Pattern.new(pattern, **options)
|
64
|
+
@translator = AttributeTranslator.new(**options, request_method: method.to_s.upcase)
|
65
65
|
end
|
66
66
|
|
67
67
|
def exec(env)
|
data/lib/grape/util/env.rb
CHANGED
@@ -18,6 +18,6 @@ module Grape
|
|
18
18
|
GRAPE_REQUEST_HEADERS = 'grape.request.headers'.freeze
|
19
19
|
GRAPE_REQUEST_PARAMS = 'grape.request.params'.freeze
|
20
20
|
GRAPE_ROUTING_ARGS = 'grape.routing_args'.freeze
|
21
|
-
|
21
|
+
GRAPE_ALLOWED_METHODS = 'grape.allowed_methods'.freeze
|
22
22
|
end
|
23
23
|
end
|
@@ -230,9 +230,13 @@ module Grape
|
|
230
230
|
full_attrs = attrs.collect { |name| { name: name, full_name: full_name(name) } }
|
231
231
|
@api.document_attribute(full_attrs, doc_attrs)
|
232
232
|
|
233
|
+
# slice out fail_fast attribute
|
234
|
+
opts = {}
|
235
|
+
opts[:fail_fast] = validations.delete(:fail_fast) || false
|
236
|
+
|
233
237
|
# Validate for presence before any other validators
|
234
238
|
if validations.key?(:presence) && validations[:presence]
|
235
|
-
validate('presence', validations[:presence], attrs, doc_attrs)
|
239
|
+
validate('presence', validations[:presence], attrs, doc_attrs, opts)
|
236
240
|
validations.delete(:presence)
|
237
241
|
validations.delete(:message) if validations.key?(:message)
|
238
242
|
end
|
@@ -240,10 +244,10 @@ module Grape
|
|
240
244
|
# Before we run the rest of the validators, let's handle
|
241
245
|
# whatever coercion so that we are working with correctly
|
242
246
|
# type casted values
|
243
|
-
coerce_type validations, attrs, doc_attrs
|
247
|
+
coerce_type validations, attrs, doc_attrs, opts
|
244
248
|
|
245
249
|
validations.each do |type, options|
|
246
|
-
validate(type, options, attrs, doc_attrs)
|
250
|
+
validate(type, options, attrs, doc_attrs, opts)
|
247
251
|
end
|
248
252
|
end
|
249
253
|
|
@@ -308,7 +312,7 @@ module Grape
|
|
308
312
|
# composited from more than one +requires+/+optional+
|
309
313
|
# parameter, and needs to be run before most other
|
310
314
|
# validations.
|
311
|
-
def coerce_type(validations, attrs, doc_attrs)
|
315
|
+
def coerce_type(validations, attrs, doc_attrs, opts)
|
312
316
|
check_coerce_with(validations)
|
313
317
|
|
314
318
|
return unless validations.key?(:coerce)
|
@@ -318,7 +322,7 @@ module Grape
|
|
318
322
|
method: validations[:coerce_with],
|
319
323
|
message: validations[:coerce_message]
|
320
324
|
}
|
321
|
-
validate('coerce', coerce_options, attrs, doc_attrs)
|
325
|
+
validate('coerce', coerce_options, attrs, doc_attrs, opts)
|
322
326
|
validations.delete(:coerce_with)
|
323
327
|
validations.delete(:coerce)
|
324
328
|
validations.delete(:coerce_message)
|
@@ -337,18 +341,22 @@ module Grape
|
|
337
341
|
raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :values, values)
|
338
342
|
end
|
339
343
|
|
340
|
-
def validate(type, options, attrs, doc_attrs)
|
344
|
+
def validate(type, options, attrs, doc_attrs, opts)
|
341
345
|
validator_class = Validations.validators[type.to_s]
|
342
346
|
|
343
347
|
raise Grape::Exceptions::UnknownValidator.new(type) unless validator_class
|
344
348
|
|
345
|
-
value = validator_class.new(attrs, options, doc_attrs[:required], self)
|
349
|
+
value = validator_class.new(attrs, options, doc_attrs[:required], self, opts)
|
346
350
|
@api.namespace_stackable(:validations, value)
|
347
351
|
end
|
348
352
|
|
349
353
|
def validate_value_coercion(coerce_type, values)
|
350
354
|
return unless coerce_type && values
|
351
355
|
return if values.is_a?(Proc)
|
356
|
+
if values.is_a?(Hash)
|
357
|
+
return if values[:value] && values[:value].is_a?(Proc)
|
358
|
+
return if values[:except] && values[:except].is_a?(Proc)
|
359
|
+
end
|
352
360
|
coerce_type = coerce_type.first if coerce_type.is_a?(Array)
|
353
361
|
value_types = values.is_a?(Range) ? [values.begin, values.end] : values
|
354
362
|
if coerce_type == Virtus::Attribute::Boolean
|
@@ -7,19 +7,6 @@ module Grape
|
|
7
7
|
value = params[attr_name]
|
8
8
|
value = value.strip if value.respond_to?(:strip)
|
9
9
|
|
10
|
-
key_exists = params.key?(attr_name)
|
11
|
-
|
12
|
-
should_validate = if @scope.root?
|
13
|
-
# root scope. validate if it's a required param. if it's optional, validate only if key exists in hash
|
14
|
-
@required || key_exists
|
15
|
-
else # nested scope
|
16
|
-
(@required && params.present?) ||
|
17
|
-
# optional param but key inside scoping element exists
|
18
|
-
(!@required && params.key?(attr_name))
|
19
|
-
end
|
20
|
-
|
21
|
-
return unless should_validate
|
22
|
-
|
23
10
|
return if false == value || value.present?
|
24
11
|
|
25
12
|
raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: message(:blank)
|
@@ -10,11 +10,13 @@ module Grape
|
|
10
10
|
# @param options [Object] implementation-dependent Validator options
|
11
11
|
# @param required [Boolean] attribute(s) are required or optional
|
12
12
|
# @param scope [ParamsScope] parent scope for this Validator
|
13
|
-
|
13
|
+
# @param opts [Hash] additional validation options
|
14
|
+
def initialize(attrs, options, required, scope, opts = {})
|
14
15
|
@attrs = Array(attrs)
|
15
16
|
@option = options
|
16
17
|
@required = required
|
17
18
|
@scope = scope
|
19
|
+
@fail_fast = opts[:fail_fast] || false
|
18
20
|
end
|
19
21
|
|
20
22
|
# Validates a given request.
|
@@ -24,6 +26,7 @@ module Grape
|
|
24
26
|
# @raise [Grape::Exceptions::Validation] if validation failed
|
25
27
|
# @return [void]
|
26
28
|
def validate(request)
|
29
|
+
return unless @scope.should_validate?(request.params)
|
27
30
|
validate!(request.params)
|
28
31
|
end
|
29
32
|
|
@@ -75,6 +78,10 @@ module Grape
|
|
75
78
|
options = instance_variable_get(:@option) if options.nil?
|
76
79
|
options.respond_to?(:key?) && options.key?(key) && !options[key].nil?
|
77
80
|
end
|
81
|
+
|
82
|
+
def fail_fast?
|
83
|
+
@fail_fast
|
84
|
+
end
|
78
85
|
end
|
79
86
|
end
|
80
87
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Grape
|
2
2
|
module Validations
|
3
3
|
class DefaultValidator < Base
|
4
|
-
def initialize(attrs, options, required, scope)
|
4
|
+
def initialize(attrs, options, required, scope, opts = {})
|
5
5
|
@default = options
|
6
6
|
super
|
7
7
|
end
|
@@ -18,8 +18,6 @@ module Grape
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def validate!(params)
|
21
|
-
return unless @scope.should_validate?(params)
|
22
|
-
|
23
21
|
attrs = AttributesIterator.new(self, @scope, params)
|
24
22
|
attrs.each do |resource_params, attr_name|
|
25
23
|
if resource_params.is_a?(Hash) && resource_params[attr_name].nil?
|
@@ -1,11 +1,6 @@
|
|
1
1
|
module Grape
|
2
2
|
module Validations
|
3
3
|
class PresenceValidator < Base
|
4
|
-
def validate!(params)
|
5
|
-
return unless @scope.should_validate?(params)
|
6
|
-
super
|
7
|
-
end
|
8
|
-
|
9
4
|
def validate_param!(attr_name, params)
|
10
5
|
return if params.respond_to?(:key?) && params.key?(attr_name)
|
11
6
|
raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: message(:presence)
|
@@ -1,8 +1,11 @@
|
|
1
1
|
module Grape
|
2
2
|
module Validations
|
3
3
|
class ValuesValidator < Base
|
4
|
-
def initialize(attrs, options, required, scope)
|
5
|
-
@
|
4
|
+
def initialize(attrs, options, required, scope, opts = {})
|
5
|
+
@excepts = (options_key?(:except, options) ? options[:except] : [])
|
6
|
+
@values = (options_key?(:value, options) ? options[:value] : [])
|
7
|
+
|
8
|
+
@values = options if @excepts == [] && @values == []
|
6
9
|
super
|
7
10
|
end
|
8
11
|
|
@@ -11,13 +14,24 @@ module Grape
|
|
11
14
|
return unless params[attr_name] || required_for_root_scope?
|
12
15
|
|
13
16
|
values = @values.is_a?(Proc) ? @values.call : @values
|
17
|
+
excepts = @excepts.is_a?(Proc) ? @excepts.call : @excepts
|
14
18
|
param_array = params[attr_name].nil? ? [nil] : Array.wrap(params[attr_name])
|
15
|
-
|
19
|
+
|
20
|
+
if param_array.all? { |param| excepts.include?(param) }
|
21
|
+
raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: except_message
|
22
|
+
end
|
23
|
+
|
24
|
+
return if (values.is_a?(Array) && values.empty?) || param_array.all? { |param| values.include?(param) }
|
16
25
|
raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: message(:values)
|
17
26
|
end
|
18
27
|
|
19
28
|
private
|
20
29
|
|
30
|
+
def except_message
|
31
|
+
options = instance_variable_get(:@option)
|
32
|
+
options_key?(:except_message) ? options[:except_message] : message(:except)
|
33
|
+
end
|
34
|
+
|
21
35
|
def required_for_root_scope?
|
22
36
|
@required && @scope.root?
|
23
37
|
end
|
data/lib/grape/version.rb
CHANGED
Binary file
|
data/spec/grape/api_spec.rb
CHANGED
@@ -665,6 +665,47 @@ XML
|
|
665
665
|
end
|
666
666
|
end
|
667
667
|
|
668
|
+
describe 'adds an OPTIONS route for namespaced endpoints that' do
|
669
|
+
before do
|
670
|
+
subject.before { header 'X-Custom-Header', 'foo' }
|
671
|
+
subject.namespace :example do
|
672
|
+
before { header 'X-Custom-Header-2', 'foo' }
|
673
|
+
get :inner do
|
674
|
+
'example/inner'
|
675
|
+
end
|
676
|
+
end
|
677
|
+
options '/example/inner'
|
678
|
+
end
|
679
|
+
|
680
|
+
it 'returns a 204' do
|
681
|
+
expect(last_response.status).to eql 204
|
682
|
+
end
|
683
|
+
|
684
|
+
it 'has an empty body' do
|
685
|
+
expect(last_response.body).to be_blank
|
686
|
+
end
|
687
|
+
|
688
|
+
it 'has an Allow header' do
|
689
|
+
expect(last_response.headers['Allow']).to eql 'OPTIONS, GET, HEAD'
|
690
|
+
end
|
691
|
+
|
692
|
+
it 'calls the outer before filter' do
|
693
|
+
expect(last_response.headers['X-Custom-Header']).to eql 'foo'
|
694
|
+
end
|
695
|
+
|
696
|
+
it 'calls the inner before filter' do
|
697
|
+
expect(last_response.headers['X-Custom-Header-2']).to eql 'foo'
|
698
|
+
end
|
699
|
+
|
700
|
+
it 'has no Content-Type' do
|
701
|
+
expect(last_response.content_type).to be_nil
|
702
|
+
end
|
703
|
+
|
704
|
+
it 'has no Content-Length' do
|
705
|
+
expect(last_response.content_length).to be_nil
|
706
|
+
end
|
707
|
+
end
|
708
|
+
|
668
709
|
describe 'adds a 405 Not Allowed route that' do
|
669
710
|
before do
|
670
711
|
subject.before { header 'X-Custom-Header', 'foo' }
|
@@ -2,7 +2,7 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Grape::Exceptions::Validation do
|
4
4
|
it 'fails when params are missing' do
|
5
|
-
expect { Grape::Exceptions::Validation.new(message: 'presence') }.to raise_error(
|
5
|
+
expect { Grape::Exceptions::Validation.new(message: 'presence') }.to raise_error(ArgumentError, 'missing keyword: params')
|
6
6
|
end
|
7
7
|
context 'when message is a symbol' do
|
8
8
|
it 'stores message_key' do
|
@@ -61,6 +61,16 @@ describe Grape::Middleware::Stack do
|
|
61
61
|
expect(subject[1]).to eq(StackSpec::FooMiddleware)
|
62
62
|
end
|
63
63
|
|
64
|
+
it 'inserts a middleware before an anonymous class given by its superclass' do
|
65
|
+
subject.use Class.new(StackSpec::BlockMiddleware)
|
66
|
+
|
67
|
+
expect { subject.insert_before StackSpec::BlockMiddleware, StackSpec::BarMiddleware }
|
68
|
+
.to change { subject.size }.by(1)
|
69
|
+
|
70
|
+
expect(subject[1]).to eq(StackSpec::BarMiddleware)
|
71
|
+
expect(subject[2]).to eq(StackSpec::BlockMiddleware)
|
72
|
+
end
|
73
|
+
|
64
74
|
it 'raises an error on an invalid index' do
|
65
75
|
expect { subject.insert_before StackSpec::BlockMiddleware, StackSpec::BarMiddleware }
|
66
76
|
.to raise_error(RuntimeError, 'No such middleware to insert before: StackSpec::BlockMiddleware')
|
@@ -75,6 +85,16 @@ describe Grape::Middleware::Stack do
|
|
75
85
|
expect(subject[0]).to eq(StackSpec::FooMiddleware)
|
76
86
|
end
|
77
87
|
|
88
|
+
it 'inserts a middleware after an anonymous class given by its superclass' do
|
89
|
+
subject.use Class.new(StackSpec::BlockMiddleware)
|
90
|
+
|
91
|
+
expect { subject.insert_after StackSpec::BlockMiddleware, StackSpec::BarMiddleware }
|
92
|
+
.to change { subject.size }.by(1)
|
93
|
+
|
94
|
+
expect(subject[1]).to eq(StackSpec::BlockMiddleware)
|
95
|
+
expect(subject[2]).to eq(StackSpec::BarMiddleware)
|
96
|
+
end
|
97
|
+
|
78
98
|
it 'raises an error on an invalid index' do
|
79
99
|
expect { subject.insert_after StackSpec::BlockMiddleware, StackSpec::BarMiddleware }
|
80
100
|
.to raise_error(RuntimeError, 'No such middleware to insert after: StackSpec::BlockMiddleware')
|
@@ -157,6 +157,35 @@ describe Grape::Validations::ParamsScope do
|
|
157
157
|
end
|
158
158
|
end
|
159
159
|
|
160
|
+
context 'coercing values validation with proc' do
|
161
|
+
it 'allows the proc to pass validation without checking' do
|
162
|
+
subject.params { requires :numbers, type: Integer, values: -> { [0, 1, 2] } }
|
163
|
+
|
164
|
+
subject.post('/required') { 'coercion with proc works' }
|
165
|
+
post '/required', numbers: '1'
|
166
|
+
expect(last_response.status).to eq(201)
|
167
|
+
expect(last_response.body).to eq('coercion with proc works')
|
168
|
+
end
|
169
|
+
|
170
|
+
it 'allows the proc to pass validation without checking in value' do
|
171
|
+
subject.params { requires :numbers, type: Integer, values: { value: -> { [0, 1, 2] } } }
|
172
|
+
|
173
|
+
subject.post('/required') { 'coercion with proc works' }
|
174
|
+
post '/required', numbers: '1'
|
175
|
+
expect(last_response.status).to eq(201)
|
176
|
+
expect(last_response.body).to eq('coercion with proc works')
|
177
|
+
end
|
178
|
+
|
179
|
+
it 'allows the proc to pass validation without checking in except' do
|
180
|
+
subject.params { requires :numbers, type: Integer, values: { except: -> { [0, 1, 2] } } }
|
181
|
+
|
182
|
+
subject.post('/required') { 'coercion with proc works' }
|
183
|
+
post '/required', numbers: '10'
|
184
|
+
expect(last_response.status).to eq(201)
|
185
|
+
expect(last_response.body).to eq('coercion with proc works')
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
160
189
|
context 'with range values' do
|
161
190
|
context "when left range endpoint isn't #kind_of? the type" do
|
162
191
|
it 'raises exception' do
|
@@ -342,6 +371,44 @@ describe Grape::Validations::ParamsScope do
|
|
342
371
|
expect(last_response.status).to eq(200)
|
343
372
|
end
|
344
373
|
|
374
|
+
it 'applies only the appropriate validation' do
|
375
|
+
subject.params do
|
376
|
+
optional :a
|
377
|
+
optional :b
|
378
|
+
mutually_exclusive :a, :b
|
379
|
+
given :a do
|
380
|
+
requires :c, type: String
|
381
|
+
end
|
382
|
+
given :b do
|
383
|
+
requires :c, type: Integer
|
384
|
+
end
|
385
|
+
end
|
386
|
+
subject.get('/multiple') { declared(params).to_json }
|
387
|
+
|
388
|
+
get '/multiple'
|
389
|
+
expect(last_response.status).to eq(200)
|
390
|
+
|
391
|
+
get '/multiple', a: true, c: 'test'
|
392
|
+
expect(last_response.status).to eq(200)
|
393
|
+
expect(JSON.parse(last_response.body).symbolize_keys).to eq a: 'true', b: nil, c: 'test'
|
394
|
+
|
395
|
+
get '/multiple', b: true, c: '3'
|
396
|
+
expect(last_response.status).to eq(200)
|
397
|
+
expect(JSON.parse(last_response.body).symbolize_keys).to eq a: nil, b: 'true', c: 3
|
398
|
+
|
399
|
+
get '/multiple', a: true
|
400
|
+
expect(last_response.status).to eq(400)
|
401
|
+
expect(last_response.body).to eq('c is missing')
|
402
|
+
|
403
|
+
get '/multiple', b: true
|
404
|
+
expect(last_response.status).to eq(400)
|
405
|
+
expect(last_response.body).to eq('c is missing')
|
406
|
+
|
407
|
+
get '/multiple', a: true, b: true, c: 'test'
|
408
|
+
expect(last_response.status).to eq(400)
|
409
|
+
expect(last_response.body).to eq('a, b are mutually exclusive, c is invalid')
|
410
|
+
end
|
411
|
+
|
345
412
|
it 'raises an error if the dependent parameter was never specified' do
|
346
413
|
expect do
|
347
414
|
subject.params do
|
@@ -407,89 +474,154 @@ describe Grape::Validations::ParamsScope do
|
|
407
474
|
end
|
408
475
|
|
409
476
|
context 'when validations are dependent on a parameter with specific value' do
|
410
|
-
|
477
|
+
# build test cases from all combinations of declarations and options
|
478
|
+
a_decls = %i(optional requires)
|
479
|
+
a_options = [{}, { values: %w(x y z) }]
|
480
|
+
b_options = [{}, { type: String }, { allow_blank: false }, { type: String, allow_blank: false }]
|
481
|
+
combinations = a_decls.product(a_options, b_options)
|
482
|
+
combinations.each_with_index do |combination, i|
|
483
|
+
a_decl, a_opts, b_opts = combination
|
484
|
+
|
485
|
+
context "(case #{i})" do
|
486
|
+
before do
|
487
|
+
# puts "a_decl: #{a_decl}, a_opts: #{a_opts}, b_opts: #{b_opts}"
|
488
|
+
subject.params do
|
489
|
+
send a_decl, :a, **a_opts
|
490
|
+
given(a: ->(val) { val == 'x' }) { requires :b, **b_opts }
|
491
|
+
given(a: ->(val) { val == 'y' }) { requires :c, **b_opts }
|
492
|
+
end
|
493
|
+
subject.get('/test') { declared(params).to_json }
|
494
|
+
end
|
495
|
+
|
496
|
+
if a_decl == :optional
|
497
|
+
it 'skips validation when base param is missing' do
|
498
|
+
get '/test'
|
499
|
+
expect(last_response.status).to eq(200)
|
500
|
+
end
|
501
|
+
end
|
502
|
+
|
503
|
+
it 'skips validation when base param does not have a specified value' do
|
504
|
+
get '/test', a: 'z'
|
505
|
+
expect(last_response.status).to eq(200)
|
506
|
+
|
507
|
+
get '/test', a: 'z', b: ''
|
508
|
+
expect(last_response.status).to eq(200)
|
509
|
+
end
|
510
|
+
|
511
|
+
it 'applies the validation when base param has the specific value' do
|
512
|
+
get '/test', a: 'x'
|
513
|
+
expect(last_response.status).to eq(400)
|
514
|
+
expect(last_response.body).to include('b is missing')
|
515
|
+
|
516
|
+
get '/test', a: 'x', b: true
|
517
|
+
expect(last_response.status).to eq(200)
|
518
|
+
|
519
|
+
get '/test', a: 'x', b: true, c: ''
|
520
|
+
expect(last_response.status).to eq(200)
|
521
|
+
end
|
522
|
+
|
523
|
+
it 'includes the parameter within #declared(params)' do
|
524
|
+
get '/test', a: 'x', b: true
|
525
|
+
expect(JSON.parse(last_response.body)).to eq('a' => 'x', 'b' => 'true', 'c' => nil)
|
526
|
+
end
|
527
|
+
end
|
528
|
+
end
|
529
|
+
end
|
530
|
+
|
531
|
+
it 'raises an error if the dependent parameter was never specified' do
|
532
|
+
expect do
|
411
533
|
subject.params do
|
534
|
+
given :c do
|
535
|
+
end
|
536
|
+
end
|
537
|
+
end.to raise_error(Grape::Exceptions::UnknownParameter)
|
538
|
+
end
|
539
|
+
|
540
|
+
it 'returns a sensible error message within a nested context' do
|
541
|
+
subject.params do
|
542
|
+
requires :bar, type: Hash do
|
412
543
|
optional :a
|
413
544
|
given a: ->(val) { val == 'x' } do
|
414
545
|
requires :b
|
415
546
|
end
|
416
547
|
end
|
417
|
-
subject.get('/test') { declared(params).to_json }
|
418
548
|
end
|
549
|
+
subject.get('/nested') { 'worked' }
|
419
550
|
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
get '/test', a: 'x'
|
425
|
-
expect(last_response.status).to eq(400)
|
426
|
-
expect(last_response.body).to eq('b is missing')
|
427
|
-
|
428
|
-
get '/test', a: 'x', b: true
|
429
|
-
expect(last_response.status).to eq(200)
|
430
|
-
end
|
551
|
+
get '/nested', bar: { a: 'x' }
|
552
|
+
expect(last_response.status).to eq(400)
|
553
|
+
expect(last_response.body).to eq('bar[b] is missing')
|
554
|
+
end
|
431
555
|
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
556
|
+
it 'includes the nested parameter within #declared(params)' do
|
557
|
+
subject.params do
|
558
|
+
requires :bar, type: Hash do
|
559
|
+
optional :a
|
560
|
+
given a: ->(val) { val == 'x' } do
|
561
|
+
requires :b
|
437
562
|
end
|
438
|
-
end
|
563
|
+
end
|
439
564
|
end
|
565
|
+
subject.get('/nested') { declared(params).to_json }
|
440
566
|
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
expect(JSON.parse(last_response.body)).to eq('a' => 'true', 'b' => 'true')
|
445
|
-
end
|
567
|
+
get '/nested', bar: { a: 'x', b: 'yes' }
|
568
|
+
expect(JSON.parse(last_response.body)).to eq('bar' => { 'a' => 'x', 'b' => 'yes' })
|
569
|
+
end
|
446
570
|
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
571
|
+
it 'includes level 2 nested parameters outside the given within #declared(params)' do
|
572
|
+
subject.params do
|
573
|
+
requires :bar, type: Hash do
|
574
|
+
optional :a
|
575
|
+
given a: ->(val) { val == 'x' } do
|
576
|
+
requires :c, type: Hash do
|
452
577
|
requires :b
|
453
578
|
end
|
454
579
|
end
|
455
580
|
end
|
456
|
-
subject.get('/nested') { 'worked' }
|
457
|
-
|
458
|
-
get '/nested', bar: { a: 'x' }
|
459
|
-
expect(last_response.status).to eq(400)
|
460
|
-
expect(last_response.body).to eq('bar[b] is missing')
|
461
581
|
end
|
582
|
+
subject.get('/nested') { declared(params).to_json }
|
462
583
|
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
584
|
+
get '/nested', bar: { a: 'x', c: { b: 'yes' } }
|
585
|
+
expect(JSON.parse(last_response.body)).to eq('bar' => { 'a' => 'x', 'c' => { 'b' => 'yes' } })
|
586
|
+
end
|
587
|
+
|
588
|
+
context 'failing fast' do
|
589
|
+
context 'when fail_fast is not defined' do
|
590
|
+
it 'does not stop validation' do
|
591
|
+
subject.params do
|
592
|
+
requires :one
|
593
|
+
requires :two
|
594
|
+
requires :three
|
470
595
|
end
|
471
|
-
|
472
|
-
subject.get('/nested') { declared(params).to_json }
|
596
|
+
subject.get('/fail-fast') { declared(params).to_json }
|
473
597
|
|
474
|
-
|
475
|
-
|
598
|
+
get '/fail-fast'
|
599
|
+
expect(last_response.status).to eq(400)
|
600
|
+
expect(last_response.body).to eq('one is missing, two is missing, three is missing')
|
601
|
+
end
|
476
602
|
end
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
given a: ->(val) { val == 'x' } do
|
483
|
-
requires :c, type: Hash do
|
484
|
-
requires :b
|
485
|
-
end
|
486
|
-
end
|
603
|
+
context 'when fail_fast is defined it stops the validation' do
|
604
|
+
it 'of other params' do
|
605
|
+
subject.params do
|
606
|
+
requires :one, fail_fast: true
|
607
|
+
requires :two
|
487
608
|
end
|
609
|
+
subject.get('/fail-fast') { declared(params).to_json }
|
610
|
+
|
611
|
+
get '/fail-fast'
|
612
|
+
expect(last_response.status).to eq(400)
|
613
|
+
expect(last_response.body).to eq('one is missing')
|
488
614
|
end
|
489
|
-
|
615
|
+
it 'for a single param' do
|
616
|
+
subject.params do
|
617
|
+
requires :one, allow_blank: false, regexp: /[0-9]+/, fail_fast: true
|
618
|
+
end
|
619
|
+
subject.get('/fail-fast') { declared(params).to_json }
|
490
620
|
|
491
|
-
|
492
|
-
|
621
|
+
get '/fail-fast', one: ''
|
622
|
+
expect(last_response.status).to eq(400)
|
623
|
+
expect(last_response.body).to eq('one is empty')
|
624
|
+
end
|
493
625
|
end
|
494
626
|
end
|
495
627
|
end
|