grape 1.3.2 → 1.5.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +82 -2
  3. data/LICENSE +1 -1
  4. data/README.md +120 -24
  5. data/UPGRADING.md +220 -39
  6. data/lib/grape.rb +3 -2
  7. data/lib/grape/api.rb +3 -3
  8. data/lib/grape/api/instance.rb +22 -25
  9. data/lib/grape/dsl/callbacks.rb +1 -1
  10. data/lib/grape/dsl/helpers.rb +1 -0
  11. data/lib/grape/dsl/inside_route.rb +70 -37
  12. data/lib/grape/dsl/parameters.rb +8 -4
  13. data/lib/grape/dsl/routing.rb +6 -7
  14. data/lib/grape/dsl/validations.rb +18 -1
  15. data/lib/grape/eager_load.rb +1 -1
  16. data/lib/grape/endpoint.rb +8 -6
  17. data/lib/grape/exceptions/validation.rb +1 -1
  18. data/lib/grape/exceptions/validation_errors.rb +1 -1
  19. data/lib/grape/middleware/auth/base.rb +3 -3
  20. data/lib/grape/middleware/base.rb +3 -2
  21. data/lib/grape/middleware/error.rb +11 -13
  22. data/lib/grape/middleware/formatter.rb +3 -3
  23. data/lib/grape/middleware/stack.rb +8 -1
  24. data/lib/grape/request.rb +1 -1
  25. data/lib/grape/router.rb +25 -39
  26. data/lib/grape/router/attribute_translator.rb +26 -5
  27. data/lib/grape/router/route.rb +1 -19
  28. data/lib/grape/{serve_file → serve_stream}/file_body.rb +1 -1
  29. data/lib/grape/{serve_file → serve_stream}/sendfile_response.rb +1 -1
  30. data/lib/grape/{serve_file/file_response.rb → serve_stream/stream_response.rb} +8 -8
  31. data/lib/grape/util/base_inheritable.rb +2 -2
  32. data/lib/grape/util/lazy_value.rb +1 -0
  33. data/lib/grape/validations/attributes_iterator.rb +8 -0
  34. data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
  35. data/lib/grape/validations/params_scope.rb +9 -7
  36. data/lib/grape/validations/single_attribute_iterator.rb +1 -1
  37. data/lib/grape/validations/types.rb +1 -4
  38. data/lib/grape/validations/types/array_coercer.rb +14 -5
  39. data/lib/grape/validations/types/build_coercer.rb +1 -5
  40. data/lib/grape/validations/types/custom_type_coercer.rb +15 -1
  41. data/lib/grape/validations/types/dry_type_coercer.rb +36 -1
  42. data/lib/grape/validations/types/invalid_value.rb +24 -0
  43. data/lib/grape/validations/types/primitive_coercer.rb +9 -3
  44. data/lib/grape/validations/types/set_coercer.rb +6 -4
  45. data/lib/grape/validations/types/variant_collection_coercer.rb +1 -1
  46. data/lib/grape/validations/validator_factory.rb +1 -1
  47. data/lib/grape/validations/validators/as.rb +1 -1
  48. data/lib/grape/validations/validators/base.rb +8 -8
  49. data/lib/grape/validations/validators/coerce.rb +8 -14
  50. data/lib/grape/validations/validators/default.rb +3 -5
  51. data/lib/grape/validations/validators/except_values.rb +1 -1
  52. data/lib/grape/validations/validators/multiple_params_base.rb +2 -1
  53. data/lib/grape/validations/validators/values.rb +1 -1
  54. data/lib/grape/version.rb +1 -1
  55. data/spec/grape/api/instance_spec.rb +50 -0
  56. data/spec/grape/api_remount_spec.rb +9 -4
  57. data/spec/grape/api_spec.rb +75 -0
  58. data/spec/grape/dsl/inside_route_spec.rb +182 -33
  59. data/spec/grape/endpoint/declared_spec.rb +601 -0
  60. data/spec/grape/endpoint_spec.rb +0 -521
  61. data/spec/grape/entity_spec.rb +7 -1
  62. data/spec/grape/integration/rack_sendfile_spec.rb +12 -8
  63. data/spec/grape/middleware/auth/strategies_spec.rb +1 -1
  64. data/spec/grape/middleware/error_spec.rb +1 -1
  65. data/spec/grape/middleware/formatter_spec.rb +1 -1
  66. data/spec/grape/middleware/stack_spec.rb +1 -0
  67. data/spec/grape/request_spec.rb +1 -1
  68. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +13 -3
  69. data/spec/grape/validations/params_scope_spec.rb +26 -0
  70. data/spec/grape/validations/single_attribute_iterator_spec.rb +17 -6
  71. data/spec/grape/validations/types/array_coercer_spec.rb +35 -0
  72. data/spec/grape/validations/types/primitive_coercer_spec.rb +65 -5
  73. data/spec/grape/validations/types/set_coercer_spec.rb +34 -0
  74. data/spec/grape/validations/validators/coerce_spec.rb +223 -25
  75. data/spec/grape/validations/validators/default_spec.rb +170 -0
  76. data/spec/grape/validations/validators/except_values_spec.rb +1 -0
  77. data/spec/grape/validations/validators/values_spec.rb +1 -1
  78. data/spec/grape/validations_spec.rb +290 -18
  79. data/spec/integration/eager_load/eager_load_spec.rb +15 -0
  80. data/spec/shared/versioning_examples.rb +20 -20
  81. data/spec/spec_helper.rb +0 -10
  82. data/spec/support/chunks.rb +14 -0
  83. data/spec/support/versioned_helpers.rb +4 -6
  84. metadata +20 -9
