grape 0.19.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of grape might be problematic. Click here for more details.

Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/Appraisals +8 -0
  3. data/CHANGELOG.md +40 -22
  4. data/Gemfile +1 -0
  5. data/Gemfile.lock +58 -59
  6. data/LICENSE +1 -1
  7. data/README.md +94 -49
  8. data/Rakefile +1 -0
  9. data/UPGRADING.md +89 -0
  10. data/benchmark/simple_with_type_coercer.rb +22 -0
  11. data/gemfiles/multi_json.gemfile +36 -0
  12. data/gemfiles/multi_xml.gemfile +36 -0
  13. data/gemfiles/rack_1.5.2.gemfile +1 -0
  14. data/gemfiles/rack_edge.gemfile +1 -0
  15. data/gemfiles/rails_3.gemfile +1 -0
  16. data/gemfiles/rails_4.gemfile +1 -0
  17. data/gemfiles/rails_5.gemfile +1 -0
  18. data/gemfiles/rails_edge.gemfile +1 -0
  19. data/grape.gemspec +0 -3
  20. data/lib/grape.rb +40 -17
  21. data/lib/grape/dsl/helpers.rb +32 -18
  22. data/lib/grape/dsl/inside_route.rb +2 -2
  23. data/lib/grape/dsl/parameters.rb +26 -0
  24. data/lib/grape/dsl/routing.rb +1 -1
  25. data/lib/grape/dsl/settings.rb +1 -1
  26. data/lib/grape/endpoint.rb +20 -16
  27. data/lib/grape/error_formatter/json.rb +1 -1
  28. data/lib/grape/error_formatter/txt.rb +1 -1
  29. data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +26 -0
  30. data/lib/grape/extensions/deep_hash_with_indifferent_access.rb +18 -0
  31. data/lib/grape/extensions/deep_mergeable_hash.rb +19 -0
  32. data/lib/grape/extensions/deep_symbolize_hash.rb +30 -0
  33. data/lib/grape/extensions/hash.rb +23 -0
  34. data/lib/grape/extensions/hashie/mash.rb +24 -0
  35. data/lib/grape/formatter/json.rb +1 -1
  36. data/lib/grape/formatter/serializable_hash.rb +2 -2
  37. data/lib/grape/locale/en.yml +1 -1
  38. data/lib/grape/middleware/globals.rb +1 -1
  39. data/lib/grape/parser/json.rb +2 -2
  40. data/lib/grape/parser/xml.rb +2 -2
  41. data/lib/grape/request.rb +11 -10
  42. data/lib/grape/util/json.rb +8 -0
  43. data/lib/grape/util/xml.rb +8 -0
  44. data/lib/grape/validations.rb +4 -0
  45. data/lib/grape/validations/params_scope.rb +77 -39
  46. data/lib/grape/validations/types/build_coercer.rb +27 -0
  47. data/lib/grape/validations/types/custom_type_coercer.rb +18 -4
  48. data/lib/grape/validations/types/file.rb +2 -3
  49. data/lib/grape/validations/validator_factory.rb +18 -0
  50. data/lib/grape/validations/validators/base.rb +4 -5
  51. data/lib/grape/validations/validators/coerce.rb +4 -0
  52. data/lib/grape/validations/validators/except_values.rb +20 -0
  53. data/lib/grape/validations/validators/values.rb +25 -5
  54. data/lib/grape/version.rb +1 -1
  55. data/spec/grape/api/invalid_format_spec.rb +3 -3
  56. data/spec/grape/api_spec.rb +28 -16
  57. data/spec/grape/dsl/helpers_spec.rb +25 -6
  58. data/spec/grape/endpoint_spec.rb +117 -13
  59. data/spec/grape/extensions/param_builders/hash_spec.rb +83 -0
  60. data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +105 -0
  61. data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +79 -0
  62. data/spec/grape/middleware/formatter_spec.rb +6 -2
  63. data/spec/grape/request_spec.rb +13 -3
  64. data/spec/grape/validations/instance_behaivour_spec.rb +44 -0
  65. data/spec/grape/validations/params_scope_spec.rb +23 -0
  66. data/spec/grape/validations/types_spec.rb +19 -0
  67. data/spec/grape/validations/validators/coerce_spec.rb +117 -8
  68. data/spec/grape/validations/validators/except_values_spec.rb +191 -0
  69. data/spec/grape/validations/validators/values_spec.rb +78 -0
  70. data/spec/integration/multi_json/json_spec.rb +7 -0
  71. data/spec/integration/multi_xml/xml_spec.rb +7 -0
  72. metadata +30 -46
  73. data/pkg/grape-0.18.0.gem +0 -0
