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