@@ -116,7 +116,7 @@ describe Grape::Entity do
116
116
  expect(last_response.body).to eq('Auto-detect!')
117
117
  end
118
118
 
119
- it 'does not run autodetection for Entity when explicitely provided' do
119
+ it 'does not run autodetection for Entity when explicitly provided' do
120
120
  entity = Class.new(Grape::Entity)
121
121
  some_array = []
122
122
 
@@ -181,6 +181,7 @@ describe Grape::Entity do
181
181
  subject.get '/example' do
182
182
  c = Class.new do
183
183
  attr_reader :id
184
+
184
185
  def initialize(id)
185
186
  @id = id
186
187
  end
@@ -202,6 +203,7 @@ describe Grape::Entity do
202
203
  subject.get '/examples' do
203
204
  c = Class.new do
204
205
  attr_reader :id
206
+
205
207
  def initialize(id)
206
208
  @id = id
207
209
  end
@@ -226,6 +228,7 @@ describe Grape::Entity do
226
228
  subject.get '/example' do
227
229
  c = Class.new do
228
230
  attr_reader :name
231
+
229
232
  def initialize(args)
230
233
  @name = args[:name] || 'no name set'
231
234
  end
@@ -255,6 +258,7 @@ XML
255
258
  subject.get '/example' do
256
259
  c = Class.new do
257
260
  attr_reader :name
261
+
258
262
  def initialize(args)
259
263
  @name = args[:name] || 'no name set'
260
264
  end
@@ -284,6 +288,7 @@ XML
284
288
  subject.get '/example' do
285
289
  c = Class.new do
286
290
  attr_reader :name
291
+
287
292
  def initialize(args)
288
293
  @name = args[:name] || 'no name set'
289
294
  end
@@ -302,6 +307,7 @@ XML
302
307
  it 'present with multiple entities using optional symbol' do
303
308
  user = Class.new do
304
309
  attr_reader :name
310
+
305
311
  def initialize(args)
306
312
  @name = args[:name] || 'no name set'
307
313
  end
@@ -4,12 +4,16 @@ require 'spec_helper'
4
4
 
5
5
  describe Rack::Sendfile do
6
6
  subject do
7
- send_file = file_streamer
7
+ content_object = file_object
8
8
  app = Class.new(Grape::API) do