@@ -0,0 +1,83 @@
1
+ require 'spec_helper'
2
+
3
+ describe Grape::Extensions::Hash::ParamBuilder do
4
+ subject { Class.new(Grape::API) }
5
+
6
+ def app
7
+ subject
8
+ end
9
+
10
+ describe 'in an endpoint' do
11
+ context '#params' do
12
+ before do
13
+ subject.params do
14
+ build_with Grape::Extensions::Hash::ParamBuilder
15
+ end
16
+
17
+ subject.get do
18
+ params.class
19
+ end
20
+ end
21
+
22
+ it 'should be of type Hash' do
23
+ get '/'
24
+ expect(last_response.status).to eq(200)
25
+ expect(last_response.body).to eq('Hash')
26
+ end
27
+ end
28
+ end
29
+
30
+ describe 'in an api' do
31
+ before do
32
+ subject.send(:include, Grape::Extensions::Hash::ParamBuilder)
33
+ end
34
+
35
+ context '#params' do
36
+ before do
37
+ subject.get do
38
+ params.class
39
+ end
40
+ end
41
+
42
+ it 'should be Hash' do
43
+ get '/'
44
+ expect(last_response.status).to eq(200)
45
+ expect(last_response.body).to eq('Hash')
46
+ end
47
+ end
48
+
49
+ it 'symbolizes params keys' do
50
+ subject.params do
51
+ optional :a, type: Hash do
52
+ optional :b, type: Hash do
53
+ optional :c, type: String
54
+ end
55
+ optional :d, type: Array
56
+ end
57
+ end
58
+
59
+ subject.get '/foo' do
60
+ [params[:a][:b][:c], params[:a][:d]]
61
+ end
62
+
63
+ get '/foo', 'a' => { b: { c: 'bar' }, 'd' => ['foo'] }
64
+ expect(last_response.status).to eq(200)
65
+ expect(last_response.body).to eq('["bar", ["foo"]]')
66
+ end
67
+
68
+ it 'symbolizes the params' do
69
+ subject.params do
70
+ build_with Grape::Extensions::Hash::ParamBuilder
71
+ requires :a, type: String
72
+ end
73
+
74
+ subject.get '/foo' do
75
+ [params[:a], params['a']]
76
+ end
77
+
78
+ get '/foo', a: 'bar'
79
+ expect(last_response.status).to eq(200)
80
+ expect(last_response.body).to eq('["bar", nil]')
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,105 @@
1
+ require 'spec_helper'
2
+
3
+ describe Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder do
4
+ subject { Class.new(Grape::API) }
5
+
6
+ def app
7
+ subject
8
+ end
9
+
10
+ describe 'in an endpoint' do
11
+ context '#params' do
12
+ before do
13
+ subject.params do
14
+ build_with Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder
15
+ end
16
+
17
+ subject.get do
18
+ params.class
19
+ end
20
+ end
21
+
22
+ it 'should be of type Hash' do
23
+ get '/'
24
+ expect(last_response.status).to eq(200)
25
+ expect(last_response.body).to eq('ActiveSupport::HashWithIndifferentAccess')
26
+ end
27
+ end
28
+ end
29
+
30
+ describe 'in an api' do
31
+ before do
32
+ subject.send(:include, Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder)
33
+ end
34
+
35
+ context '#params' do
36
+ before do
37
+ subject.get do
38
+ params.class
39
+ end
40
+ end
41
+
42
+ it 'is a Hash' do
43
+ get '/'
44
+ expect(last_response.status).to eq(200)
45
+ expect(last_response.body).to eq('ActiveSupport::HashWithIndifferentAccess')
46
+ end
47
+
48
+ it 'parses sub hash params' do
49
+ subject.params do
50
+ build_with Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder
51
+
52
+ optional :a, type: Hash do
53
+ optional :b, type: Hash do
54
+ optional :c, type: String
55
+ end
56
+ optional :d, type: Array
57
+ end
58
+ end
59
+
60
+ subject.get '/foo' do
61
+ [params[:a]['b'][:c], params['a'][:d]]
62
+ end
63
+
64
+ get '/foo', a: { b: { c: 'bar' }, d: ['foo'] }
65
+ expect(last_response.status).to eq(200)
66
+ expect(last_response.body).to eq('["bar", ["foo"]]')
67
+ end
68
+
69
+ it 'params are indifferent to symbol or string keys' do
70
+ subject.params do
71
+ build_with Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder
72
+ optional :a, type: Hash do
73
+ optional :b, type: Hash do
74
+ optional :c, type: String
75
+ end
76
+ optional :d, type: Array
77
+ end
78
+ end
79
+
80
+ subject.get '/foo' do
81
+ [params[:a]['b'][:c], params['a'][:d]]
82
+ end
83
+
84
+ get '/foo', 'a' => { b: { c: 'bar' }, 'd' => ['foo'] }
85
+ expect(last_response.status).to eq(200)
86
+ expect(last_response.body).to eq('["bar", ["foo"]]')
87
+ end
88
+
89
+ it 'responds to string keys' do
90
+ subject.params do
91
+ build_with Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder
92
+ requires :a, type: String
93
+ end
94
+
95
+ subject.get '/foo' do
96
+ [params[:a], params['a']]
97
+ end
98
+
99
+ get '/foo', a: 'bar'
100
+ expect(last_response.status).to eq(200)
101
+ expect(last_response.body).to eq('["bar", "bar"]')
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,79 @@
1
+ require 'spec_helper'
2
+
3
+ describe Grape::Extensions::Hashie::Mash::ParamBuilder do
4
+ subject { Class.new(Grape::API) }
5
+
6
+ def app
7
+ subject
8
+ end
9
+
10
+ describe 'in an endpoint' do
11
+ context '#params' do
12
+ before do
13
+ subject.params do
14
+ build_with Grape::Extensions::Hashie::Mash::ParamBuilder
15
+ end
16
+
17
+ subject.get do
18
+ params.class
19
+ end
20
+ end
21
+
22
+ it 'should be of type Hashie::Mash' do
23
+ get '/'
24
+ expect(last_response.status).to eq(200)
25
+ expect(last_response.body).to eq('Hashie::Mash')
26
+ end
27
+ end
28
+ end
29
+
30
+ describe 'in an api' do
31
+ before do
32
+ subject.send(:include, Grape::Extensions::Hashie::Mash::ParamBuilder)
33
+ end
34
+
35
+ context '#params' do
36
+ before do
37
+ subject.get do
38
+ params.class
39
+ end
40
+ end
41
+
42
+ it 'should be Hashie::Mash' do
43
+ get '/'
44
+ expect(last_response.status).to eq(200)
45
+ expect(last_response.body).to eq('Hashie::Mash')
46
+ end
47
+ end
48
+
49
+ context 'in a nested namespace api' do
50
+ before do
51
+ subject.namespace :foo do
52
+ get do
53
+ params.class
54
+ end
55
+ end
56
+ end
57
+
58
+ it 'should be Hashie::Mash' do
59
+ get '/foo'
60
+ expect(last_response.status).to eq(200)
61
+ expect(last_response.body).to eq('Hashie::Mash')
62
+ end
63
+ end
64
+
65
+ it 'is indifferent to key or symbol access' do
66
+ subject.params do
67
+ build_with Grape::Extensions::Hashie::Mash::ParamBuilder
68
+ requires :a, type: String
69
+ end
70
+ subject.get '/foo' do
71
+ [params[:a], params['a']]
72
+ end
73
+
74
+ get '/foo', a: 'bar'
75
+ expect(last_response.status).to eq(200)
76
+ expect(last_response.body).to eq('["bar", "bar"]')
77
+ end
78
+ end
79
+ end
@@ -11,7 +11,7 @@ describe Grape::Middleware::Formatter do
11
11
  let(:body) { { 'abc' => 'def' } }
