grape 1.3.1 → 1.5.1

Sign up to get free protection for your applications and to get access to all the features.
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