9
9
  use Rack::Sendfile
10
10
  format :json
11
11
  get do
12
- file send_file
12
+ if content_object.is_a?(String)
13
+ sendfile content_object
14
+ else
15
+ stream content_object
16
+ end
13
17
  end
14
18
  end
15
19
 
@@ -22,9 +26,9 @@ describe Rack::Sendfile do
22
26
  app.call(env)
23
27
  end
24
28
 
25
- context do
26
- let(:file_streamer) do
27
- double(:file_streamer, to_path: '/accel/mapping/some/path')
29
+ context 'when calling sendfile' do
30
+ let(:file_object) do
31
+ '/accel/mapping/some/path'
28
32
  end
29
33
 
30
34
  it 'contains Sendfile headers' do
@@ -33,9 +37,9 @@ describe Rack::Sendfile do
33
37
  end
34
38
  end
35
39
 
36
- context do
37
- let(:file_streamer) do
38
- double(:file_streamer)
40
+ context 'when streaming non file content' do
41
+ let(:file_object) do
42
+ double(:file_object, each: nil)
39
43
  end
40
44
 
41
45
  it 'not contains Sendfile headers' do
@@ -36,7 +36,7 @@ describe Grape::Middleware::Auth::Strategies do
36
36
  RSpec::Matchers.define :be_challenge do
37
37
  match do |actual_response|
38
38
  actual_response.status == 401 &&
39
- actual_response['WWW-Authenticate'] =~ /^Digest / &&
39
+ actual_response['WWW-Authenticate'].start_with?('Digest ') &&
40
40
  actual_response.body.empty?
41
41
  end
42
42
  end
@@ -30,7 +30,7 @@ describe Grape::Middleware::Error do
30
30
  opts = options
31
31
  Rack::Builder.app do
32
32
  use Spec::Support::EndpointFaker
33
- use Grape::Middleware::Error, opts
33
+ use Grape::Middleware::Error, **opts
34
34
  run ErrorSpec::ErrApp
35
35
  end
36
36
  end
@@ -380,7 +380,7 @@ describe Grape::Middleware::Formatter do
380
380
 
381
381
  context 'send file' do
382
382
  let(:file) { double(File) }
383
- let(:file_body) { Grape::ServeFile::FileResponse.new(file) }
383
+ let(:file_body) { Grape::ServeStream::StreamResponse.new(file) }
384
384
  let(:app) { ->(_env) { [200, {}, file_body] } }
385
385
 
386
386
  it 'returns a file response' do
@@ -8,6 +8,7 @@ describe Grape::Middleware::Stack do
8
8
  class BarMiddleware; end
9
9
  class BlockMiddleware
10
10
  attr_reader :block
11
+
11
12
  def initialize(&block)
12
13
  @block = block
13
14
  end
@@ -75,7 +75,7 @@ module Grape
75
75
  Grape.config.reset
76
76
  end
77
77
 
78
- subject(:request_params) { Grape::Request.new(env, opts).params }
78
+ subject(:request_params) { Grape::Request.new(env, **opts).params }
79
79
 
80
80
  context 'when the API does not include a specific param builder' do
81
81
  let(:opts) { {} }
@@ -13,8 +13,8 @@ describe Grape::Validations::MultipleAttributesIterator do
13
13
  { first: 'string', second: 'string' }
14
14
  end
15
15
 
16
- it 'yields the whole params hash without the list of attrs' do
17
- expect { |b| iterator.each(&b) }.to yield_with_args(params)
16
+ it 'yields the whole params hash and the skipped flag without the list of attrs' do
17
+ expect { |b| iterator.each(&b) }.to yield_with_args(params, false)
18
18
  end
19
19
  end
20
20
 
@@ -24,7 +24,17 @@ describe Grape::Validations::MultipleAttributesIterator do
24
24
  end
25
25
 
26
26
  it 'yields each element of the array without the list of attrs' do
