grape 1.3.0 → 1.5.0

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