grape 1.5.0 → 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 +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
|