27
- expect { |b| iterator.each(&b) }.to yield_successive_args(params[0], params[1])
27
+ expect { |b| iterator.each(&b) }.to yield_successive_args([params[0], false], [params[1], false])
28
+ end
29
+ end
30
+
31
+ context 'when params is empty optional placeholder' do
32
+ let(:params) do
33
+ [Grape::DSL::Parameters::EmptyOptionalValue, { first: 'string2', second: 'string2' }]
34
+ end
35
+
36
+ it 'yields each element of the array without the list of attrs' do
37
+ expect { |b| iterator.each(&b) }.to yield_successive_args([Grape::DSL::Parameters::EmptyOptionalValue, true], [params[1], false])
28
38
  end
29
39
  end
30
40
  end
@@ -633,6 +633,32 @@ describe Grape::Validations::ParamsScope do
633
633
  expect(last_response.status).to eq(200)
634
634
  end
635
635
 
636
+ it 'detect unmet nested dependency' do
637
+ subject.params do
638
+ requires :a, type: String, allow_blank: false, values: %w[x y z]
639
+ given a: ->(val) { val == 'z' } do
640
+ requires :inner3, type: Array, allow_blank: false do
641
+ requires :bar, type: String, allow_blank: false
642
+ given bar: ->(val) { val == 'b' } do
643
+ requires :baz, type: Array do
644
+ optional :baz_category, type: String
645
+ end
646
+ end
647
+ given bar: ->(val) { val == 'c' } do
648
+ requires :baz, type: Array do
649
+ requires :baz_category, type: String
650
+ end
651
+ end
652
+ end
653
+ end
654
+ end
655
+ subject.get('/nested-dependency') { declared(params).to_json }
656
+
657
+ get '/nested-dependency', a: 'z', inner3: [{ bar: 'c', baz: [{ unrelated: 'nope' }] }]
658
+ expect(last_response.status).to eq(400)
659
+ expect(last_response.body).to eq 'inner3[0][baz][0][baz_category] is missing'
660
+ end
661
+
636
662
  it 'includes the parameter within #declared(params)' do
637
663
  get '/test', a: true, b: true
638
664
 
@@ -15,7 +15,7 @@ describe Grape::Validations::SingleAttributeIterator do
15
15
 
16
16
  it 'yields params and every single attribute from the list' do
17
17
  expect { |b| iterator.each(&b) }
18
- .to yield_successive_args([params, :first, false], [params, :second, false])
18
+ .to yield_successive_args([params, :first, false, false], [params, :second, false, false])
19
19
  end
20
20
  end
21
21
 
@@ -26,8 +26,8 @@ describe Grape::Validations::SingleAttributeIterator do
26
26
 
27
27
  it 'yields every single attribute from the list for each of the array elements' do
28
28
  expect { |b| iterator.each(&b) }.to yield_successive_args(
29
- [params[0], :first, false], [params[0], :second, false],
30
- [params[1], :first, false], [params[1], :second, false]
29
+ [params[0], :first, false, false], [params[0], :second, false, false],
30
+ [params[1], :first, false, false], [params[1], :second, false, false]
31
31
  )
32
32
  end
33
33
 
@@ -36,9 +36,20 @@ describe Grape::Validations::SingleAttributeIterator do
36
36
 
37
37
  it 'marks params with empty values' do
38
38
  expect { |b| iterator.each(&b) }.to yield_successive_args(
39
- [params[0], :first, true], [params[0], :second, true],
40
- [params[1], :first, true], [params[1], :second, true],
41
- [params[2], :first, false], [params[2], :second, false]
39
+ [params[0], :first, true, false], [params[0], :second, true, false],
40
+ [params[1], :first, true, false], [params[1], :second, true, false],
41
+ [params[2], :first, false, false], [params[2], :second, false, false]
42
+ )
43
+ end
44
+ end
45
+
46
+ context 'when missing optional value' do
47
+ let(:params) { [Grape::DSL::Parameters::EmptyOptionalValue, 10] }
48
+
49
+ it 'marks params with skipped values' do
50
+ expect { |b| iterator.each(&b) }.to yield_successive_args(
51
+ [params[0], :first, false, true], [params[0], :second, false, true],
52
+ [params[1], :first, false, false], [params[1], :second, false, false],
42
53
  )
