grape 1.5.0 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +51 -2
- data/README.md +43 -10
- data/UPGRADING.md +91 -0
- data/grape.gemspec +5 -5
- data/lib/grape/api/instance.rb +13 -17
- data/lib/grape/api.rb +7 -14
- data/lib/grape/cookies.rb +2 -0
- data/lib/grape/dsl/callbacks.rb +1 -1
- data/lib/grape/dsl/desc.rb +3 -5
- data/lib/grape/dsl/helpers.rb +6 -4
- data/lib/grape/dsl/inside_route.rb +18 -9
- data/lib/grape/dsl/middleware.rb +4 -4
- data/lib/grape/dsl/parameters.rb +11 -7
- data/lib/grape/dsl/request_response.rb +9 -6
- data/lib/grape/dsl/routing.rb +7 -6
- data/lib/grape/dsl/settings.rb +5 -5
- data/lib/grape/endpoint.rb +21 -36
- data/lib/grape/error_formatter/json.rb +2 -6
- data/lib/grape/error_formatter/xml.rb +2 -6
- data/lib/grape/exceptions/empty_message_body.rb +11 -0
- data/lib/grape/exceptions/validation.rb +2 -3
- data/lib/grape/exceptions/validation_errors.rb +1 -1
- 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/locale/en.yml +1 -1
- data/lib/grape/middleware/auth/base.rb +3 -3
- data/lib/grape/middleware/base.rb +4 -2
- data/lib/grape/middleware/error.rb +1 -1
- data/lib/grape/middleware/formatter.rb +4 -4
- data/lib/grape/middleware/stack.rb +10 -16
- 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/parser/json.rb +1 -1
- data/lib/grape/parser/xml.rb +1 -1
- data/lib/grape/path.rb +1 -0
- data/lib/grape/request.rb +3 -0
- data/lib/grape/router/attribute_translator.rb +1 -1
- 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/attributes_iterator.rb +8 -0
- data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
- data/lib/grape/validations/params_scope.rb +92 -58
- data/lib/grape/validations/single_attribute_iterator.rb +1 -1
- data/lib/grape/validations/types/custom_type_coercer.rb +3 -2
- data/lib/grape/validations/types/dry_type_coercer.rb +1 -1
- data/lib/grape/validations/types/invalid_value.rb +24 -0
- data/lib/grape/validations/types/json.rb +2 -1
- data/lib/grape/validations/types/primitive_coercer.rb +3 -3
- data/lib/grape/validations/types.rb +1 -4
- data/lib/grape/validations/validator_factory.rb +1 -1
- 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 +12 -7
- data/lib/grape/validations/validators/coerce.rb +8 -9
- 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 +5 -2
- 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 +3 -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_remount_spec.rb +9 -4
- data/spec/grape/api_spec.rb +203 -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 +259 -1
- data/spec/grape/endpoint_spec.rb +18 -5
- data/spec/grape/entity_spec.rb +10 -10
- 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 +4 -3
- data/spec/grape/request_spec.rb +1 -1
- data/spec/grape/validations/multiple_attributes_iterator_spec.rb +13 -3
- data/spec/grape/validations/params_scope_spec.rb +37 -3
- data/spec/grape/validations/single_attribute_iterator_spec.rb +17 -6
- data/spec/grape/validations/types/primitive_coercer_spec.rb +2 -2
- data/spec/grape/validations/validators/coerce_spec.rb +129 -22
- 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 +280 -0
- data/spec/shared/versioning_examples.rb +22 -22
- data/spec/spec_helper.rb +1 -1
- data/spec/support/basic_auth_encode_helpers.rb +1 -1
- data/spec/support/versioned_helpers.rb +1 -1
- metadata +8 -6
@@ -16,6 +16,7 @@ describe Grape::Endpoint do
|
|
16
16
|
requires :first
|
17
17
|
optional :second
|
18
18
|
optional :third, default: 'third-default'
|
19
|
+
optional :multiple_types, types: [Integer, String]
|
19
20
|
optional :nested, type: Hash do
|
20
21
|
optional :fourth
|
21
22
|
optional :fifth
|
@@ -96,6 +97,16 @@ describe Grape::Endpoint do
|
|
96
97
|
expect(JSON.parse(last_response.body)['nested']['fourth']).to be_nil
|
97
98
|
end
|
98
99
|
|
100
|
+
it 'should show nil for multiple allowed types if include_missing is true' do
|
101
|
+
subject.get '/declared' do
|
102
|
+
declared(params, include_missing: true)
|
103
|
+
end
|
104
|
+
|
105
|
+
get '/declared?first=present'
|
106
|
+
expect(last_response.status).to eq(200)
|
107
|
+
expect(JSON.parse(last_response.body)['multiple_types']).to be_nil
|
108
|
+
end
|
109
|
+
|
99
110
|
it 'does not work in a before filter' do
|
100
111
|
subject.before do
|
101
112
|
declared(params)
|
@@ -113,7 +124,7 @@ describe Grape::Endpoint do
|
|
113
124
|
end
|
114
125
|
get '/declared?first=present'
|
115
126
|
expect(last_response.status).to eq(200)
|
116
|
-
expect(JSON.parse(last_response.body).keys.size).to eq(
|
127
|
+
expect(JSON.parse(last_response.body).keys.size).to eq(11)
|
117
128
|
end
|
118
129
|
|
119
130
|
it 'has a optional param with default value all the time' do
|
@@ -587,4 +598,251 @@ describe Grape::Endpoint do
|
|
587
598
|
expect(json.key?(:artist_id)).not_to be_truthy
|
588
599
|
end
|
589
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
|
590
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]
|
@@ -420,6 +420,19 @@ describe Grape::Endpoint do
|
|
420
420
|
expect(last_response.status).to eq(201)
|
421
421
|
expect(last_response.body).to eq('Bob')
|
422
422
|
end
|
423
|
+
|
424
|
+
# Rack swallowed this error until v2.2.0
|
425
|
+
it 'returns a 400 if given an invalid multipart body', if: Gem::Version.new(Rack.release) >= Gem::Version.new('2.2.0') do
|
426
|
+
subject.params do
|
427
|
+
requires :file, type: Rack::Multipart::UploadedFile
|
428
|
+
end
|
429
|
+
subject.post '/upload' do
|
430
|
+
params[:file][:filename]
|
431
|
+
end
|
432
|
+
post '/upload', { file: '' }, 'CONTENT_TYPE' => 'multipart/form-data; boundary=foobar'
|
433
|
+
expect(last_response.status).to eq(400)
|
434
|
+
expect(last_response.body).to eq('Empty message body supplied with multipart/form-data; boundary=foobar content-type')
|
435
|
+
end
|
423
436
|
end
|
424
437
|
|
425
438
|
it 'responds with a 415 for an unsupported content-type' do
|
data/spec/grape/entity_spec.rb
CHANGED
@@ -116,7 +116,7 @@ describe Grape::Entity do
|
|
116
116
|
expect(last_response.body).to eq('Auto-detect!')
|
117
117
|
end
|
118
118
|
|
119
|
-
it 'does not run autodetection for Entity when
|
119
|
+
it 'does not run autodetection for Entity when explicitly provided' do
|
120
120
|
entity = Class.new(Grape::Entity)
|
121
121
|
some_array = []
|
122
122
|
|
@@ -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 }
|
@@ -35,8 +37,7 @@ describe Grape::Middleware::Stack do
|
|
35
37
|
expect { subject.use StackSpec::BarMiddleware, false, my_arg: 42 }
|
36
38
|
.to change { subject.size }.by(1)
|
37
39
|
expect(subject.last).to eq(StackSpec::BarMiddleware)
|
38
|
-
expect(subject.last.args).to eq([false])
|
39
|
-
expect(subject.last.opts).to eq(my_arg: 42)
|
40
|
+
expect(subject.last.args).to eq([false, { my_arg: 42 }])
|
40
41
|
end
|
41
42
|
|
42
43
|
it 'pushes a middleware class with block arguments onto the stack' do
|
data/spec/grape/request_spec.rb
CHANGED
@@ -75,7 +75,7 @@ module Grape
|
|
75
75
|
Grape.config.reset
|
76
76
|
end
|
77
77
|
|
78
|
-
subject(:request_params) { Grape::Request.new(env, opts).params }
|
78
|
+
subject(:request_params) { Grape::Request.new(env, **opts).params }
|
79
79
|
|
80
80
|
context 'when the API does not include a specific param builder' do
|
81
81
|
let(:opts) { {} }
|
@@ -13,8 +13,8 @@ describe Grape::Validations::MultipleAttributesIterator do
|
|
13
13
|
{ first: 'string', second: 'string' }
|
14
14
|
end
|
15
15
|
|
16
|
-
it 'yields the whole params hash without the list of attrs' do
|
17
|
-
expect { |b| iterator.each(&b) }.to yield_with_args(params)
|
16
|
+
it 'yields the whole params hash and the skipped flag without the list of attrs' do
|
17
|
+
expect { |b| iterator.each(&b) }.to yield_with_args(params, false)
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
@@ -24,7 +24,17 @@ describe Grape::Validations::MultipleAttributesIterator do
|
|
24
24
|
end
|
25
25
|
|
26
26
|
it 'yields each element of the array without the list of attrs' do
|
27
|
-
expect { |b| iterator.each(&b) }.to yield_successive_args(params[0], params[1])
|
27
|
+
expect { |b| iterator.each(&b) }.to yield_successive_args([params[0], false], [params[1], false])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'when params is empty optional placeholder' do
|
32
|
+
let(:params) do
|
33
|
+
[Grape::DSL::Parameters::EmptyOptionalValue, { first: 'string2', second: 'string2' }]
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'yields each element of the array without the list of attrs' do
|
37
|
+
expect { |b| iterator.each(&b) }.to yield_successive_args([Grape::DSL::Parameters::EmptyOptionalValue, true], [params[1], false])
|
28
38
|
end
|
29
39
|
end
|
30
40
|
end
|
@@ -98,6 +98,7 @@ describe Grape::Validations::ParamsScope do
|
|
98
98
|
|
99
99
|
def self.parse(value)
|
100
100
|
raise if value == 'invalid'
|
101
|
+
|
101
102
|
new(value)
|
102
103
|
end
|
103
104
|
|
@@ -144,7 +145,7 @@ describe Grape::Validations::ParamsScope do
|
|
144
145
|
get '/renaming-coerced', foo: ' there we go '
|
145
146
|
|
146
147
|
expect(last_response.status).to eq(200)
|
147
|
-
expect(last_response.body).to eq('there we go
|
148
|
+
expect(last_response.body).to eq('-there we go')
|
148
149
|
end
|
149
150
|
|
150
151
|
it do
|
@@ -180,6 +181,28 @@ describe Grape::Validations::ParamsScope do
|
|
180
181
|
expect(last_response.status).to eq(200)
|
181
182
|
expect(last_response.body).to eq('{"baz":{"qux":"any"}}')
|
182
183
|
end
|
184
|
+
|
185
|
+
it 'renaming can be defined before default' do
|
186
|
+
subject.params do
|
187
|
+
optional :foo, as: :bar, default: 'before'
|
188
|
+
end
|
189
|
+
subject.get('/rename-before-default') { declared(params)[:bar] }
|
190
|
+
get '/rename-before-default'
|
191
|
+
|
192
|
+
expect(last_response.status).to eq(200)
|
193
|
+
expect(last_response.body).to eq('before')
|
194
|
+
end
|
195
|
+
|
196
|
+
it 'renaming can be defined after default' do
|
197
|
+
subject.params do
|
198
|
+
optional :foo, default: 'after', as: :bar
|
199
|
+
end
|
200
|
+
subject.get('/rename-after-default') { declared(params)[:bar] }
|
201
|
+
get '/rename-after-default'
|
202
|
+
|
203
|
+
expect(last_response.status).to eq(200)
|
204
|
+
expect(last_response.body).to eq('after')
|
205
|
+
end
|
183
206
|
end
|
184
207
|
|
185
208
|
context 'array without coerce type explicitly given' do
|
@@ -568,7 +591,7 @@ describe Grape::Validations::ParamsScope do
|
|
568
591
|
it 'allows renaming of dependent on parameter' do
|
569
592
|
subject.params do
|
570
593
|
optional :a, as: :b
|
571
|
-
given
|
594
|
+
given a: ->(val) { val == 'x' } do
|
572
595
|
requires :c
|
573
596
|
end
|
574
597
|
end
|
@@ -582,7 +605,7 @@ describe Grape::Validations::ParamsScope do
|
|
582
605
|
expect(last_response.status).to eq 200
|
583
606
|
end
|
584
607
|
|
585
|
-
it '
|
608
|
+
it 'does not raise if the dependent parameter is not the renamed one' do
|
586
609
|
expect do
|
587
610
|
subject.params do
|
588
611
|
optional :a, as: :b
|
@@ -590,6 +613,17 @@ describe Grape::Validations::ParamsScope do
|
|
590
613
|
requires :c
|
591
614
|
end
|
592
615
|
end
|
616
|
+
end.not_to raise_error
|
617
|
+
end
|
618
|
+
|
619
|
+
it 'raises an error if the dependent parameter is the renamed one' do
|
620
|
+
expect do
|
621
|
+
subject.params do
|
622
|
+
optional :a, as: :b
|
623
|
+
given :b do
|
624
|
+
requires :c
|
625
|
+
end
|
626
|
+
end
|
593
627
|
end.to raise_error(Grape::Exceptions::UnknownParameter)
|
594
628
|
end
|
595
629
|
|
@@ -15,7 +15,7 @@ describe Grape::Validations::SingleAttributeIterator do
|
|
15
15
|
|
16
16
|
it 'yields params and every single attribute from the list' do
|
17
17
|
expect { |b| iterator.each(&b) }
|
18
|
-
.to yield_successive_args([params, :first, false], [params, :second, false])
|
18
|
+
.to yield_successive_args([params, :first, false, false], [params, :second, false, false])
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
@@ -26,8 +26,8 @@ describe Grape::Validations::SingleAttributeIterator do
|
|
26
26
|
|
27
27
|
it 'yields every single attribute from the list for each of the array elements' do
|
28
28
|
expect { |b| iterator.each(&b) }.to yield_successive_args(
|
29
|
-
[params[0], :first, false], [params[0], :second, false],
|
30
|
-
[params[1], :first, false], [params[1], :second, false]
|
29
|
+
[params[0], :first, false, false], [params[0], :second, false, false],
|
30
|
+
[params[1], :first, false, false], [params[1], :second, false, false]
|
31
31
|
)
|
32
32
|
end
|
33
33
|
|
@@ -36,9 +36,20 @@ describe Grape::Validations::SingleAttributeIterator do
|
|
36
36
|
|
37
37
|
it 'marks params with empty values' do
|
38
38
|
expect { |b| iterator.each(&b) }.to yield_successive_args(
|
39
|
-
[params[0], :first, true], [params[0], :second, true],
|
40
|
-
[params[1], :first, true], [params[1], :second, true],
|
41
|
-
[params[2], :first, false], [params[2], :second, false]
|
39
|
+
[params[0], :first, true, false], [params[0], :second, true, false],
|
40
|
+
[params[1], :first, true, false], [params[1], :second, true, false],
|
41
|
+
[params[2], :first, false, false], [params[2], :second, false, false]
|
42
|
+
)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'when missing optional value' do
|
47
|
+
let(:params) { [Grape::DSL::Parameters::EmptyOptionalValue, 10] }
|
48
|
+
|
49
|
+
it 'marks params with skipped values' do
|
50
|
+
expect { |b| iterator.each(&b) }.to yield_successive_args(
|
51
|
+
[params[0], :first, false, true], [params[0], :second, false, true],
|
52
|
+
[params[1], :first, false, false], [params[1], :second, false, false]
|
42
53
|
)
|
43
54
|
end
|
44
55
|
end
|
@@ -12,7 +12,7 @@ describe Grape::Validations::Types::PrimitiveCoercer do
|
|
12
12
|
let(:type) { BigDecimal }
|
13
13
|
|
14
14
|
it 'coerces to BigDecimal' do
|
15
|
-
expect(subject.call(5)).to eq(BigDecimal(5))
|
15
|
+
expect(subject.call(5)).to eq(BigDecimal('5'))
|
16
16
|
end
|
17
17
|
|
18
18
|
it 'coerces an empty string to nil' do
|
@@ -127,7 +127,7 @@ describe Grape::Validations::Types::PrimitiveCoercer do
|
|
127
127
|
end
|
128
128
|
|
129
129
|
it 'returns a value as it is when the given value is BigDecimal' do
|
130
|
-
expect(subject.call(BigDecimal(0))).to eq(BigDecimal(0))
|
130
|
+
expect(subject.call(BigDecimal('0'))).to eq(BigDecimal('0'))
|
131
131
|
end
|
132
132
|
end
|
133
133
|
end
|