12
12
  it 'looks at the bodies for possibly serializable data' do
13
13
  _, _, bodies = *subject.call('PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/json')
14
- bodies.each { |b| expect(b).to eq(MultiJson.dump(body)) }
14
+ bodies.each { |b| expect(b).to eq(::Grape::Json.dump(body)) }
15
15
  end
16
16
 
17
17
  context 'default format' do
@@ -274,7 +274,11 @@ describe Grape::Middleware::Formatter do
274
274
  'rack.input' => io,
275
275
  'CONTENT_LENGTH' => io.length
276
276
  )
277
- expect(subject.env['rack.request.form_hash']['thing']['name']).to eq('Test')
277
+ if Object.const_defined? :MultiXml
278
+ expect(subject.env['rack.request.form_hash']['thing']['name']).to eq('Test')
279
+ else
280
+ expect(subject.env['rack.request.form_hash']['thing']['name']['__content__']).to eq('Test')
281
+ end
278
282
  end
279
283
  [Rack::Request::FORM_DATA_MEDIA_TYPES, Rack::Request::PARSEABLE_DATA_MEDIA_TYPES].flatten.each do |content_type|
280
284
  it "ignores #{content_type}" do
@@ -30,8 +30,18 @@ module Grape
30
30
  }
31
31
  end