43
54
  end
44
55
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Grape::Validations::Types::ArrayCoercer do
6
+ subject { described_class.new(type) }
7
+
8
+ describe '#call' do
9
+ context 'an array of primitives' do
10
+ let(:type) { Array[String] }
11
+
12
+ it 'coerces elements in the array' do
13
+ expect(subject.call([10, 20])).to eq(%w[10 20])
14
+ end
15
+ end
16
+
17
+ context 'an array of arrays' do
18
+ let(:type) { Array[Array[Integer]] }
19
+
20
+ it 'coerces elements in the nested array' do
21
+ expect(subject.call([%w[10 20]])).to eq([[10, 20]])
22
+ expect(subject.call([['10'], ['20']])).to eq([[10], [20]])
23
+ end
24
+ end
25
+
26
+ context 'an array of sets' do
27
+ let(:type) { Array[Set[Integer]] }
28
+
29
+ it 'coerces elements in the nested set' do
30
+ expect(subject.call([%w[10 20]])).to eq([Set[10, 20]])
31
+ expect(subject.call([['10'], ['20']])).to eq([Set[10], Set[20]])
32
+ end
33
+ end
34
+ end
35
+ end
@@ -7,7 +7,19 @@ describe Grape::Validations::Types::PrimitiveCoercer do
7
7
 
8
8
  subject { described_class.new(type, strict) }
9
9
 
10
- describe '.call' do
10
+ describe '#call' do
11
+ context 'BigDecimal' do
12
+ let(:type) { BigDecimal }
13
+
14
+ it 'coerces to BigDecimal' do
15
+ expect(subject.call(5)).to eq(BigDecimal(5))
16
+ end
17
+
18
+ it 'coerces an empty string to nil' do
19
+ expect(subject.call('')).to be_nil
20
+ end
21
+ end
22
+
11
23
  context 'Boolean' do
12
24
  let(:type) { Grape::API::Boolean }
13
25
 
@@ -26,6 +38,50 @@ describe Grape::Validations::Types::PrimitiveCoercer do
26
38
  it 'returns an error when the given value cannot be coerced' do
27
39
  expect(subject.call(123)).to be_instance_of(Grape::Validations::Types::InvalidValue)
28
40
  end
41
+
42
+ it 'coerces an empty string to nil' do
43
+ expect(subject.call('')).to be_nil
44
+ end
45
+ end
46
+
47
+ context 'DateTime' do
48
+ let(:type) { DateTime }
49
+
50
+ it 'coerces an empty string to nil' do
51
+ expect(subject.call('')).to be_nil
52
+ end
53
+ end
54
+
55
+ context 'Float' do
56
+ let(:type) { Float }
57
+
58
+ it 'coerces an empty string to nil' do
59
+ expect(subject.call('')).to be_nil
60
+ end
61
+ end
62
+
63
+ context 'Integer' do
64
+ let(:type) { Integer }
65
+
66
+ it 'coerces an empty string to nil' do
67
+ expect(subject.call('')).to be_nil
68
+ end
69
+ end
70
+
71
+ context 'Numeric' do
72
+ let(:type) { Numeric }
73
+
74
+ it 'coerces an empty string to nil' do
75
+ expect(subject.call('')).to be_nil
76
+ end
77
+ end
78
+
79
+ context 'Time' do
80
+ let(:type) { Time }
81
+
82
+ it 'coerces an empty string to nil' do
83
+ expect(subject.call('')).to be_nil
84
+ end
29
85
  end
30
86
 
31
87
  context 'String' do
@@ -34,13 +90,17 @@ describe Grape::Validations::Types::PrimitiveCoercer do
34
90
  it 'coerces to String' do
35
91
  expect(subject.call(10)).to eq('10')
36
92
  end
