grape 1.3.0 → 1.5.0

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 (94) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +90 -0
  3. data/LICENSE +1 -1
  4. data/README.md +104 -21
  5. data/UPGRADING.md +243 -39
  6. data/lib/grape.rb +4 -5
  7. data/lib/grape/api.rb +4 -4
  8. data/lib/grape/api/instance.rb +32 -31
  9. data/lib/grape/content_types.rb +34 -0
  10. data/lib/grape/dsl/helpers.rb +2 -1
  11. data/lib/grape/dsl/inside_route.rb +76 -42
  12. data/lib/grape/dsl/parameters.rb +4 -4
  13. data/lib/grape/dsl/routing.rb +8 -8
  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/base.rb +0 -4
  18. data/lib/grape/exceptions/validation_errors.rb +11 -12
  19. data/lib/grape/http/headers.rb +26 -0
  20. data/lib/grape/middleware/base.rb +3 -4
  21. data/lib/grape/middleware/error.rb +10 -12
  22. data/lib/grape/middleware/formatter.rb +3 -3
  23. data/lib/grape/middleware/stack.rb +19 -5
  24. data/lib/grape/middleware/versioner/header.rb +4 -4
  25. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
  26. data/lib/grape/middleware/versioner/path.rb +1 -1
  27. data/lib/grape/namespace.rb +12 -2
  28. data/lib/grape/path.rb +13 -3
  29. data/lib/grape/request.rb +13 -8
  30. data/lib/grape/router.rb +26 -30
  31. data/lib/grape/router/attribute_translator.rb +25 -4
  32. data/lib/grape/router/pattern.rb +17 -16
  33. data/lib/grape/router/route.rb +5 -24
  34. data/lib/grape/{serve_file → serve_stream}/file_body.rb +1 -1
  35. data/lib/grape/{serve_file → serve_stream}/sendfile_response.rb +1 -1
  36. data/lib/grape/{serve_file/file_response.rb → serve_stream/stream_response.rb} +8 -8
  37. data/lib/grape/util/base_inheritable.rb +15 -8
  38. data/lib/grape/util/cache.rb +20 -0
  39. data/lib/grape/util/lazy_object.rb +43 -0
  40. data/lib/grape/util/lazy_value.rb +1 -0
  41. data/lib/grape/util/reverse_stackable_values.rb +2 -0
  42. data/lib/grape/util/stackable_values.rb +7 -20
  43. data/lib/grape/validations/params_scope.rb +6 -5
  44. data/lib/grape/validations/types.rb +6 -5
  45. data/lib/grape/validations/types/array_coercer.rb +14 -5
  46. data/lib/grape/validations/types/build_coercer.rb +5 -8
  47. data/lib/grape/validations/types/custom_type_coercer.rb +14 -2
  48. data/lib/grape/validations/types/dry_type_coercer.rb +36 -1
  49. data/lib/grape/validations/types/file.rb +15 -12
  50. data/lib/grape/validations/types/json.rb +40 -36
  51. data/lib/grape/validations/types/primitive_coercer.rb +15 -6
  52. data/lib/grape/validations/types/set_coercer.rb +6 -4
  53. data/lib/grape/validations/types/variant_collection_coercer.rb +1 -1
  54. data/lib/grape/validations/validators/as.rb +1 -1
  55. data/lib/grape/validations/validators/base.rb +2 -4
  56. data/lib/grape/validations/validators/coerce.rb +4 -11
  57. data/lib/grape/validations/validators/default.rb +3 -5
  58. data/lib/grape/validations/validators/exactly_one_of.rb +4 -2
  59. data/lib/grape/validations/validators/except_values.rb +1 -1
  60. data/lib/grape/validations/validators/regexp.rb +1 -1
  61. data/lib/grape/validations/validators/values.rb +1 -1
  62. data/lib/grape/version.rb +1 -1
  63. data/spec/grape/api/instance_spec.rb +50 -0
  64. data/spec/grape/api_spec.rb +82 -6
  65. data/spec/grape/dsl/inside_route_spec.rb +182 -33
  66. data/spec/grape/endpoint/declared_spec.rb +590 -0
  67. data/spec/grape/endpoint_spec.rb +0 -521
  68. data/spec/grape/entity_spec.rb +6 -0
  69. data/spec/grape/exceptions/validation_errors_spec.rb +2 -2
  70. data/spec/grape/integration/rack_sendfile_spec.rb +12 -8
  71. data/spec/grape/middleware/auth/strategies_spec.rb +1 -1
  72. data/spec/grape/middleware/error_spec.rb +1 -1
  73. data/spec/grape/middleware/formatter_spec.rb +3 -3
  74. data/spec/grape/middleware/stack_spec.rb +12 -1
  75. data/spec/grape/path_spec.rb +4 -4
  76. data/spec/grape/validations/instance_behaivour_spec.rb +1 -1
  77. data/spec/grape/validations/params_scope_spec.rb +26 -0
  78. data/spec/grape/validations/types/array_coercer_spec.rb +35 -0
  79. data/spec/grape/validations/types/primitive_coercer_spec.rb +135 -0
  80. data/spec/grape/validations/types/set_coercer_spec.rb +34 -0
  81. data/spec/grape/validations/types_spec.rb +1 -1
  82. data/spec/grape/validations/validators/coerce_spec.rb +329 -77
  83. data/spec/grape/validations/validators/default_spec.rb +170 -0
  84. data/spec/grape/validations/validators/exactly_one_of_spec.rb +12 -12
  85. data/spec/grape/validations/validators/except_values_spec.rb +1 -0
  86. data/spec/grape/validations/validators/values_spec.rb +1 -1
  87. data/spec/grape/validations_spec.rb +30 -30
  88. data/spec/integration/eager_load/eager_load_spec.rb +15 -0
  89. data/spec/spec_helper.rb +3 -10
  90. data/spec/support/chunks.rb +14 -0
  91. data/spec/support/eager_load.rb +19 -0
  92. data/spec/support/versioned_helpers.rb +3 -5
  93. metadata +121 -105
  94. data/lib/grape/util/content_types.rb +0 -28