32
32
 
33
- it 'returns params' do
34
- expect(request.params).to eq('a' => '123', 'b' => 'xyz')
33
+ it 'by default returns stringified parameter keys' do
34
+ expect(request.params).to eq(ActiveSupport::HashWithIndifferentAccess.new('a' => '123', 'b' => 'xyz'))
35
+ end
36
+
37
+ context 'when build_params_with: Grape::Extensions::Hash::ParamBuilder is specified' do
38
+ let(:request) do
39
+ Grape::Request.new(env, build_params_with: Grape::Extensions::Hash::ParamBuilder)
40
+ end
41
+
42
+ it 'returns symbolized params' do
43
+ expect(request.params).to eq(a: '123', b: 'xyz')
44
+ end
35
45
  end
36
46
 
37
47
  describe 'with grape.routing_args' do
@@ -47,7 +57,7 @@ module Grape
47
57
  end
48
58
 
49
59
  it 'cuts version and route_info' do
50
- expect(request.params).to eq('a' => '123', 'b' => 'xyz', 'c' => 'ccc')
60
+ expect(request.params).to eq(ActiveSupport::HashWithIndifferentAccess.new(a: '123', b: 'xyz', c: 'ccc'))
51
61
  end
52
62
  end
53
63
  end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Validator with instance variables' do
4
+ let(:validator_type) do
5
+ Class.new(Grape::Validations::Base) do
6
+ def validate_param!(_attr_name, _params)
7
+ if @instance_variable
8
+ raise Grape::Exceptions::Validation, params: ['params'],
9
+ message: 'This should never happen'
10
+ end
11
+ @instance_variable = true
12
+ end
13
+ end
14
+ end
15
+
16
+ before do
17
+ Grape::Validations.register_validator('instance_validator', validator_type)
18
+ end
19
+
20
+ after do
21
+ Grape::Validations.deregister_validator('instance_validator')
22
+ end
23
+
24
+ let(:app) do
25
+ Class.new(Grape::API) do
26
+ params do
27
+ optional :param_to_validate, instance_validator: true
28
+ optional :another_param_to_validate, instance_validator: true
29
+ end
30
+ get do
31
+ 'noop'
32
+ end
33
+ end
34
+ end
35
+
36
+ it 'passes validation every time' do
37
+ expect(validator_type).to receive(:new).exactly(4).times.and_call_original
38
+
39
+ 2.times do
40
+ get '/', param_to_validate: 'value', another_param_to_validate: 'value'
41
+ expect(last_response.status).to eq 200
42
+ end
43
+ end
44
+ end
@@ -473,6 +473,29 @@ describe Grape::Validations::ParamsScope do
473
473
  end
474
474
  end
475
475
 
476
+ context 'when validations are dependent on a parameter within an array param' do
477
+ before do
478
+ subject.params do
479
+ requires :foos, type: Array do
480
+ optional :foo_type, :baz_type
481
+ given :foo_type do
482
+ requires :bar
483
+ end
484
+ end
485
+ end
486
+ subject.post('/test') { declared(params).to_json }
487
+ end
488
+
489
+ it 'applies the constraint within each value' do
490
+ post '/test',
491
+ { foos: [{ foo_type: 'a' }, { baz_type: 'c' }] }.to_json,
492
+ 'CONTENT_TYPE' => 'application/json'
493
+
494
+ expect(last_response.status).to eq(400)
495
+ expect(last_response.body).to eq('foos[0][bar] is missing')
496
+ end
497
+ end
498
+
476
499
  context 'when validations are dependent on a parameter with specific value' do
477
500
  # build test cases from all combinations of declarations and options
478
501
  a_decls = %i(optional requires)
@@ -90,4 +90,23 @@ describe Grape::Validations::Types do
90
90
  expect(described_class.custom?(TypesSpec::BarType)).to be_falsy
91
91
  end