93
+
94
+ it 'does not coerce an empty string to nil' do
95
+ expect(subject.call('')).to eq('')
96
+ end
37
97
  end
38
98
 
39
- context 'BigDecimal' do
40
- let(:type) { BigDecimal }
99
+ context 'Symbol' do
100
+ let(:type) { Symbol }
41
101
 
42
- it 'coerces to BigDecimal' do
43
- expect(subject.call(5)).to eq(BigDecimal(5))
102
+ it 'coerces an empty string to nil' do
103
+ expect(subject.call('')).to be_nil
44
104
  end
45
105
  end
46
106
 
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Grape::Validations::Types::SetCoercer do
6
+ subject { described_class.new(type) }
7
+
8
+ describe '#call' do
9
+ context 'a set of primitives' do
10
+ let(:type) { Set[String] }
11
+
12
+ it 'coerces elements to the set' do
13
+ expect(subject.call([10, 20])).to eq(Set['10', '20'])
14
+ end
15
+ end
16
+
17
+ context 'a set of sets' do
18
+ let(:type) { Set[Set[Integer]] }
19
+
20
+ it 'coerces elements in the nested set' do
21
+ expect(subject.call([%w[10 20]])).to eq(Set[Set[10, 20]])
22
+ expect(subject.call([['10'], ['20']])).to eq(Set[Set[10], Set[20]])
23
+ end
24
+ end
25
+
26
+ context 'a set of sets of arrays' do
27
+ let(:type) { Set[Set[Array[Integer]]] }
28
+
29
+ it 'coerces elements in the nested set' do
30
+ expect(subject.call([[['10'], ['20']]])).to eq(Set[Set[Array[10], Array[20]]])
31
+ end
32
+ end
33
+ end
34
+ end
@@ -227,23 +227,51 @@ describe Grape::Validations::CoerceValidator do
227
227
  expect(last_response.body).to eq('NilClass')
228
228
  end
229
229
 
230
- it 'is a custom type' do
231
- subject.params do
232
- requires :uri, coerce: SecureURIOnly
233
- end
234
- subject.get '/secure_uri' do
235
- params[:uri].class
230
+ context 'a custom type' do
231
+ it 'coerces the given value' do
232
+ subject.params do
233
+ requires :uri, coerce: SecureURIOnly
234
+ end
235
+ subject.get '/secure_uri' do
236
+ params[:uri].class
237
+ end
238
+
239
+ get 'secure_uri', uri: 'https://www.example.com'
240
+
241
+ expect(last_response.status).to eq(200)
242
+ expect(last_response.body).to eq('URI::HTTPS')
243
+
244
+ get 'secure_uri', uri: 'http://www.example.com'
245
+
246
+ expect(last_response.status).to eq(400)
247
+ expect(last_response.body).to eq('uri is invalid')
236
248
  end
237
249
 
238
- get 'secure_uri', uri: 'https://www.example.com'
250
+ context 'returning the InvalidValue instance when invalid' do
251
+ let(:custom_type) do
252
+ Class.new do
253
+ def self.parse(_val)
254
+ Grape::Types::InvalidValue.new('must be unique')
255
+ end
256
+ end
257
+ end
239
258
 
240
- expect(last_response.status).to eq(200)
241
- expect(last_response.body).to eq('URI::HTTPS')
259
+ it 'uses a custom message added to the invalid value' do
260
+ type = custom_type
242
261
 
243
- get 'secure_uri', uri: 'http://www.example.com'
262
+ subject.params do
263
+ requires :name, type: type
264
+ end
265
+ subject.get '/whatever' do
266
+ params[:name].class
267
+ end
244
268
 
245
- expect(last_response.status).to eq(400)
246
- expect(last_response.body).to eq('uri is invalid')
269
+ get 'whatever', name: 'Bob'
270
+
271
+ expect(last_response.status).to eq(400)
272
+ expect(last_response.body).to eq('name must be unique')
273
+ end
274
+ end
247
275
  end
248
276
 
