grape 1.3.1 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +82 -1
  3. data/LICENSE +1 -1
  4. data/README.md +104 -21
  5. data/UPGRADING.md +265 -39
  6. data/lib/grape.rb +2 -2
  7. data/lib/grape/api.rb +2 -2
  8. data/lib/grape/api/instance.rb +29 -28
  9. data/lib/grape/dsl/helpers.rb +1 -0
  10. data/lib/grape/dsl/inside_route.rb +69 -36
  11. data/lib/grape/dsl/parameters.rb +7 -3
  12. data/lib/grape/dsl/routing.rb +2 -4
  13. data/lib/grape/dsl/validations.rb +18 -1
  14. data/lib/grape/eager_load.rb +1 -1
  15. data/lib/grape/endpoint.rb +8 -6
  16. data/lib/grape/http/headers.rb +1 -0
  17. data/lib/grape/middleware/base.rb +2 -1
  18. data/lib/grape/middleware/error.rb +10 -12
  19. data/lib/grape/middleware/formatter.rb +3 -3
  20. data/lib/grape/middleware/stack.rb +21 -8
  21. data/lib/grape/middleware/versioner/header.rb +1 -1
  22. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
  23. data/lib/grape/path.rb +2 -2
  24. data/lib/grape/request.rb +1 -1
  25. data/lib/grape/router.rb +30 -43
  26. data/lib/grape/router/attribute_translator.rb +26 -5
  27. data/lib/grape/router/route.rb +3 -22
  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 +11 -8
  32. data/lib/grape/util/lazy_value.rb +1 -0
  33. data/lib/grape/util/reverse_stackable_values.rb +3 -1
  34. data/lib/grape/util/stackable_values.rb +3 -1
  35. data/lib/grape/validations/attributes_iterator.rb +8 -0
  36. data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
  37. data/lib/grape/validations/params_scope.rb +8 -6
  38. data/lib/grape/validations/single_attribute_iterator.rb +1 -1
  39. data/lib/grape/validations/types.rb +6 -5
  40. data/lib/grape/validations/types/array_coercer.rb +14 -5
  41. data/lib/grape/validations/types/build_coercer.rb +5 -8
  42. data/lib/grape/validations/types/custom_type_coercer.rb +14 -2
  43. data/lib/grape/validations/types/dry_type_coercer.rb +36 -1
  44. data/lib/grape/validations/types/file.rb +15 -13
  45. data/lib/grape/validations/types/json.rb +40 -36
  46. data/lib/grape/validations/types/primitive_coercer.rb +11 -5
  47. data/lib/grape/validations/types/set_coercer.rb +6 -4
  48. data/lib/grape/validations/types/variant_collection_coercer.rb +1 -1
  49. data/lib/grape/validations/validator_factory.rb +1 -1
  50. data/lib/grape/validations/validators/as.rb +1 -1
  51. data/lib/grape/validations/validators/base.rb +4 -5
  52. data/lib/grape/validations/validators/coerce.rb +3 -10
  53. data/lib/grape/validations/validators/default.rb +3 -5
  54. data/lib/grape/validations/validators/except_values.rb +1 -1
  55. data/lib/grape/validations/validators/multiple_params_base.rb +2 -1
  56. data/lib/grape/validations/validators/regexp.rb +1 -1
  57. data/lib/grape/validations/validators/values.rb +1 -1
  58. data/lib/grape/version.rb +1 -1
  59. data/spec/grape/api/instance_spec.rb +50 -0
  60. data/spec/grape/api_spec.rb +75 -0
  61. data/spec/grape/dsl/inside_route_spec.rb +182 -33
  62. data/spec/grape/endpoint/declared_spec.rb +601 -0
  63. data/spec/grape/endpoint_spec.rb +0 -521
  64. data/spec/grape/entity_spec.rb +6 -0
  65. data/spec/grape/integration/rack_sendfile_spec.rb +12 -8
  66. data/spec/grape/middleware/auth/strategies_spec.rb +1 -1
  67. data/spec/grape/middleware/error_spec.rb +1 -1
  68. data/spec/grape/middleware/formatter_spec.rb +1 -1
  69. data/spec/grape/middleware/stack_spec.rb +3 -1
  70. data/spec/grape/path_spec.rb +4 -4
  71. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +13 -3
  72. data/spec/grape/validations/params_scope_spec.rb +26 -0
  73. data/spec/grape/validations/single_attribute_iterator_spec.rb +17 -6
  74. data/spec/grape/validations/types/array_coercer_spec.rb +35 -0
  75. data/spec/grape/validations/types/primitive_coercer_spec.rb +65 -5
  76. data/spec/grape/validations/types/set_coercer_spec.rb +34 -0
  77. data/spec/grape/validations/types_spec.rb +1 -1
  78. data/spec/grape/validations/validators/coerce_spec.rb +317 -29
  79. data/spec/grape/validations/validators/default_spec.rb +170 -0
  80. data/spec/grape/validations/validators/except_values_spec.rb +1 -0
  81. data/spec/grape/validations/validators/values_spec.rb +1 -1
  82. data/spec/grape/validations_spec.rb +290 -18
  83. data/spec/integration/eager_load/eager_load_spec.rb +15 -0
  84. data/spec/spec_helper.rb +0 -10
  85. data/spec/support/chunks.rb +14 -0
  86. data/spec/support/versioned_helpers.rb +3 -5
  87. metadata +18 -8