92
92
  end
93
+
94
+ describe '::build_coercer' do
95
+ it 'has internal cache variables' do
96
+ expect(described_class.instance_variable_get(:@__cache)).to be_a(Hash)
97
+ expect(described_class.instance_variable_get(:@__cache_write_lock)).to be_a(Mutex)
98
+ end
99
+
100
+ it 'caches the result of the Virtus::Attribute.build method' do
101
+ original_cache = described_class.instance_variable_get(:@__cache)
102
+ described_class.instance_variable_set(:@__cache, {})
103
+
104
+ coercer = 'TestCoercer'
105
+ expect(Virtus::Attribute).to receive(:build).once.and_return(coercer)
106
+ expect(described_class.build_coercer(Array[String])).to eq(coercer)
107
+ expect(described_class.build_coercer(Array[String])).to eq(coercer)
108
+
109
+ described_class.instance_variable_set(:@__cache, original_cache)
110
+ end
111
+ end
93
112
  end
@@ -280,12 +280,49 @@ describe Grape::Validations::CoerceValidator do
280
280
  expect(last_response.body).to eq('TrueClass')
281
281
  end
282
282
 
283
+ it 'Boolean' do
284
+ subject.params do
285
+ optional :boolean, type: Boolean, default: true
286
+ end
287
+ subject.get '/boolean' do
288
+ params[:boolean].class
289
+ end
290
+
291
+ get '/boolean'
292
+ expect(last_response.status).to eq(200)
293
+ expect(last_response.body).to eq('TrueClass')
294
+
295
+ get '/boolean', boolean: true
296
+ expect(last_response.status).to eq(200)
297
+ expect(last_response.body).to eq('TrueClass')
298
+
299
+ get '/boolean', boolean: false
300
+ expect(last_response.status).to eq(200)
301
+ expect(last_response.body).to eq('FalseClass')
302
+
303
+ get '/boolean', boolean: 'true'
304
+ expect(last_response.status).to eq(200)
305
+ expect(last_response.body).to eq('TrueClass')
306
+
307
+ get '/boolean', boolean: 'false'
308
+ expect(last_response.status).to eq(200)
309
+ expect(last_response.body).to eq('FalseClass')
310
+
311
+ get '/boolean', boolean: 123
312
+ expect(last_response.status).to eq(400)
313
+ expect(last_response.body).to eq('boolean is invalid')
314
+
315
+ get '/boolean', boolean: '123'
316
+ expect(last_response.status).to eq(400)
317
+ expect(last_response.body).to eq('boolean is invalid')
318
+ end
319
+
283
320
  it 'Rack::Multipart::UploadedFile' do
284
321
  subject.params do
285
322
  requires :file, type: Rack::Multipart::UploadedFile
286
323
  end
287
324
  subject.post '/upload' do
288
- params[:file].filename
325
+ params[:file][:filename]
289
326
  end
290
327
 
291
328
  post '/upload', file: Rack::Test::UploadedFile.new(__FILE__)
@@ -302,7 +339,7 @@ describe Grape::Validations::CoerceValidator do
302
339
  requires :file, coerce: File
303
340
  end
304
341
  subject.post '/upload' do
305
- params[:file].filename
342
+ params[:file][:filename]
306
343
  end
307
344
 
308
345
  post '/upload', file: Rack::Test::UploadedFile.new(__FILE__)
@@ -625,7 +662,7 @@ describe Grape::Validations::CoerceValidator do
625
662
 
626
663
  get '/', a: %w(the other)
627
664
  expect(last_response.status).to eq(200)
628
- expect(last_response.body).to eq('#<Hashie::Array ["the", "other"]>')
665
+ expect(last_response.body).to eq('["the", "other"]')
629
666
 
630
667
  get '/', a: { a: 1, b: 2 }
631
668
  expect(last_response.status).to eq(400)
@@ -633,27 +670,27 @@ describe Grape::Validations::CoerceValidator do
633
670
 
634
671
  get '/', a: [1, 2, 3]
635
672
  expect(last_response.status).to eq(200)
636
- expect(last_response.body).to eq('#<Hashie::Array ["1", "2", "3"]>')
673
+ expect(last_response.body).to eq('["1", "2", "3"]')
637
674
  end
638
675
 
639
676
  it 'allows multiple collection types' do
640
677
  get '/', b: [1, 2, 3]
641
678
  expect(last_response.status).to eq(200)
