grape 1.5.3 → 1.6.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -0
- data/README.md +23 -3
- data/UPGRADING.md +43 -1
- data/grape.gemspec +5 -5
- data/lib/grape/api/instance.rb +13 -17
- data/lib/grape/api.rb +5 -12
- data/lib/grape/cookies.rb +2 -0
- data/lib/grape/dsl/desc.rb +3 -5
- data/lib/grape/dsl/helpers.rb +6 -4
- data/lib/grape/dsl/inside_route.rb +17 -8
- data/lib/grape/dsl/middleware.rb +4 -4
- data/lib/grape/dsl/parameters.rb +3 -3
- data/lib/grape/dsl/request_response.rb +9 -6
- data/lib/grape/dsl/routing.rb +2 -2
- data/lib/grape/dsl/settings.rb +5 -5
- data/lib/grape/endpoint.rb +20 -35
- data/lib/grape/error_formatter/json.rb +2 -6
- data/lib/grape/error_formatter/xml.rb +2 -6
- data/lib/grape/exceptions/validation.rb +1 -2
- data/lib/grape/formatter/json.rb +1 -0
- data/lib/grape/formatter/serializable_hash.rb +2 -1
- data/lib/grape/formatter/xml.rb +1 -0
- data/lib/grape/middleware/base.rb +2 -0
- data/lib/grape/middleware/formatter.rb +4 -4
- data/lib/grape/middleware/stack.rb +2 -2
- data/lib/grape/middleware/versioner/accept_version_header.rb +3 -5
- data/lib/grape/middleware/versioner/header.rb +6 -4
- data/lib/grape/middleware/versioner/param.rb +1 -0
- data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
- data/lib/grape/middleware/versioner/path.rb +2 -0
- data/lib/grape/path.rb +1 -0
- data/lib/grape/request.rb +1 -0
- data/lib/grape/router/pattern.rb +1 -1
- data/lib/grape/router/route.rb +2 -2
- data/lib/grape/router.rb +6 -0
- data/lib/grape/util/inheritable_setting.rb +1 -3
- data/lib/grape/util/lazy_value.rb +3 -2
- data/lib/grape/validations/params_scope.rb +88 -55
- data/lib/grape/validations/types/custom_type_coercer.rb +1 -0
- data/lib/grape/validations/types/dry_type_coercer.rb +1 -1
- data/lib/grape/validations/types/json.rb +2 -1
- data/lib/grape/validations/types/primitive_coercer.rb +3 -3
- data/lib/grape/validations/validators/all_or_none.rb +1 -0
- data/lib/grape/validations/validators/as.rb +4 -8
- data/lib/grape/validations/validators/at_least_one_of.rb +1 -0
- data/lib/grape/validations/validators/base.rb +4 -1
- data/lib/grape/validations/validators/coerce.rb +1 -5
- data/lib/grape/validations/validators/default.rb +1 -0
- data/lib/grape/validations/validators/exactly_one_of.rb +1 -0
- data/lib/grape/validations/validators/multiple_params_base.rb +2 -0
- data/lib/grape/validations/validators/mutual_exclusion.rb +1 -0
- data/lib/grape/validations/validators/presence.rb +1 -0
- data/lib/grape/validations/validators/regexp.rb +1 -0
- data/lib/grape/validations/validators/same_as.rb +1 -0
- data/lib/grape/validations/validators/values.rb +3 -0
- data/lib/grape/version.rb +1 -1
- data/lib/grape.rb +1 -1
- data/spec/grape/api/custom_validations_spec.rb +1 -0
- data/spec/grape/api/routes_with_requirements_spec.rb +8 -8
- data/spec/grape/api_spec.rb +126 -37
- data/spec/grape/dsl/callbacks_spec.rb +1 -1
- data/spec/grape/dsl/middleware_spec.rb +1 -1
- data/spec/grape/dsl/parameters_spec.rb +1 -0
- data/spec/grape/dsl/routing_spec.rb +1 -1
- data/spec/grape/endpoint/declared_spec.rb +247 -0
- data/spec/grape/endpoint_spec.rb +5 -5
- data/spec/grape/entity_spec.rb +9 -9
- data/spec/grape/middleware/auth/dsl_spec.rb +1 -1
- data/spec/grape/middleware/error_spec.rb +1 -2
- data/spec/grape/middleware/formatter_spec.rb +2 -2
- data/spec/grape/middleware/stack_spec.rb +3 -1
- data/spec/grape/validations/params_scope_spec.rb +37 -3
- data/spec/grape/validations/single_attribute_iterator_spec.rb +1 -1
- data/spec/grape/validations/types/primitive_coercer_spec.rb +2 -2
- data/spec/grape/validations/validators/coerce_spec.rb +13 -10
- data/spec/grape/validations/validators/except_values_spec.rb +2 -2
- data/spec/grape/validations/validators/values_spec.rb +15 -11
- data/spec/grape/validations_spec.rb +54 -42
- data/spec/shared/versioning_examples.rb +2 -2
- data/spec/spec_helper.rb +1 -1
- data/spec/support/basic_auth_encode_helpers.rb +1 -1
- metadata +6 -6
data/spec/grape/api_spec.rb
CHANGED
@@ -610,6 +610,7 @@ describe Grape::API do
|
|
610
610
|
subject.namespace :example do
|
611
611
|
before do
|
612
612
|
raise 'before filter ran twice' if already_run
|
613
|
+
|
613
614
|
already_run = true
|
614
615
|
header 'X-Custom-Header', 'foo'
|
615
616
|
end
|
@@ -650,12 +651,12 @@ describe Grape::API do
|
|
650
651
|
|
651
652
|
put '/example'
|
652
653
|
expect(last_response.status).to eql 405
|
653
|
-
expect(last_response.body).to eq
|
654
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
655
|
-
<error>
|
656
|
-
|
657
|
-
</error>
|
658
|
-
XML
|
654
|
+
expect(last_response.body).to eq <<~XML
|
655
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
656
|
+
<error>
|
657
|
+
<message>405 Not Allowed</message>
|
658
|
+
</error>
|
659
|
+
XML
|
659
660
|
end
|
660
661
|
end
|
661
662
|
|
@@ -749,6 +750,47 @@ XML
|
|
749
750
|
end
|
750
751
|
end
|
751
752
|
|
753
|
+
describe 'when a resource routes by POST, GET, PATCH, PUT, and DELETE' do
|
754
|
+
before do
|
755
|
+
subject.namespace :example do
|
756
|
+
get do
|
757
|
+
'example'
|
758
|
+
end
|
759
|
+
|
760
|
+
patch do
|
761
|
+
'example'
|
762
|
+
end
|
763
|
+
|
764
|
+
post do
|
765
|
+
'example'
|
766
|
+
end
|
767
|
+
|
768
|
+
delete do
|
769
|
+
'example'
|
770
|
+
end
|
771
|
+
|
772
|
+
put do
|
773
|
+
'example'
|
774
|
+
end
|
775
|
+
end
|
776
|
+
options '/example'
|
777
|
+
end
|
778
|
+
|
779
|
+
describe 'it adds an OPTIONS route for namespaced endpoints that' do
|
780
|
+
it 'returns a 204' do
|
781
|
+
expect(last_response.status).to eql 204
|
782
|
+
end
|
783
|
+
|
784
|
+
it 'has an empty body' do
|
785
|
+
expect(last_response.body).to be_blank
|
786
|
+
end
|
787
|
+
|
788
|
+
it 'has an Allow header' do
|
789
|
+
expect(last_response.headers['Allow']).to eql 'OPTIONS, GET, PATCH, POST, DELETE, PUT, HEAD'
|
790
|
+
end
|
791
|
+
end
|
792
|
+
end
|
793
|
+
|
752
794
|
describe 'adds an OPTIONS route for namespaced endpoints that' do
|
753
795
|
before do
|
754
796
|
subject.before { header 'X-Custom-Header', 'foo' }
|
@@ -2106,7 +2148,9 @@ XML
|
|
2106
2148
|
context 'custom errors' do
|
2107
2149
|
before do
|
2108
2150
|
class ConnectionError < RuntimeError; end
|
2151
|
+
|
2109
2152
|
class DatabaseError < RuntimeError; end
|
2153
|
+
|
2110
2154
|
class CommunicationError < StandardError; end
|
2111
2155
|
end
|
2112
2156
|
|
@@ -2262,6 +2306,7 @@ XML
|
|
2262
2306
|
module ApiSpec
|
2263
2307
|
module APIErrors
|
2264
2308
|
class ParentError < StandardError; end
|
2309
|
+
|
2265
2310
|
class ChildError < ParentError; end
|
2266
2311
|
end
|
2267
2312
|
end
|
@@ -3119,10 +3164,10 @@ XML
|
|
3119
3164
|
subject.get 'method'
|
3120
3165
|
|
3121
3166
|
expect(subject.routes.map(&:params)).to eq [{
|
3122
|
-
'group1'
|
3167
|
+
'group1' => { required: true, type: 'Array' },
|
3123
3168
|
'group1[param1]' => { required: false, desc: 'group1 param1 desc' },
|
3124
3169
|
'group1[param2]' => { required: true, desc: 'group1 param2 desc' },
|
3125
|
-
'group2'
|
3170
|
+
'group2' => { required: true, type: 'Array' },
|
3126
3171
|
'group2[param1]' => { required: false, desc: 'group2 param1 desc' },
|
3127
3172
|
'group2[param2]' => { required: true, desc: 'group2 param2 desc' }
|
3128
3173
|
}]
|
@@ -3335,8 +3380,8 @@ XML
|
|
3335
3380
|
mount app
|
3336
3381
|
end
|
3337
3382
|
expect(subject.routes.size).to eq(2)
|
3338
|
-
expect(subject.routes.first.path).to match(%r{
|
3339
|
-
expect(subject.routes.last.path).to match(%r{
|
3383
|
+
expect(subject.routes.first.path).to match(%r{/cool/awesome})
|
3384
|
+
expect(subject.routes.last.path).to match(%r{/cool/sauce})
|
3340
3385
|
end
|
3341
3386
|
|
3342
3387
|
it 'mounts on a path' do
|
@@ -3358,7 +3403,7 @@ XML
|
|
3358
3403
|
APP2.get '/nice' do
|
3359
3404
|
'play'
|
3360
3405
|
end
|
3361
|
-
#
|
3406
|
+
# NOTE: that the reverse won't work, mount from outside-in
|
3362
3407
|
APP3 = subject
|
3363
3408
|
APP3.mount APP1 => '/app1'
|
3364
3409
|
APP1.mount APP2 => '/app2'
|
@@ -3549,6 +3594,7 @@ XML
|
|
3549
3594
|
def self.included(base)
|
3550
3595
|
base.extend(ClassMethods)
|
3551
3596
|
end
|
3597
|
+
|
3552
3598
|
module ClassMethods
|
3553
3599
|
def my_method
|
3554
3600
|
@test = true
|
@@ -3793,12 +3839,12 @@ XML
|
|
3793
3839
|
end
|
3794
3840
|
get '/example'
|
3795
3841
|
expect(last_response.status).to eq(500)
|
3796
|
-
expect(last_response.body).to eq
|
3797
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
3798
|
-
<error>
|
3799
|
-
|
3800
|
-
</error>
|
3801
|
-
XML
|
3842
|
+
expect(last_response.body).to eq <<~XML
|
3843
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
3844
|
+
<error>
|
3845
|
+
<message>cannot convert String to xml</message>
|
3846
|
+
</error>
|
3847
|
+
XML
|
3802
3848
|
end
|
3803
3849
|
it 'hash' do
|
3804
3850
|
subject.get '/example' do
|
@@ -3809,13 +3855,13 @@ XML
|
|
3809
3855
|
end
|
3810
3856
|
get '/example'
|
3811
3857
|
expect(last_response.status).to eq(200)
|
3812
|
-
expect(last_response.body).to eq
|
3813
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
3814
|
-
<hash>
|
3815
|
-
|
3816
|
-
|
3817
|
-
</hash>
|
3818
|
-
XML
|
3858
|
+
expect(last_response.body).to eq <<~XML
|
3859
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
3860
|
+
<hash>
|
3861
|
+
<example1>example1</example1>
|
3862
|
+
<example2>example2</example2>
|
3863
|
+
</hash>
|
3864
|
+
XML
|
3819
3865
|
end
|
3820
3866
|
it 'array' do
|
3821
3867
|
subject.get '/example' do
|
@@ -3823,13 +3869,13 @@ XML
|
|
3823
3869
|
end
|
3824
3870
|
get '/example'
|
3825
3871
|
expect(last_response.status).to eq(200)
|
3826
|
-
expect(last_response.body).to eq
|
3827
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
3828
|
-
<strings type="array">
|
3829
|
-
|
3830
|
-
|
3831
|
-
</strings>
|
3832
|
-
XML
|
3872
|
+
expect(last_response.body).to eq <<~XML
|
3873
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
3874
|
+
<strings type="array">
|
3875
|
+
<string>example1</string>
|
3876
|
+
<string>example2</string>
|
3877
|
+
</strings>
|
3878
|
+
XML
|
3833
3879
|
end
|
3834
3880
|
it 'raised :error from middleware' do
|
3835
3881
|
middleware = Class.new(Grape::Middleware::Base) do
|
@@ -3842,12 +3888,12 @@ XML
|
|
3842
3888
|
end
|
3843
3889
|
get '/'
|
3844
3890
|
expect(last_response.status).to eq(42)
|
3845
|
-
expect(last_response.body).to eq
|
3846
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
3847
|
-
<error>
|
3848
|
-
|
3849
|
-
</error>
|
3850
|
-
XML
|
3891
|
+
expect(last_response.body).to eq <<~XML
|
3892
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
3893
|
+
<error>
|
3894
|
+
<message>Unauthorized</message>
|
3895
|
+
</error>
|
3896
|
+
XML
|
3851
3897
|
end
|
3852
3898
|
end
|
3853
3899
|
end
|
@@ -4040,6 +4086,49 @@ XML
|
|
4040
4086
|
end
|
4041
4087
|
end
|
4042
4088
|
|
4089
|
+
describe '.inherited' do
|
4090
|
+
context 'overriding within class' do
|
4091
|
+
let(:root_api) do
|
4092
|
+
Class.new(Grape::API) do
|
4093
|
+
@bar = 'Hello, world'
|
4094
|
+
|
4095
|
+
def self.inherited(child_api)
|
4096
|
+
super
|
4097
|
+
child_api.instance_variable_set(:@foo, @bar.dup)
|
4098
|
+
end
|
4099
|
+
end
|
4100
|
+
end
|
4101
|
+
|
4102
|
+
let(:child_api) { Class.new(root_api) }
|
4103
|
+
|
4104
|
+
it 'allows overriding the hook' do
|
4105
|
+
expect(child_api.instance_variable_get(:@foo)).to eq('Hello, world')
|
4106
|
+
end
|
4107
|
+
end
|
4108
|
+
|
4109
|
+
context 'overriding via composition' do
|
4110
|
+
module Inherited
|
4111
|
+
def inherited(api)
|
4112
|
+
super
|
4113
|
+
api.instance_variable_set(:@foo, @bar.dup)
|
4114
|
+
end
|
4115
|
+
end
|
4116
|
+
|
4117
|
+
let(:root_api) do
|
4118
|
+
Class.new(Grape::API) do
|
4119
|
+
@bar = 'Hello, world'
|
4120
|
+
extend Inherited
|
4121
|
+
end
|
4122
|
+
end
|
4123
|
+
|
4124
|
+
let(:child_api) { Class.new(root_api) }
|
4125
|
+
|
4126
|
+
it 'allows overriding the hook' do
|
4127
|
+
expect(child_api.instance_variable_get(:@foo)).to eq('Hello, world')
|
4128
|
+
end
|
4129
|
+
end
|
4130
|
+
end
|
4131
|
+
|
4043
4132
|
describe 'const_missing' do
|
4044
4133
|
subject(:grape_api) { Class.new(Grape::API) }
|
4045
4134
|
let(:mounted) do
|
@@ -598,4 +598,251 @@ describe Grape::Endpoint do
|
|
598
598
|
expect(json.key?(:artist_id)).not_to be_truthy
|
599
599
|
end
|
600
600
|
end
|
601
|
+
|
602
|
+
describe 'parameter renaming' do
|
603
|
+
context 'with a deeply nested parameter structure' do
|
604
|
+
let(:params) do
|
605
|
+
{
|
606
|
+
i_a: 'a',
|
607
|
+
i_b: {
|
608
|
+
i_c: 'c',
|
609
|
+
i_d: {
|
610
|
+
i_e: {
|
611
|
+
i_f: 'f',
|
612
|
+
i_g: 'g',
|
613
|
+
i_h: [
|
614
|
+
{
|
615
|
+
i_ha: 'ha1',
|
616
|
+
i_hb: {
|
617
|
+
i_hc: 'c'
|
618
|
+
}
|
619
|
+
},
|
620
|
+
{
|
621
|
+
i_ha: 'ha2',
|
622
|
+
i_hb: {
|
623
|
+
i_hc: 'c'
|
624
|
+
}
|
625
|
+
}
|
626
|
+
]
|
627
|
+
}
|
628
|
+
}
|
629
|
+
}
|
630
|
+
}
|
631
|
+
end
|
632
|
+
let(:declared) do
|
633
|
+
{
|
634
|
+
o_a: 'a',
|
635
|
+
o_b: {
|
636
|
+
o_c: 'c',
|
637
|
+
o_d: {
|
638
|
+
o_e: {
|
639
|
+
o_f: 'f',
|
640
|
+
o_g: 'g',
|
641
|
+
o_h: [
|
642
|
+
{
|
643
|
+
o_ha: 'ha1',
|
644
|
+
o_hb: {
|
645
|
+
o_hc: 'c'
|
646
|
+
}
|
647
|
+
},
|
648
|
+
{
|
649
|
+
o_ha: 'ha2',
|
650
|
+
o_hb: {
|
651
|
+
o_hc: 'c'
|
652
|
+
}
|
653
|
+
}
|
654
|
+
]
|
655
|
+
}
|
656
|
+
}
|
657
|
+
}
|
658
|
+
}
|
659
|
+
end
|
660
|
+
let(:params_keys) do
|
661
|
+
[
|
662
|
+
'i_a',
|
663
|
+
'i_b',
|
664
|
+
'i_b[i_c]',
|
665
|
+
'i_b[i_d]',
|
666
|
+
'i_b[i_d][i_e]',
|
667
|
+
'i_b[i_d][i_e][i_f]',
|
668
|
+
'i_b[i_d][i_e][i_g]',
|
669
|
+
'i_b[i_d][i_e][i_h]',
|
670
|
+
'i_b[i_d][i_e][i_h][i_ha]',
|
671
|
+
'i_b[i_d][i_e][i_h][i_hb]',
|
672
|
+
'i_b[i_d][i_e][i_h][i_hb][i_hc]'
|
673
|
+
]
|
674
|
+
end
|
675
|
+
|
676
|
+
before do
|
677
|
+
subject.format :json
|
678
|
+
subject.params do
|
679
|
+
optional :i_a, type: String, as: :o_a
|
680
|
+
optional :i_b, type: Hash, as: :o_b do
|
681
|
+
optional :i_c, type: String, as: :o_c
|
682
|
+
optional :i_d, type: Hash, as: :o_d do
|
683
|
+
optional :i_e, type: Hash, as: :o_e do
|
684
|
+
optional :i_f, type: String, as: :o_f
|
685
|
+
optional :i_g, type: String, as: :o_g
|
686
|
+
optional :i_h, type: Array, as: :o_h do
|
687
|
+
optional :i_ha, type: String, as: :o_ha
|
688
|
+
optional :i_hb, type: Hash, as: :o_hb do
|
689
|
+
optional :i_hc, type: String, as: :o_hc
|
690
|
+
end
|
691
|
+
end
|
692
|
+
end
|
693
|
+
end
|
694
|
+
end
|
695
|
+
end
|
696
|
+
subject.post '/test' do
|
697
|
+
declared(params, include_missing: false)
|
698
|
+
end
|
699
|
+
subject.post '/test/no-mod' do
|
700
|
+
before = params.to_h
|
701
|
+
declared(params, include_missing: false)
|
702
|
+
after = params.to_h
|
703
|
+
{ before: before, after: after }
|
704
|
+
end
|
705
|
+
end
|
706
|
+
|
707
|
+
it 'generates the correct parameter names for documentation' do
|
708
|
+
expect(subject.routes.first.params.keys).to match(params_keys)
|
709
|
+
end
|
710
|
+
|
711
|
+
it 'maps the renamed parameter correctly' do
|
712
|
+
post '/test', **params
|
713
|
+
expect(JSON.parse(last_response.body, symbolize_names: true)).to \
|
714
|
+
match(declared)
|
715
|
+
end
|
716
|
+
|
717
|
+
it 'maps no parameters when none are given' do
|
718
|
+
post '/test'
|
719
|
+
expect(JSON.parse(last_response.body)).to match({})
|
720
|
+
end
|
721
|
+
|
722
|
+
it 'does not modify the request params' do
|
723
|
+
post '/test/no-mod', **params
|
724
|
+
result = JSON.parse(last_response.body, symbolize_names: true)
|
725
|
+
expect(result[:before]).to match(result[:after])
|
726
|
+
end
|
727
|
+
end
|
728
|
+
|
729
|
+
context 'with a renamed root parameter' do
|
730
|
+
before do
|
731
|
+
subject.format :json
|
732
|
+
subject.params do
|
733
|
+
optional :email_address, type: String, regexp: /.+@.+/, as: :email
|
734
|
+
end
|
735
|
+
subject.post '/test' do
|
736
|
+
declared(params, include_missing: false)
|
737
|
+
end
|
738
|
+
end
|
739
|
+
|
740
|
+
it 'generates the correct parameter names for documentation' do
|
741
|
+
expect(subject.routes.first.params.keys).to match(%w[email_address])
|
742
|
+
end
|
743
|
+
|
744
|
+
it 'maps the renamed parameter correctly (original name)' do
|
745
|
+
post '/test', email_address: 'test@example.com'
|
746
|
+
expect(JSON.parse(last_response.body)).to \
|
747
|
+
match('email' => 'test@example.com')
|
748
|
+
end
|
749
|
+
|
750
|
+
it 'validates the renamed parameter correctly (original name)' do
|
751
|
+
post '/test', email_address: 'bad[at]example.com'
|
752
|
+
expect(JSON.parse(last_response.body)).to \
|
753
|
+
match('error' => 'email_address is invalid')
|
754
|
+
end
|
755
|
+
|
756
|
+
it 'ignores the renamed parameter (as name)' do
|
757
|
+
post '/test', email: 'test@example.com'
|
758
|
+
expect(JSON.parse(last_response.body)).to match({})
|
759
|
+
end
|
760
|
+
end
|
761
|
+
|
762
|
+
context 'with a renamed hash with nested parameters' do
|
763
|
+
before do
|
764
|
+
subject.format :json
|
765
|
+
subject.params do
|
766
|
+
optional :address, type: Hash, as: :address_attributes do
|
767
|
+
optional :street, type: String, values: ['Street 1', 'Street 2'],
|
768
|
+
default: 'Street 1'
|
769
|
+
optional :city, type: String
|
770
|
+
end
|
771
|
+
end
|
772
|
+
subject.post '/test' do
|
773
|
+
declared(params, include_missing: false)
|
774
|
+
end
|
775
|
+
end
|
776
|
+
|
777
|
+
it 'generates the correct parameter names for documentation' do
|
778
|
+
expect(subject.routes.first.params.keys).to \
|
779
|
+
match(%w[address address[street] address[city]])
|
780
|
+
end
|
781
|
+
|
782
|
+
it 'maps the renamed parameter correctly (original name)' do
|
783
|
+
post '/test', address: { city: 'Berlin', street: 'Street 2', t: 't' }
|
784
|
+
expect(JSON.parse(last_response.body)).to \
|
785
|
+
match('address_attributes' => { 'city' => 'Berlin',
|
786
|
+
'street' => 'Street 2' })
|
787
|
+
end
|
788
|
+
|
789
|
+
it 'validates the renamed parameter correctly (original name)' do
|
790
|
+
post '/test', address: { street: 'unknown' }
|
791
|
+
expect(JSON.parse(last_response.body)).to \
|
792
|
+
match('error' => 'address[street] does not have a valid value')
|
793
|
+
end
|
794
|
+
|
795
|
+
it 'ignores the renamed parameter (as name)' do
|
796
|
+
post '/test', address_attributes: { city: 'Berlin', unknown: '1' }
|
797
|
+
expect(JSON.parse(last_response.body)).to match({})
|
798
|
+
end
|
799
|
+
end
|
800
|
+
|
801
|
+
context 'with a renamed hash with nested renamed parameter' do
|
802
|
+
before do
|
803
|
+
subject.format :json
|
804
|
+
subject.params do
|
805
|
+
optional :user, type: Hash, as: :user_attributes do
|
806
|
+
optional :email_address, type: String, regexp: /.+@.+/, as: :email
|
807
|
+
end
|
808
|
+
end
|
809
|
+
subject.post '/test' do
|
810
|
+
declared(params, include_missing: false)
|
811
|
+
end
|
812
|
+
end
|
813
|
+
|
814
|
+
it 'generates the correct parameter names for documentation' do
|
815
|
+
expect(subject.routes.first.params.keys).to \
|
816
|
+
match(%w[user user[email_address]])
|
817
|
+
end
|
818
|
+
|
819
|
+
it 'maps the renamed parameter correctly (original name)' do
|
820
|
+
post '/test', user: { email_address: 'test@example.com' }
|
821
|
+
expect(JSON.parse(last_response.body)).to \
|
822
|
+
match('user_attributes' => { 'email' => 'test@example.com' })
|
823
|
+
end
|
824
|
+
|
825
|
+
it 'validates the renamed parameter correctly (original name)' do
|
826
|
+
post '/test', user: { email_address: 'bad[at]example.com' }
|
827
|
+
expect(JSON.parse(last_response.body)).to \
|
828
|
+
match('error' => 'user[email_address] is invalid')
|
829
|
+
end
|
830
|
+
|
831
|
+
it 'ignores the renamed parameter (as name, 1)' do
|
832
|
+
post '/test', user: { email: 'test@example.com' }
|
833
|
+
expect(JSON.parse(last_response.body)).to \
|
834
|
+
match({ 'user_attributes' => {} })
|
835
|
+
end
|
836
|
+
|
837
|
+
it 'ignores the renamed parameter (as name, 2)' do
|
838
|
+
post '/test', user_attributes: { email_address: 'test@example.com' }
|
839
|
+
expect(JSON.parse(last_response.body)).to match({})
|
840
|
+
end
|
841
|
+
|
842
|
+
it 'ignores the renamed parameter (as name, 3)' do
|
843
|
+
post '/test', user_attributes: { email: 'test@example.com' }
|
844
|
+
expect(JSON.parse(last_response.body)).to match({})
|
845
|
+
end
|
846
|
+
end
|
847
|
+
end
|
601
848
|
end
|
data/spec/grape/endpoint_spec.rb
CHANGED
@@ -150,7 +150,7 @@ describe Grape::Endpoint do
|
|
150
150
|
end
|
151
151
|
it 'includes headers passed as symbols' do
|
152
152
|
env = Rack::MockRequest.env_for('/headers')
|
153
|
-
env[
|
153
|
+
env[:HTTP_SYMBOL_HEADER] = 'Goliath passes symbols'
|
154
154
|
body = read_chunks(subject.call(env)[2]).join
|
155
155
|
expect(JSON.parse(body)['Symbol-Header']).to eq('Goliath passes symbols')
|
156
156
|
end
|
@@ -212,10 +212,10 @@ describe Grape::Endpoint do
|
|
212
212
|
end
|
213
213
|
get '/test', {}, 'HTTP_COOKIE' => 'delete_this_cookie=1; and_this=2'
|
214
214
|
expect(last_response.body).to eq('3')
|
215
|
-
cookies =
|
215
|
+
cookies = last_response.headers['Set-Cookie'].split("\n").map do |set_cookie|
|
216
216
|
cookie = CookieJar::Cookie.from_set_cookie 'http://localhost/test', set_cookie
|
217
217
|
[cookie.name, cookie]
|
218
|
-
end
|
218
|
+
end.to_h
|
219
219
|
expect(cookies.size).to eq(2)
|
220
220
|
%w[and_this delete_this_cookie].each do |cookie_name|
|
221
221
|
cookie = cookies[cookie_name]
|
@@ -236,10 +236,10 @@ describe Grape::Endpoint do
|
|
236
236
|
end
|
237
237
|
get('/test', {}, 'HTTP_COOKIE' => 'delete_this_cookie=1; and_this=2')
|
238
238
|
expect(last_response.body).to eq('3')
|
239
|
-
cookies =
|
239
|
+
cookies = last_response.headers['Set-Cookie'].split("\n").map do |set_cookie|
|
240
240
|
cookie = CookieJar::Cookie.from_set_cookie 'http://localhost/test', set_cookie
|
241
241
|
[cookie.name, cookie]
|
242
|
-
end
|
242
|
+
end.to_h
|
243
243
|
expect(cookies.size).to eq(2)
|
244
244
|
%w[and_this delete_this_cookie].each do |cookie_name|
|
245
245
|
cookie = cookies[cookie_name]
|
data/spec/grape/entity_spec.rb
CHANGED
@@ -238,14 +238,14 @@ describe Grape::Entity do
|
|
238
238
|
get '/example'
|
239
239
|
expect(last_response.status).to eq(200)
|
240
240
|
expect(last_response.headers['Content-type']).to eq('application/xml')
|
241
|
-
expect(last_response.body).to eq
|
242
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
243
|
-
<hash>
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
</hash>
|
248
|
-
XML
|
241
|
+
expect(last_response.body).to eq <<~XML
|
242
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
243
|
+
<hash>
|
244
|
+
<example>
|
245
|
+
<name>johnnyiller</name>
|
246
|
+
</example>
|
247
|
+
</hash>
|
248
|
+
XML
|
249
249
|
end
|
250
250
|
|
251
251
|
it 'presents with json' do
|
@@ -326,7 +326,7 @@ XML
|
|
326
326
|
end
|
327
327
|
get '/example'
|
328
328
|
expect_response_json = {
|
329
|
-
'page'
|
329
|
+
'page' => 1,
|
330
330
|
'user1' => { 'name' => 'user1' },
|
331
331
|
'user2' => { 'name' => 'user2' }
|
332
332
|
}
|
@@ -20,7 +20,7 @@ describe Grape::Middleware::Formatter do
|
|
20
20
|
let(:body) { ['foo'] }
|
21
21
|
it 'calls #to_json since default format is json' do
|
22
22
|
body.instance_eval do
|
23
|
-
def to_json
|
23
|
+
def to_json(*_args)
|
24
24
|
'"bar"'
|
25
25
|
end
|
26
26
|
end
|
@@ -33,7 +33,7 @@ describe Grape::Middleware::Formatter do
|
|
33
33
|
let(:body) { { 'foos' => [{ 'bar' => 'baz' }] } }
|
34
34
|
it 'calls #to_json if the content type is jsonapi' do
|
35
35
|
body.instance_eval do
|
36
|
-
def to_json
|
36
|
+
def to_json(*_args)
|
37
37
|
'{"foos":[{"bar":"baz"}] }'
|
38
38
|
end
|
39
39
|
end
|
@@ -5,7 +5,9 @@ require 'spec_helper'
|
|
5
5
|
describe Grape::Middleware::Stack do
|
6
6
|
module StackSpec
|
7
7
|
class FooMiddleware; end
|
8
|
+
|
8
9
|
class BarMiddleware; end
|
10
|
+
|
9
11
|
class BlockMiddleware
|
10
12
|
attr_reader :block
|
11
13
|
|
@@ -15,7 +17,7 @@ describe Grape::Middleware::Stack do
|
|
15
17
|
end
|
16
18
|
end
|
17
19
|
|
18
|
-
let(:proc) { ->
|
20
|
+
let(:proc) { -> {} }
|
19
21
|
let(:others) { [[:use, StackSpec::BarMiddleware], [:insert_before, StackSpec::BarMiddleware, StackSpec::BlockMiddleware, proc]] }
|
20
22
|
|
21
23
|
subject { Grape::Middleware::Stack.new }
|