@@ -154,6 +154,36 @@ describe Grape::Validations::CoerceValidator do
154
154
  end
155
155
 
156
156
  context 'coerces' do
157
+ context 'json' do
158
+ let(:headers) { { 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json' } }
159
+
160
+ it 'BigDecimal' do
161
+ subject.params do
162
+ requires :bigdecimal, type: BigDecimal
163
+ end
164
+ subject.post '/bigdecimal' do
165
+ "#{params[:bigdecimal].class} #{params[:bigdecimal].to_f}"
166
+ end
167
+
168
+ post '/bigdecimal', { bigdecimal: 45.1 }.to_json, headers
169
+ expect(last_response.status).to eq(201)
170
+ expect(last_response.body).to eq('BigDecimal 45.1')
171
+ end
172
+
173
+ it 'Boolean' do
174
+ subject.params do
175
+ requires :boolean, type: Boolean
176
+ end
177
+ subject.post '/boolean' do
178
+ params[:boolean]
179
+ end
180
+
181
+ post '/boolean', { boolean: 'true' }.to_json, headers
182
+ expect(last_response.status).to eq(201)
183
+ expect(last_response.body).to eq('true')
184
+ end
185
+ end
186
+
157
187
  it 'BigDecimal' do
158
188
  subject.params do
159
189
  requires :bigdecimal, coerce: BigDecimal
@@ -180,6 +210,23 @@ describe Grape::Validations::CoerceValidator do
180
210
  expect(last_response.body).to eq(integer_class_name)
181
211
  end
182
212
 
213
+ it 'String' do
214
+ subject.params do
215
+ requires :string, coerce: String
216
+ end
217
+ subject.get '/string' do
218
+ params[:string].class
219
+ end
220
+
221
+ get '/string', string: 45
222
+ expect(last_response.status).to eq(200)
223
+ expect(last_response.body).to eq('String')
224
+
225
+ get '/string', string: nil
226
+ expect(last_response.status).to eq(200)
227
+ expect(last_response.body).to eq('NilClass')
228
+ end
229
+
183
230
  it 'is a custom type' do
184
231
  subject.params do
185
232
  requires :uri, coerce: SecureURIOnly
@@ -307,42 +354,60 @@ describe Grape::Validations::CoerceValidator do
307
354
  expect(last_response.body).to eq('TrueClass')
308
355
  end
309
356
 
310
- it 'Rack::Multipart::UploadedFile' do
311
- subject.params do
312
- requires :file, type: Rack::Multipart::UploadedFile
313
- end
314
- subject.post '/upload' do
315
- params[:file][:filename]
316
- end
357
+ context 'File' do
358
+ let(:file) { Rack::Test::UploadedFile.new(__FILE__) }
359
+ let(:filename) { File.basename(__FILE__).to_s }
317
360
 
318
- post '/upload', file: Rack::Test::UploadedFile.new(__FILE__)
319
- expect(last_response.status).to eq(201)
320
- expect(last_response.body).to eq(File.basename(__FILE__).to_s)
361
+ it 'Rack::Multipart::UploadedFile' do
362
+ subject.params do
363
+ requires :file, type: Rack::Multipart::UploadedFile
364
+ end
365
+ subject.post '/upload' do
366
+ params[:file][:filename]
367
+ end
321
368
 
322
- post '/upload', file: 'not a file'
323
- expect(last_response.status).to eq(400)
324
- expect(last_response.body).to eq('file is invalid')
325
- end
369
+ post '/upload', file: file
370
+ expect(last_response.status).to eq(201)
371
+ expect(last_response.body).to eq(filename)
326
372
 
327
- it 'File' do
328
- subject.params do
329
- requires :file, coerce: File
330
- end
331
- subject.post '/upload' do
332
- params[:file][:filename]
373
+ post '/upload', file: 'not a file'
374
+ expect(last_response.status).to eq(400)
375
+ expect(last_response.body).to eq('file is invalid')
333
376
  end
334
377
 
335
- post '/upload', file: Rack::Test::UploadedFile.new(__FILE__)
336
- expect(last_response.status).to eq(201)
337
- expect(last_response.body).to eq(File.basename(__FILE__).to_s)
378
+ it 'File' do
379
+ subject.params do
380
+ requires :file, coerce: File
381
+ end
382
+ subject.post '/upload' do
383
+ params[:file][:filename]
384
+ end
338
385
 
339
- post '/upload', file: 'not a file'
340
- expect(last_response.status).to eq(400)
341
- expect(last_response.body).to eq('file is invalid')
386
+ post '/upload', file: file
387
+ expect(last_response.status).to eq(201)
388
+ expect(last_response.body).to eq(filename)
342
389
 
343
- post '/upload', file: { filename: 'fake file', tempfile: '/etc/passwd' }
344
- expect(last_response.status).to eq(400)
345
- expect(last_response.body).to eq('file is invalid')
390
+ post '/upload', file: 'not a file'
391
+ expect(last_response.status).to eq(400)
392
+ expect(last_response.body).to eq('file is invalid')
393
+
394
+ post '/upload', file: { filename: 'fake file', tempfile: '/etc/passwd' }
395
+ expect(last_response.status).to eq(400)
396
+ expect(last_response.body).to eq('file is invalid')
397
+ end
398
+
399
+ it 'collection' do
400
+ subject.params do
401
+ requires :files, type: Array[File]
402
+ end
403
+ subject.post '/upload' do
404
+ params[:files].first[:filename]
405
+ end
406
+
407
+ post '/upload', files: [file]
408
+ expect(last_response.status).to eq(201)
409
+ expect(last_response.body).to eq(filename)
410
+ end
346
411
  end
347
412
 
348
413
  it 'Nests integers' do
@@ -359,6 +424,165 @@ describe Grape::Validations::CoerceValidator do
359
424
  expect(last_response.status).to eq(200)
360
425
  expect(last_response.body).to eq(integer_class_name)
361
426
  end
427
+
428
+ context 'nil values' do
429
+ context 'primitive types' do
430
+ Grape::Validations::Types::PRIMITIVES.each do |type|
431
+ it 'respects the nil value' do
432
+ subject.params do
433
+ requires :param, type: type
434
+ end
435
+ subject.get '/nil_value' do
436
+ params[:param].class
437
+ end
438
+
439
+ get '/nil_value', param: nil
440
+ expect(last_response.status).to eq(200)
441
+ expect(last_response.body).to eq('NilClass')
442
+ end
443
+ end
444
+ end
445
+
446
+ context 'structures types' do
447
+ Grape::Validations::Types::STRUCTURES.each do |type|
448
+ it 'respects the nil value' do
449
+ subject.params do
450
+ requires :param, type: type
451
+ end
452
+ subject.get '/nil_value' do
453
+ params[:param].class
454
+ end
455
+
456
+ get '/nil_value', param: nil
457
+ expect(last_response.status).to eq(200)
458
+ expect(last_response.body).to eq('NilClass')
459
+ end
460
+ end
461
+ end
462
+
463
+ context 'special types' do
464
+ Grape::Validations::Types::SPECIAL.each_key do |type|
465
+ it 'respects the nil value' do
466
+ subject.params do
467
+ requires :param, type: type
468
+ end
469
+ subject.get '/nil_value' do
470
+ params[:param].class
471
+ end
472
+
473
+ get '/nil_value', param: nil
474
+ expect(last_response.status).to eq(200)
475
+ expect(last_response.body).to eq('NilClass')
476
+ end
477
+ end
478
+
479
+ context 'variant-member-type collections' do
480
+ [
481
+ Array[Integer, String],
482
+ [Integer, String, Array[Integer, String]]
483
+ ].each do |type|
484
+ it 'respects the nil value' do
485
+ subject.params do
486
+ requires :param, type: type
487
+ end
488
+ subject.get '/nil_value' do
489
+ params[:param].class
490
+ end
491
+
492
+ get '/nil_value', param: nil
493
+ expect(last_response.status).to eq(200)
494
+ expect(last_response.body).to eq('NilClass')
495
+ end
496
+ end
497
+ end
498
+ end
499
+ end
500
+
501
+ context 'empty string' do
502
+ context 'primitive types' do
503
+ (Grape::Validations::Types::PRIMITIVES - [String]).each do |type|
504
+ it "is coerced to nil for type #{type}" do
505
+ subject.params do
506
+ requires :param, type: type
507
+ end
508
+ subject.get '/empty_string' do
509
+ params[:param].class
510
+ end
511
+
512
+ get '/empty_string', param: ''
513
+ expect(last_response.status).to eq(200)
514
+ expect(last_response.body).to eq('NilClass')
515
+ end
516
+ end
517
+
518
+ it 'is not coerced to nil for type String' do
519
+ subject.params do
520
+ requires :param, type: String
521
+ end
522
+ subject.get '/empty_string' do
523
+ params[:param].class
524
+ end
525
+
526
+ get '/empty_string', param: ''
527
+ expect(last_response.status).to eq(200)
528
+ expect(last_response.body).to eq('String')
529
+ end
530
+ end
531
+
532
+ context 'structures types' do
533
+ (Grape::Validations::Types::STRUCTURES - [Hash]).each do |type|
534
+ it "is coerced to nil for type #{type}" do
535
+ subject.params do
536
+ requires :param, type: type
537
+ end
538
+ subject.get '/empty_string' do
539
+ params[:param].class
540
+ end
541
+
542
+ get '/empty_string', param: ''
543
+ expect(last_response.status).to eq(200)
544
+ expect(last_response.body).to eq('NilClass')
545
+ end
546
+ end
547
+ end
548
+
549
+ context 'special types' do
550
+ (Grape::Validations::Types::SPECIAL.keys - [File, Rack::Multipart::UploadedFile]).each do |type|
551
+ it "is coerced to nil for type #{type}" do
552
+ subject.params do
553
+ requires :param, type: type
554
+ end
555
+ subject.get '/empty_string' do
556
+ params[:param].class
557
+ end
558
+
559
+ get '/empty_string', param: ''
560
+ expect(last_response.status).to eq(200)
561
+ expect(last_response.body).to eq('NilClass')
562
+ end
563
+ end
564
+
565
+ context 'variant-member-type collections' do
566
+ [
567
+ Array[Integer, String],
568
+ [Integer, String, Array[Integer, String]]
569
+ ].each do |type|
570
+ it "is coerced to nil for type #{type}" do
571
+ subject.params do
572
+ requires :param, type: type
573
+ end
574
+ subject.get '/empty_string' do
575
+ params[:param].class
576
+ end
577
+
578
+ get '/empty_string', param: ''
579
+ expect(last_response.status).to eq(200)
580
+ expect(last_response.body).to eq('NilClass')
581
+ end
582
+ end
583
+ end
584
+ end
585
+ end
362
586
  end
363
587
 
364
588
  context 'using coerce_with' do
@@ -396,6 +620,30 @@ describe Grape::Validations::CoerceValidator do
396
620
  expect(JSON.parse(last_response.body)).to eq(%w[a b c d])
397
621
  end
398
622
 
623
+ it 'parses parameters with Array[Array[String]] type and coerce_with' do
624
+ subject.params do
625
+ requires :values, type: Array[Array[String]], coerce_with: ->(val) { val.is_a?(String) ? [val.split(/,/).map(&:strip)] : val }
626
+ end
627
+ subject.post '/coerce_nested_strings' do
628
+ params[:values]
629
+ end
630
+
631
+ post '/coerce_nested_strings', ::Grape::Json.dump(values: 'a,b,c,d'), 'CONTENT_TYPE' => 'application/json'
632
+ expect(last_response.status).to eq(201)
633
+ expect(JSON.parse(last_response.body)).to eq([%w[a b c d]])
634
+
635
+ post '/coerce_nested_strings', ::Grape::Json.dump(values: [%w[a c], %w[b]]), 'CONTENT_TYPE' => 'application/json'
636
+ expect(last_response.status).to eq(201)
637
+ expect(JSON.parse(last_response.body)).to eq([%w[a c], %w[b]])
638
+
639
+ post '/coerce_nested_strings', ::Grape::Json.dump(values: [[]]), 'CONTENT_TYPE' => 'application/json'
640
+ expect(last_response.status).to eq(201)
641
+ expect(JSON.parse(last_response.body)).to eq([[]])
642
+
643
+ post '/coerce_nested_strings', ::Grape::Json.dump(values: [['a', { bar: 0 }], ['b']]), 'CONTENT_TYPE' => 'application/json'
644
+ expect(last_response.status).to eq(400)
645
+ end
646
+
399
647
  it 'parses parameters with Array[Integer] type' do
400
648
  subject.params do
401
649
  requires :values, type: Array[Integer], coerce_with: ->(val) { val.split(/\s+/).map(&:to_i) }
@@ -478,6 +726,46 @@ describe Grape::Validations::CoerceValidator do
478
726
  expect(last_response.body).to eq('3')
479
727
  end
480
728
 
729
+ context 'Integer type and coerce_with potentially returning nil' do
730
+ before do
731
+ subject.params do
732
+ requires :int, type: Integer, coerce_with: (lambda do |val|
733
+ if val == '0'
734
+ nil
735
+ elsif val.match?(/^-?\d+$/)
736
+ val.to_i
737
+ else
738
+ val
739
+ end
740
+ end)
741
+ end
742
+ subject.get '/' do
743
+ params[:int].class.to_s
744
+ end
745
+ end
746
+
747
+ it 'accepts value that coerces to nil' do
748
+ get '/', int: '0'
749
+
750
+ expect(last_response.status).to eq(200)
751
+ expect(last_response.body).to eq('NilClass')
752
+ end
753
+
754
+ it 'coerces to Integer' do
755
+ get '/', int: '1'
756
+
757
+ expect(last_response.status).to eq(200)
758
+ expect(last_response.body).to eq('Integer')
759
+ end
760
+
761
+ it 'returns invalid value if coercion returns a wrong type' do
762
+ get '/', int: 'lol'
763
+
764
+ expect(last_response.status).to eq(400)
765
+ expect(last_response.body).to eq('int is invalid')
766
+ end
767
+ end
768
+
481
769
  it 'must be supplied with :type or :coerce' do
482
770
  expect do
483
771
  subject.params do
@@ -298,4 +298,174 @@ describe Grape::Validations::DefaultValidator do
298
298
  end
299
299
  end
300
300
  end
301
+
302
+ context 'optional with nil as value' do
303
+ subject do
304
+ Class.new(Grape::API) do
305
+ default_format :json
306
+ end
307
+ end
308
+
309
+ def app
310
+ subject
311
+ end
312
+
313
+ context 'primitive types' do
314
+ [
315
+ [Integer, 0],
316
+ [Integer, 42],
317
+ [Float, 0.0],
318
+ [Float, 4.2],
319
+ [BigDecimal, 0.0],
320
+ [BigDecimal, 4.2],
321
+ [Numeric, 0],
322
+ [Numeric, 42],
323
+ [Date, Date.today],
324
+ [DateTime, DateTime.now],
325
+ [Time, Time.now],
326
+ [Time, Time.at(0)],
327
+ [Grape::API::Boolean, false],
328
+ [String, ''],
329
+ [String, 'non-empty-string'],
330
+ [Symbol, :symbol],
331
+ [TrueClass, true],
332
+ [FalseClass, false]
333
+ ].each do |type, default|
334
+ it 'respects the default value' do
335
+ subject.params do
336
+ optional :param, type: type, default: default
337
+ end
338
+ subject.get '/default_value' do
339
+ params[:param]
340
+ end
341
+
342
+ get '/default_value', param: nil
343
+ expect(last_response.status).to eq(200)
344
+ expect(last_response.body).to eq(default.to_json)
345
+ end
346
+ end
347
+ end
348
+
349
+ context 'structures types' do
350
+ [
351
+ [Hash, {}],
352
+ [Hash, { test: 'non-empty' }],
353
+ [Array, []],
354
+ [Array, ['non-empty']],
355
+ [Array[Integer], []],
356
+ [Set, []],
357
+ [Set, [1]]
358
+ ].each do |type, default|
359
+ it 'respects the default value' do
360
+ subject.params do
361
+ optional :param, type: type, default: default
362
+ end
363
+ subject.get '/default_value' do
364
+ params[:param]
365
+ end
366
+
367
+ get '/default_value', param: nil
368
+ expect(last_response.status).to eq(200)
369
+ expect(last_response.body).to eq(default.to_json)
370
+ end
371
+ end
372
+ end
373
+
374
+ context 'special types' do
375
+ [
376
+ [JSON, ''],
377
+ [JSON, { test: 'non-empty-string' }.to_json],
378
+ [Array[JSON], []],
379
+ [Array[JSON], [{ test: 'non-empty-string' }.to_json]],
380
+ [::File, ''],
381
+ [::File, { test: 'non-empty-string' }.to_json],
382
+ [Rack::Multipart::UploadedFile, ''],
383
+ [Rack::Multipart::UploadedFile, { test: 'non-empty-string' }.to_json]
384
+ ].each do |type, default|
385
+ it 'respects the default value' do
386
+ subject.params do
387
+ optional :param, type: type, default: default
388
+ end
389
+ subject.get '/default_value' do
390
+ params[:param]
391
+ end
392
+
393
+ get '/default_value', param: nil
394
+ expect(last_response.status).to eq(200)
395
+ expect(last_response.body).to eq(default.to_json)
396
+ end
397
+ end
398
+ end
399
+
400
+ context 'variant-member-type collections' do
401
+ [
402
+ [Array[Integer, String], [0, '']],
403
+ [Array[Integer, String], [42, 'non-empty-string']],
404
+ [[Integer, String, Array[Integer, String]], [0, '', [0, '']]],
405
+ [[Integer, String, Array[Integer, String]], [42, 'non-empty-string', [42, 'non-empty-string']]]
406
+ ].each do |type, default|
407
+ it 'respects the default value' do
408
+ subject.params do
409
+ optional :param, type: type, default: default
410
+ end
411
+ subject.get '/default_value' do
412
+ params[:param]
413
+ end
414
+
415
+ get '/default_value', param: nil
416
+ expect(last_response.status).to eq(200)
417
+ expect(last_response.body).to eq(default.to_json)
418
+ end
419
+ end
420
+ end
421
+ end
422
+
423
+ context 'array with default values and given conditions' do
424
+ subject do
425
+ Class.new(Grape::API) do
426
+ default_format :json
427
+ end
428
+ end
429
+
430
+ def app
431
+ subject
432
+ end
433
+
434
+ it 'applies the default values only if the conditions are met' do
435
+ subject.params do
436
+ requires :ary, type: Array do
437
+ requires :has_value, type: Grape::API::Boolean
438
+ given has_value: ->(has_value) { has_value } do
439
+ optional :type, type: String, values: %w[str int], default: 'str'
440
+ given type: ->(type) { type == 'str' } do
441
+ optional :str, type: String, default: 'a'
442
+ end
443
+ given type: ->(type) { type == 'int' } do
444
+ optional :int, type: Integer, default: 1
445
+ end
446
+ end
447
+ end
448
+ end
449
+ subject.post('/nested_given_and_default') { declared(self.params) }
450
+
451
+ params = {
452
+ ary: [
453
+ { has_value: false },
454
+ { has_value: true, type: 'int', int: 123 },
455
+ { has_value: true, type: 'str', str: 'b' }
456
+ ]
457
+ }
458
+ expected = {
459
+ 'ary' => [
460
+ { 'has_value' => false, 'type' => nil, 'int' => nil, 'str' => nil },
461
+ { 'has_value' => true, 'type' => 'int', 'int' => 123, 'str' => nil },
462
+ { 'has_value' => true, 'type' => 'str', 'int' => nil, 'str' => 'b' }
463
+ ]
464
+ }
465
+
466
+ post '/nested_given_and_default', params
467
+ expect(last_response.status).to eq(201)
468
+ expect(JSON.parse(last_response.body)).to eq(expected)
469
+ end
470
+ end
301
471
  end