@@ -280,527 +280,6 @@ describe Grape::Endpoint do
280
280
  end
281
281
  end
282
282
 
283
- describe '#declared' do
284
- before do
285
- subject.format :json
286
- subject.params do
287
- requires :first
288
- optional :second
289
- optional :third, default: 'third-default'
290
- optional :nested, type: Hash do
291
- optional :fourth
292
- optional :fifth
293
- optional :nested_two, type: Hash do
294
- optional :sixth
295
- optional :nested_three, type: Hash do
296
- optional :seventh
297
- end
298
- end
299
- end
300
- optional :nested_arr, type: Array do
301
- optional :eighth
302
- end
303
- end
304
- end
305
-
306
- context 'when params are not built with default class' do
307
- it 'returns an object that corresponds with the params class - hash with indifferent access' do
308
- subject.params do
309
- build_with Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder
310
- end
311
- subject.get '/declared' do
312
- d = declared(params, include_missing: true)
313
- { declared_class: d.class.to_s }
314
- end
315
-
316
- get '/declared?first=present'
317
- expect(JSON.parse(last_response.body)['declared_class']).to eq('ActiveSupport::HashWithIndifferentAccess')
318
- end
319
-
320
- it 'returns an object that corresponds with the params class - hashie mash' do
321
- subject.params do
322
- build_with Grape::Extensions::Hashie::Mash::ParamBuilder
323
- end
324
- subject.get '/declared' do
325
- d = declared(params, include_missing: true)
326
- { declared_class: d.class.to_s }
327
- end
328
-
329
- get '/declared?first=present'
330
- expect(JSON.parse(last_response.body)['declared_class']).to eq('Hashie::Mash')
331
- end
332
-
333
- it 'returns an object that corresponds with the params class - hash' do
334
- subject.params do
335
- build_with Grape::Extensions::Hash::ParamBuilder
336
- end
337
- subject.get '/declared' do
338
- d = declared(params, include_missing: true)
339
- { declared_class: d.class.to_s }
340
- end
341
-
342
- get '/declared?first=present'
343
- expect(JSON.parse(last_response.body)['declared_class']).to eq('Hash')
344
- end
345
- end
346
-
347
- it 'should show nil for nested params if include_missing is true' do
348
- subject.get '/declared' do
349
- declared(params, include_missing: true)
350
- end
351
-
352
- get '/declared?first=present'
353
- expect(last_response.status).to eq(200)
354
- expect(JSON.parse(last_response.body)['nested']['fourth']).to be_nil
355
- end
356
-
357
- it 'does not work in a before filter' do
358
- subject.before do
359
- declared(params)
360
- end
361
- subject.get('/declared') { declared(params) }
362
-
363
- expect { get('/declared') }.to raise_error(
364
- Grape::DSL::InsideRoute::MethodNotYetAvailable
365
- )
366
- end
367
-
368
- it 'has as many keys as there are declared params' do
369
- subject.get '/declared' do
370
- declared(params)
371
- end
372
- get '/declared?first=present'
373
- expect(last_response.status).to eq(200)
374
- expect(JSON.parse(last_response.body).keys.size).to eq(5)
375
- end
376
-
377
- it 'has a optional param with default value all the time' do
378
- subject.get '/declared' do
379
- declared(params)
380
- end
381
- get '/declared?first=one'
382
- expect(last_response.status).to eq(200)
383
- expect(JSON.parse(last_response.body)['third']).to eql('third-default')
384
- end
385
-
386
- it 'builds nested params' do
387
- subject.get '/declared' do
388
- declared(params)
389
- end
390
-
391
- get '/declared?first=present&nested[fourth]=1'
392
- expect(last_response.status).to eq(200)
393
- expect(JSON.parse(last_response.body)['nested'].keys.size).to eq 3
394
- end
395
-
396
- it 'builds nested params when given array' do
397
- subject.get '/dummy' do
398
- end
399
- subject.params do
400
- requires :first
401
- optional :second
402
- optional :third, default: 'third-default'
403
- optional :nested, type: Array do
404
- optional :fourth
405
- end
406
- end
407
- subject.get '/declared' do
408
- declared(params)
409
- end
410
-
411
- get '/declared?first=present&nested[][fourth]=1&nested[][fourth]=2'
412
- expect(last_response.status).to eq(200)
413
- expect(JSON.parse(last_response.body)['nested'].size).to eq 2
414
- end
415
-
416
- context 'sets nested objects when the param is missing' do
417
- it 'to be a hash when include_missing is true' do
418
- subject.get '/declared' do
419
- declared(params, include_missing: true)
420
- end
421
-
422
- get '/declared?first=present'
423
- expect(last_response.status).to eq(200)
424
- expect(JSON.parse(last_response.body)['nested']).to be_a(Hash)
425
- end
426
-
427
- it 'to be an array when include_missing is true' do
428
- subject.get '/declared' do
429
- declared(params, include_missing: true)
430
- end
431
-
432
- get '/declared?first=present'
433
- expect(last_response.status).to eq(200)
434
- expect(JSON.parse(last_response.body)['nested_arr']).to be_a(Array)
435
- end
436
-
437
- it 'to be nil when include_missing is false' do
438
- subject.get '/declared' do
439
- declared(params, include_missing: false)
440
- end
441
-
442
- get '/declared?first=present'
443
- expect(last_response.status).to eq(200)
444
- expect(JSON.parse(last_response.body)['nested']).to be_nil
445
- end
446
- end
447
-
448
- it 'filters out any additional params that are given' do
449
- subject.get '/declared' do
450
- declared(params)
451
- end
452
- get '/declared?first=one&other=two'
453
- expect(last_response.status).to eq(200)
454
- expect(JSON.parse(last_response.body).key?(:other)).to eq false
455
- end
456
-
457
- it 'stringifies if that option is passed' do
458
- subject.get '/declared' do
459
- declared(params, stringify: true)
460
- end
461
-
462
- get '/declared?first=one&other=two'
463
- expect(last_response.status).to eq(200)
464
- expect(JSON.parse(last_response.body)['first']).to eq 'one'
465
- end
466
-
467
- it 'does not include missing attributes if that option is passed' do
468
- subject.get '/declared' do
469
- error! 'expected nil', 400 if declared(params, include_missing: false).key?(:second)
470
- ''
471
- end
472
-
473
- get '/declared?first=one&other=two'
474
- expect(last_response.status).to eq(200)
475
- end
476
-
477
- it 'does not include renamed missing attributes if that option is passed' do
478
- subject.params do
479
- optional :renamed_original, as: :renamed
480
- end
481
- subject.get '/declared' do
482
- error! 'expected nil', 400 if declared(params, include_missing: false).key?(:renamed)
483
- ''
484
- end
485
-
486
- get '/declared?first=one&other=two'
487
- expect(last_response.status).to eq(200)
488
- end
489
-
490
- it 'includes attributes with value that evaluates to false' do
491
- subject.params do
492
- requires :first
493
- optional :boolean
494
- end
495
-
496
- subject.post '/declared' do
497
- error!('expected false', 400) if declared(params, include_missing: false)[:boolean] != false
498
- ''
499
- end
500
-
501
- post '/declared', ::Grape::Json.dump(first: 'one', boolean: false), 'CONTENT_TYPE' => 'application/json'
502
- expect(last_response.status).to eq(201)
503
- end
504
-
505
- it 'includes attributes with value that evaluates to nil' do
506
- subject.params do
507
- requires :first
508
- optional :second
509
- end
510
-
511
- subject.post '/declared' do
512
- error!('expected nil', 400) unless declared(params, include_missing: false)[:second].nil?
513
- ''
514
- end
515
-
516
- post '/declared', ::Grape::Json.dump(first: 'one', second: nil), 'CONTENT_TYPE' => 'application/json'
517
- expect(last_response.status).to eq(201)
518
- end
519
-
520
- it 'includes missing attributes with defaults when there are nested hashes' do
521
- subject.get '/dummy' do
522
- end
523
-
524
- subject.params do
525
- requires :first
526
- optional :second
527
- optional :third, default: nil
528
- optional :nested, type: Hash do
529
- optional :fourth, default: nil
530
- optional :fifth, default: nil
531
- requires :nested_nested, type: Hash do
532
- optional :sixth, default: 'sixth-default'
533
- optional :seven, default: nil
534
- end
535
- end
536
- end
537
-
538
- subject.get '/declared' do
539
- declared(params, include_missing: false)
540
- end
541
-
542
- get '/declared?first=present&nested[fourth]=&nested[nested_nested][sixth]=sixth'
543
- json = JSON.parse(last_response.body)
544
- expect(last_response.status).to eq(200)
545
- expect(json['first']).to eq 'present'
546
- expect(json['nested'].keys).to eq %w[fourth fifth nested_nested]
547
- expect(json['nested']['fourth']).to eq ''
548
- expect(json['nested']['nested_nested'].keys).to eq %w[sixth seven]
549
- expect(json['nested']['nested_nested']['sixth']).to eq 'sixth'
550
- end
551
-
552
- it 'does not include missing attributes when there are nested hashes' do
553
- subject.get '/dummy' do
554
- end
555
-
556
- subject.params do
557
- requires :first
558
- optional :second
559
- optional :third
560
- optional :nested, type: Hash do
561
- optional :fourth
562
- optional :fifth
563
- end
564
- end
565
-
566
- subject.get '/declared' do
567
- declared(params, include_missing: false)
568
- end
569
-
570
- get '/declared?first=present&nested[fourth]=4'
571
- json = JSON.parse(last_response.body)
572
- expect(last_response.status).to eq(200)
573
- expect(json['first']).to eq 'present'
574
- expect(json['nested'].keys).to eq %w[fourth]
575
- expect(json['nested']['fourth']).to eq '4'
576
- end
577
- end
578
-
579
- describe '#declared; call from child namespace' do
580
- before do
581
- subject.format :json
582
- subject.namespace :parent do
583
- params do
584
- requires :parent_name, type: String
585
- end
586
-
587
- namespace ':parent_name' do
588
- params do
589
- requires :child_name, type: String
590
- requires :child_age, type: Integer
591
- end
592
-
593
- namespace ':child_name' do
594
- params do
595
- requires :grandchild_name, type: String
596
- end
597
-
598
- get ':grandchild_name' do
599
- {
600
- 'params' => params,
601
- 'without_parent_namespaces' => declared(params, include_parent_namespaces: false),
602
- 'with_parent_namespaces' => declared(params, include_parent_namespaces: true)
603
- }
604
- end
605
- end
606
- end
607
- end
608
-
609
- get '/parent/foo/bar/baz', child_age: 5, extra: 'hello'
610
- end
611
-
612
- let(:parsed_response) { JSON.parse(last_response.body, symbolize_names: true) }
613
-
614
- it { expect(last_response.status).to eq 200 }
615
-
616
- context 'with include_parent_namespaces: false' do
617
- it 'returns declared parameters only from current namespace' do
618
- expect(parsed_response[:without_parent_namespaces]).to eq(
619
- grandchild_name: 'baz'
620
- )
621
- end
622
- end
623
-
624
- context 'with include_parent_namespaces: true' do
625
- it 'returns declared parameters from every parent namespace' do
626
- expect(parsed_response[:with_parent_namespaces]).to eq(
627
- parent_name: 'foo',
628
- child_name: 'bar',
629
- grandchild_name: 'baz',
630
- child_age: 5
631
- )
632
- end
633
- end
634
-
635
- context 'without declaration' do
636
- it 'returns all requested parameters' do
637
- expect(parsed_response[:params]).to eq(
638
- parent_name: 'foo',
639
- child_name: 'bar',
640
- grandchild_name: 'baz',
641
- child_age: 5,
642
- extra: 'hello'
643
- )
644
- end
645
- end
646
- end
647
-
648
- describe '#declared; from a nested mounted endpoint' do
649
- before do
650
- doubly_mounted = Class.new(Grape::API)
651
- doubly_mounted.namespace :more do
652
- params do
653
- requires :y, type: Integer
654
- end
655
- route_param :y do
656
- get do
657
- {
658
- params: params,
659
- declared_params: declared(params)
660
- }
661
- end
662
- end
663
- end
664
-
665
- mounted = Class.new(Grape::API)
666
- mounted.namespace :another do
667
- params do
668
- requires :mount_space, type: Integer
669
- end
670
- route_param :mount_space do
671
- mount doubly_mounted
672
- end
673
- end
674
-
675
- subject.format :json
676
- subject.namespace :something do
677
- params do
678
- requires :id, type: Integer
679
- end
680
- resource ':id' do
681
- mount mounted
682
- end
683
- end
684
- end
685
-
686
- it 'can access parent attributes' do
687
- get '/something/123/another/456/more/789'
688
- expect(last_response.status).to eq 200
689
- json = JSON.parse(last_response.body, symbolize_names: true)
690
-
691
- # test all three levels of params
692
- expect(json[:declared_params][:y]).to eq 789
693
- expect(json[:declared_params][:mount_space]).to eq 456
694
- expect(json[:declared_params][:id]).to eq 123
695
- end
696
- end
697
-
698
- describe '#declared; mixed nesting' do
699
- before do
700
- subject.format :json
701
- subject.resource :users do
702
- route_param :id, type: Integer, desc: 'ID desc' do
703
- # Adding this causes route_setting(:declared_params) to be nil for the
704
- # get block in namespace 'foo' below
705
- get do
706
- end
707
-
708
- namespace 'foo' do
709
- get do
710
- {
711
- params: params,
712
- declared_params: declared(params),
713
- declared_params_no_parent: declared(params, include_parent_namespaces: false)
714
- }
715
- end
716
- end
717
- end
718
- end
719
- end
720
-
721
- it 'can access parent route_param' do
722
- get '/users/123/foo', bar: 'bar'
723
- expect(last_response.status).to eq 200
724
- json = JSON.parse(last_response.body, symbolize_names: true)
725
-
726
- expect(json[:declared_params][:id]).to eq 123
727
- expect(json[:declared_params_no_parent][:id]).to eq nil
728
- end
729
- end
730
-
731
- describe '#declared; with multiple route_param' do
732
- before do
733
- mounted = Class.new(Grape::API)
734
- mounted.namespace :albums do
735
- get do
736
- declared(params)
737
- end
738
- end
739
-
740
- subject.format :json
741
- subject.namespace :artists do
742
- route_param :id, type: Integer do
743
- get do
744
- declared(params)
745
- end
746
-
747
- params do
748
- requires :filter, type: String
749
- end
750
- get :some_route do
751
- declared(params)
752
- end
753
- end
754
-
755
- route_param :artist_id, type: Integer do
756
- namespace :compositions do
757
- get do
758
- declared(params)
759
- end
760
- end
761
- end
762
-
763
- route_param :compositor_id, type: Integer do
764
- mount mounted
765
- end
766
- end
767
- end
768
-
769
- it 'return only :id without :artist_id' do
770
- get '/artists/1'
771
- json = JSON.parse(last_response.body, symbolize_names: true)
772
-
773
- expect(json.key?(:id)).to be_truthy
774
- expect(json.key?(:artist_id)).not_to be_truthy
775
- end
776
-
777
- it 'return only :artist_id without :id' do
778
- get '/artists/1/compositions'
779
- json = JSON.parse(last_response.body, symbolize_names: true)
780
-
781
- expect(json.key?(:artist_id)).to be_truthy
782
- expect(json.key?(:id)).not_to be_truthy
783
- end
784
-
785
- it 'return :filter and :id parameters in declared for second enpoint inside route_param' do
786
- get '/artists/1/some_route', filter: 'some_filter'
787
- json = JSON.parse(last_response.body, symbolize_names: true)
788
-
789
- expect(json.key?(:filter)).to be_truthy
790
- expect(json.key?(:id)).to be_truthy
791
- expect(json.key?(:artist_id)).not_to be_truthy
792
- end
793
-
794
- it 'return :compositor_id for mounter in route_param' do
795
- get '/artists/1/albums'
796
- json = JSON.parse(last_response.body, symbolize_names: true)
797
-
798
- expect(json.key?(:compositor_id)).to be_truthy
799
- expect(json.key?(:id)).not_to be_truthy
800
- expect(json.key?(:artist_id)).not_to be_truthy
801
- end
802
- end
803
-
804
283
  describe '#params' do
805
284
  it 'is available to the caller' do
806
285
  subject.get('/hey') do