249
277
  context 'Array' do
@@ -424,6 +452,165 @@ describe Grape::Validations::CoerceValidator do
424
452
  expect(last_response.status).to eq(200)
425
453
  expect(last_response.body).to eq(integer_class_name)
426
454
  end
455
+
456
+ context 'nil values' do
457
+ context 'primitive types' do
458
+ Grape::Validations::Types::PRIMITIVES.each do |type|
459
+ it 'respects the nil value' do
460
+ subject.params do
461
+ requires :param, type: type
462
+ end
463
+ subject.get '/nil_value' do
464
+ params[:param].class
465
+ end
466
+
467
+ get '/nil_value', param: nil
468
+ expect(last_response.status).to eq(200)
469
+ expect(last_response.body).to eq('NilClass')
470
+ end
471
+ end
472
+ end
473
+
474
+ context 'structures types' do
475
+ Grape::Validations::Types::STRUCTURES.each do |type|
476
+ it 'respects the nil value' do
477
+ subject.params do
478
+ requires :param, type: type
479
+ end
480
+ subject.get '/nil_value' do
481
+ params[:param].class
482
+ end
483
+
484
+ get '/nil_value', param: nil
485
+ expect(last_response.status).to eq(200)
486
+ expect(last_response.body).to eq('NilClass')
487
+ end
488
+ end
489
+ end
490
+
491
+ context 'special types' do
492
+ Grape::Validations::Types::SPECIAL.each_key do |type|
493
+ it 'respects the nil value' do
494
+ subject.params do
495
+ requires :param, type: type
496
+ end
497
+ subject.get '/nil_value' do
498
+ params[:param].class
499
+ end
500
+
501
+ get '/nil_value', param: nil
502
+ expect(last_response.status).to eq(200)
503
+ expect(last_response.body).to eq('NilClass')
504
+ end
505
+ end
506
+
507
+ context 'variant-member-type collections' do
508
+ [
509
+ Array[Integer, String],
510
+ [Integer, String, Array[Integer, String]]
511
+ ].each do |type|
512
+ it 'respects the nil value' do
513
+ subject.params do
514
+ requires :param, type: type
515
+ end
516
+ subject.get '/nil_value' do
517
+ params[:param].class
518
+ end
519
+
520
+ get '/nil_value', param: nil
521
+ expect(last_response.status).to eq(200)
522
+ expect(last_response.body).to eq('NilClass')
523
+ end
524
+ end
525
+ end
526
+ end
527
+ end
528
+
529
+ context 'empty string' do
530
+ context 'primitive types' do
531
+ (Grape::Validations::Types::PRIMITIVES - [String]).each do |type|
532
+ it "is coerced to nil for type #{type}" do
533
+ subject.params do
534
+ requires :param, type: type
535
+ end
536
+ subject.get '/empty_string' do
537
+ params[:param].class
538
+ end
539
+
540
+ get '/empty_string', param: ''
541
+ expect(last_response.status).to eq(200)
542
+ expect(last_response.body).to eq('NilClass')
543
+ end
544
+ end
545
+
546
+ it 'is not coerced to nil for type String' do
547
+ subject.params do
548
+ requires :param, type: String
549
+ end
550
+ subject.get '/empty_string' do
551
+ params[:param].class
552
+ end
553
+
554
+ get '/empty_string', param: ''
555
+ expect(last_response.status).to eq(200)
556
+ expect(last_response.body).to eq('String')
557
+ end
558
+ end
559
+
560
+ context 'structures types' do
561
+ (Grape::Validations::Types::STRUCTURES - [Hash]).each do |type|
562
+ it "is coerced to nil for type #{type}" do
563
+ subject.params do
564
+ requires :param, type: type
565
+ end
566
+ subject.get '/empty_string' do
567
+ params[:param].class
568
+ end
569
+
570
+ get '/empty_string', param: ''
571
+ expect(last_response.status).to eq(200)
572
+ expect(last_response.body).to eq('NilClass')
573
+ end
574
+ end
575
+ end
576
+
577
+ context 'special types' do
578
+ (Grape::Validations::Types::SPECIAL.keys - [File, Rack::Multipart::UploadedFile]).each do |type|
579
+ it "is coerced to nil for type #{type}" do
580
+ subject.params do
581
+ requires :param, type: type
582
+ end
583
+ subject.get '/empty_string' do
584
+ params[:param].class
585
+ end
586
+
587
+ get '/empty_string', param: ''
588
+ expect(last_response.status).to eq(200)
589
+ expect(last_response.body).to eq('NilClass')
590
+ end
591
+ end
592
+
593
+ context 'variant-member-type collections' do
594
+ [
595
+ Array[Integer, String],
596
+ [Integer, String, Array[Integer, String]]
597
+ ].each do |type|
598
+ it "is coerced to nil for type #{type}" do
599
+ subject.params do
600
+ requires :param, type: type
601
+ end
602
+ subject.get '/empty_string' do
603
+ params[:param].class
604
+ end
605
+
606
+ get '/empty_string', param: ''
607
+ expect(last_response.status).to eq(200)
608
+ expect(last_response.body).to eq('NilClass')
609
+ end
610
+ end
611
+ end
612
+ end
613
+ end
427
614
  end