642
- expect(last_response.body).to eq('#<Hashie::Array [1, 2, 3]>')
679
+ expect(last_response.body).to eq('[1, 2, 3]')
643
680
 
644
681
  get '/', b: %w(1 2 3)
645
682
  expect(last_response.status).to eq(200)
646
- expect(last_response.body).to eq('#<Hashie::Array [1, 2, 3]>')
683
+ expect(last_response.body).to eq('[1, 2, 3]')
647
684
 
648
685
  get '/', b: [1, true, 'three']
649
686
  expect(last_response.status).to eq(200)
650
- expect(last_response.body).to eq('#<Hashie::Array ["1", "true", "three"]>')
687
+ expect(last_response.body).to eq('["1", "true", "three"]')
651
688
  end
652
689
 
653
690
  it 'allows collections with multiple types' do
654
691
  get '/', c: [1, '2', true, 'three']
655
692
  expect(last_response.status).to eq(200)
656
- expect(last_response.body).to eq('#<Hashie::Array [1, 2, "true", "three"]>')
693
+ expect(last_response.body).to eq('[1, 2, "true", "three"]')
657
694
 
658
695
  get '/', d: '1'
659
696
  expect(last_response.status).to eq(200)
@@ -669,6 +706,78 @@ describe Grape::Validations::CoerceValidator do
669
706
  end
670
707
  end
671
708
 
709
+ context 'when params is Hashie::Mash' do
710
+ context 'for primitive collections' do
711
+ before do
712
+ subject.params do
713
+ build_with Grape::Extensions::Hashie::Mash::ParamBuilder
714
+ optional :a, types: [String, Array[String]]
715
+ optional :b, types: [Array[Integer], Array[String]]
716
+ optional :c, type: Array[Integer, String]
717
+ optional :d, types: [Integer, String, Set[Integer, String]]
718
+ end
719
+ subject.get '/' do
720
+ (
721
+ params.a ||
722
+ params.b ||
723
+ params.c ||
724
+ params.d
725
+ ).inspect
726
+ end
727
+ end
728
+
729
+ it 'allows singular form declaration' do
730
+ get '/', a: 'one way'
731
+ expect(last_response.status).to eq(200)
732
+ expect(last_response.body).to eq('"one way"')
733
+
734
+ get '/', a: %w(the other)
735
+ expect(last_response.status).to eq(200)
736
+ expect(last_response.body).to eq('#<Hashie::Array ["the", "other"]>')
737
+
738
+ get '/', a: { a: 1, b: 2 }
739
+ expect(last_response.status).to eq(400)
740
+ expect(last_response.body).to eq('a is invalid')
741
+
742
+ get '/', a: [1, 2, 3]
743
+ expect(last_response.status).to eq(200)
744
+ expect(last_response.body).to eq('#<Hashie::Array ["1", "2", "3"]>')
745
+ end
746
+
747
+ it 'allows multiple collection types' do
748
+ get '/', b: [1, 2, 3]
749
+ expect(last_response.status).to eq(200)
750
+ expect(last_response.body).to eq('#<Hashie::Array [1, 2, 3]>')
751
+
752
+ get '/', b: %w(1 2 3)
753
+ expect(last_response.status).to eq(200)
754
+ expect(last_response.body).to eq('#<Hashie::Array [1, 2, 3]>')
755
+
756
+ get '/', b: [1, true, 'three']
757
+ expect(last_response.status).to eq(200)
758
+ expect(last_response.body).to eq('#<Hashie::Array ["1", "true", "three"]>')
759
+ end
760
+
761
+ it 'allows collections with multiple types' do
762
+ get '/', c: [1, '2', true, 'three']
763
+ expect(last_response.status).to eq(200)
764
+ expect(last_response.body).to eq('#<Hashie::Array [1, 2, "true", "three"]>')
765
+
766
+ get '/', d: '1'
767
+ expect(last_response.status).to eq(200)
768
+ expect(last_response.body).to eq('1')
769
+
770
+ get '/', d: 'one'
771
+ expect(last_response.status).to eq(200)
772
+ expect(last_response.body).to eq('"one"')
773
+
774
+ get '/', d: %w(1 two)
775
+ expect(last_response.status).to eq(200)
776
+ expect(last_response.body).to eq('#<Set: {1, "two"}>')
777
+ end
778
+ end
779
+ end
780
+
672
781
  context 'custom coercion rules' do
673
782
  before do
674
783
  subject.params do