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.

Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -3
  3. data/Dangerfile +1 -80
  4. data/Gemfile +1 -1
  5. data/Gemfile.lock +69 -51
  6. data/README.md +42 -1
  7. data/UPGRADING.md +2 -2
  8. data/grape.gemspec +1 -1
  9. data/lib/grape/api.rb +5 -15
  10. data/lib/grape/cookies.rb +1 -1
  11. data/lib/grape/dsl/inside_route.rb +2 -3
  12. data/lib/grape/dsl/request_response.rb +1 -1
  13. data/lib/grape/endpoint.rb +19 -7
  14. data/lib/grape/error_formatter.rb +2 -2
  15. data/lib/grape/exceptions/base.rb +18 -18
  16. data/lib/grape/exceptions/validation.rb +6 -7
  17. data/lib/grape/exceptions/validation_errors.rb +5 -5
  18. data/lib/grape/formatter.rb +2 -2
  19. data/lib/grape/locale/en.yml +1 -0
  20. data/lib/grape/middleware/auth/base.rb +2 -2
  21. data/lib/grape/middleware/base.rb +2 -2
  22. data/lib/grape/middleware/error.rb +1 -1
  23. data/lib/grape/middleware/stack.rb +1 -1
  24. data/lib/grape/middleware/versioner/header.rb +1 -1
  25. data/lib/grape/namespace.rb +1 -1
  26. data/lib/grape/parser.rb +2 -2
  27. data/lib/grape/presenters/presenter.rb +1 -1
  28. data/lib/grape/router.rb +6 -6
  29. data/lib/grape/router/pattern.rb +7 -7
  30. data/lib/grape/router/route.rb +3 -3
  31. data/lib/grape/util/env.rb +1 -1
  32. data/lib/grape/validations/params_scope.rb +15 -7
  33. data/lib/grape/validations/validators/allow_blank.rb +0 -13
  34. data/lib/grape/validations/validators/base.rb +8 -1
  35. data/lib/grape/validations/validators/default.rb +1 -3
  36. data/lib/grape/validations/validators/presence.rb +0 -5
  37. data/lib/grape/validations/validators/values.rb +17 -3
  38. data/lib/grape/version.rb +1 -1
  39. data/pkg/grape-0.18.0.gem +0 -0
  40. data/spec/grape/api_spec.rb +41 -0
  41. data/spec/grape/exceptions/validation_spec.rb +1 -1
  42. data/spec/grape/middleware/stack_spec.rb +20 -0
  43. data/spec/grape/validations/params_scope_spec.rb +190 -58
  44. data/spec/grape/validations/validators/allow_blank_spec.rb +22 -7
  45. data/spec/grape/validations/validators/coerce_spec.rb +6 -6
  46. data/spec/grape/validations/validators/values_spec.rb +146 -0
  47. data/spec/grape/validations_spec.rb +28 -0
  48. metadata +6 -6
@@ -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, options = {})
38
- pattern << '*path' unless options[:anchor] || pattern.end_with?('*path')
39
- pattern + options[:suffix].to_s
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(options = {})
43
- requirements = {}.merge(options[:requirements])
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?
@@ -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.merge(request_method: method.to_s.upcase))
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)
@@ -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
- GRAPE_METHOD_NOT_ALLOWED = 'grape.method_not_allowed'.freeze
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
- def initialize(attrs, options, required, scope)
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
- @values = (options_key?(:value, options) ? options[:value] : options)
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
- return if param_array.all? { |param| values.include?(param) }
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
@@ -1,4 +1,4 @@
1
1
  module Grape
2
2
  # The current version of Grape.
3
- VERSION = '0.17.0'.freeze
3
+ VERSION = '0.18.0'.freeze
4
4
  end
Binary file
@@ -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(RuntimeError, 'Params are missing:')
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
- before do
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
- it 'applies the validations only if the parameter has the specific value' do
421
- get '/test'
422
- expect(last_response.status).to eq(200)
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
- it 'raises an error if the dependent parameter was never specified' do
433
- expect do
434
- subject.params do
435
- given :c do
436
- end
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.to raise_error(Grape::Exceptions::UnknownParameter)
563
+ end
439
564
  end
565
+ subject.get('/nested') { declared(params).to_json }
440
566
 
441
- it 'includes the parameter within #declared(params)' do
442
- get '/test', a: true, b: true
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
- it 'returns a sensible error message within a nested context' do
448
- subject.params do
449
- requires :bar, type: Hash do
450
- optional :a
451
- given a: ->(val) { val == 'x' } do
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
- it 'includes the nested parameter within #declared(params)' do
464
- subject.params do
465
- requires :bar, type: Hash do
466
- optional :a
467
- given a: ->(val) { val == 'x' } do
468
- requires :b
469
- end
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
- end
472
- subject.get('/nested') { declared(params).to_json }
596
+ subject.get('/fail-fast') { declared(params).to_json }
473
597
 
474
- get '/nested', bar: { a: 'x', b: 'yes' }
475
- expect(JSON.parse(last_response.body)).to eq('bar' => { 'a' => 'x', 'b' => 'yes' })
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
- it 'includes level 2 nested parameters outside the given within #declared(params)' do
479
- subject.params do
480
- requires :bar, type: Hash do
481
- optional :a
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
- subject.get('/nested') { declared(params).to_json }
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
- get '/nested', bar: { a: 'x', c: { b: 'yes' } }
492
- expect(JSON.parse(last_response.body)).to eq('bar' => { 'a' => 'x', 'c' => { 'b' => 'yes' } })
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