428
615
 
429
616
  context 'using coerce_with' do
@@ -461,6 +648,30 @@ describe Grape::Validations::CoerceValidator do
461
648
  expect(JSON.parse(last_response.body)).to eq(%w[a b c d])
462
649
  end
463
650
 
651
+ it 'parses parameters with Array[Array[String]] type and coerce_with' do
652
+ subject.params do
653
+ requires :values, type: Array[Array[String]], coerce_with: ->(val) { val.is_a?(String) ? [val.split(/,/).map(&:strip)] : val }
654
+ end
655
+ subject.post '/coerce_nested_strings' do
656
+ params[:values]
657
+ end
658
+
659
+ post '/coerce_nested_strings', ::Grape::Json.dump(values: 'a,b,c,d'), 'CONTENT_TYPE' => 'application/json'
660
+ expect(last_response.status).to eq(201)
661
+ expect(JSON.parse(last_response.body)).to eq([%w[a b c d]])
662
+
663
+ post '/coerce_nested_strings', ::Grape::Json.dump(values: [%w[a c], %w[b]]), 'CONTENT_TYPE' => 'application/json'
664
+ expect(last_response.status).to eq(201)
665
+ expect(JSON.parse(last_response.body)).to eq([%w[a c], %w[b]])
666
+
667
+ post '/coerce_nested_strings', ::Grape::Json.dump(values: [[]]), 'CONTENT_TYPE' => 'application/json'
668
+ expect(last_response.status).to eq(201)
669
+ expect(JSON.parse(last_response.body)).to eq([[]])
670
+
671
+ post '/coerce_nested_strings', ::Grape::Json.dump(values: [['a', { bar: 0 }], ['b']]), 'CONTENT_TYPE' => 'application/json'
672
+ expect(last_response.status).to eq(400)
673
+ end
674
+
464
675
  it 'parses parameters with Array[Integer] type' do
465
676
  subject.params do
466
677
  requires :values, type: Array[Integer], coerce_with: ->(val) { val.split(/\s+/).map(&:to_i) }
@@ -752,19 +963,6 @@ describe Grape::Validations::CoerceValidator do
752
963
  expect(last_response.body).to eq('String')
753
964
  end
754
965
 
755
- it 'respects nil values' do
756
- subject.params do
757
- optional :a, types: [File, String]
758
- end
759
- subject.get '/' do
760
- params[:a].class.to_s
761
- end
762
-
763
- get '/', a: nil
764
- expect(last_response.status).to eq(200)
765
- expect(last_response.body).to eq('NilClass')
766
- end
767
-
768
966
  it 'fails when no coercion is possible' do
769
967
  subject.params do
770
968
  requires :a, types: [Boolean, Integer]