grape 1.3.2 → 1.5.2
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 +82 -2
- data/LICENSE +1 -1
- data/README.md +120 -24
- data/UPGRADING.md +220 -39
- data/lib/grape.rb +3 -2
- data/lib/grape/api.rb +3 -3
- data/lib/grape/api/instance.rb +22 -25
- data/lib/grape/dsl/callbacks.rb +1 -1
- data/lib/grape/dsl/helpers.rb +1 -0
- data/lib/grape/dsl/inside_route.rb +70 -37
- data/lib/grape/dsl/parameters.rb +8 -4
- data/lib/grape/dsl/routing.rb +6 -7
- data/lib/grape/dsl/validations.rb +18 -1
- data/lib/grape/eager_load.rb +1 -1
- data/lib/grape/endpoint.rb +8 -6
- data/lib/grape/exceptions/validation.rb +1 -1
- data/lib/grape/exceptions/validation_errors.rb +1 -1
- data/lib/grape/middleware/auth/base.rb +3 -3
- data/lib/grape/middleware/base.rb +3 -2
- data/lib/grape/middleware/error.rb +11 -13
- data/lib/grape/middleware/formatter.rb +3 -3
- data/lib/grape/middleware/stack.rb +8 -1
- data/lib/grape/request.rb +1 -1
- data/lib/grape/router.rb +25 -39
- data/lib/grape/router/attribute_translator.rb +26 -5
- data/lib/grape/router/route.rb +1 -19
- data/lib/grape/{serve_file → serve_stream}/file_body.rb +1 -1
- data/lib/grape/{serve_file → serve_stream}/sendfile_response.rb +1 -1
- data/lib/grape/{serve_file/file_response.rb → serve_stream/stream_response.rb} +8 -8
- data/lib/grape/util/base_inheritable.rb +2 -2
- data/lib/grape/util/lazy_value.rb +1 -0
- 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 +9 -7
- data/lib/grape/validations/single_attribute_iterator.rb +1 -1
- data/lib/grape/validations/types.rb +1 -4
- data/lib/grape/validations/types/array_coercer.rb +14 -5
- data/lib/grape/validations/types/build_coercer.rb +1 -5
- data/lib/grape/validations/types/custom_type_coercer.rb +15 -1
- data/lib/grape/validations/types/dry_type_coercer.rb +36 -1
- data/lib/grape/validations/types/invalid_value.rb +24 -0
- data/lib/grape/validations/types/primitive_coercer.rb +9 -3
- data/lib/grape/validations/types/set_coercer.rb +6 -4
- data/lib/grape/validations/types/variant_collection_coercer.rb +1 -1
- data/lib/grape/validations/validator_factory.rb +1 -1
- data/lib/grape/validations/validators/as.rb +1 -1
- data/lib/grape/validations/validators/base.rb +8 -8
- data/lib/grape/validations/validators/coerce.rb +8 -14
- data/lib/grape/validations/validators/default.rb +3 -5
- data/lib/grape/validations/validators/except_values.rb +1 -1
- data/lib/grape/validations/validators/multiple_params_base.rb +2 -1
- data/lib/grape/validations/validators/values.rb +1 -1
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api/instance_spec.rb +50 -0
- data/spec/grape/api_remount_spec.rb +9 -4
- data/spec/grape/api_spec.rb +75 -0
- data/spec/grape/dsl/inside_route_spec.rb +182 -33
- data/spec/grape/endpoint/declared_spec.rb +601 -0
- data/spec/grape/endpoint_spec.rb +0 -521
- data/spec/grape/entity_spec.rb +7 -1
- data/spec/grape/integration/rack_sendfile_spec.rb +12 -8
- data/spec/grape/middleware/auth/strategies_spec.rb +1 -1
- data/spec/grape/middleware/error_spec.rb +1 -1
- data/spec/grape/middleware/formatter_spec.rb +1 -1
- data/spec/grape/middleware/stack_spec.rb +1 -0
- 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 +26 -0
- data/spec/grape/validations/single_attribute_iterator_spec.rb +17 -6
- data/spec/grape/validations/types/array_coercer_spec.rb +35 -0
- data/spec/grape/validations/types/primitive_coercer_spec.rb +65 -5
- data/spec/grape/validations/types/set_coercer_spec.rb +34 -0
- data/spec/grape/validations/validators/coerce_spec.rb +223 -25
- data/spec/grape/validations/validators/default_spec.rb +170 -0
- data/spec/grape/validations/validators/except_values_spec.rb +1 -0
- data/spec/grape/validations/validators/values_spec.rb +1 -1
- data/spec/grape/validations_spec.rb +290 -18
- data/spec/integration/eager_load/eager_load_spec.rb +15 -0
- data/spec/shared/versioning_examples.rb +20 -20
- data/spec/spec_helper.rb +0 -10
- data/spec/support/chunks.rb +14 -0
- data/spec/support/versioned_helpers.rb +4 -6
- metadata +20 -9
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
|
|
@@ -181,6 +181,7 @@ describe Grape::Entity do
|
|
181
181
|
subject.get '/example' do
|
182
182
|
c = Class.new do
|
183
183
|
attr_reader :id
|
184
|
+
|
184
185
|
def initialize(id)
|
185
186
|
@id = id
|
186
187
|
end
|
@@ -202,6 +203,7 @@ describe Grape::Entity do
|
|
202
203
|
subject.get '/examples' do
|
203
204
|
c = Class.new do
|
204
205
|
attr_reader :id
|
206
|
+
|
205
207
|
def initialize(id)
|
206
208
|
@id = id
|
207
209
|
end
|
@@ -226,6 +228,7 @@ describe Grape::Entity do
|
|
226
228
|
subject.get '/example' do
|
227
229
|
c = Class.new do
|
228
230
|
attr_reader :name
|
231
|
+
|
229
232
|
def initialize(args)
|
230
233
|
@name = args[:name] || 'no name set'
|
231
234
|
end
|
@@ -255,6 +258,7 @@ XML
|
|
255
258
|
subject.get '/example' do
|
256
259
|
c = Class.new do
|
257
260
|
attr_reader :name
|
261
|
+
|
258
262
|
def initialize(args)
|
259
263
|
@name = args[:name] || 'no name set'
|
260
264
|
end
|
@@ -284,6 +288,7 @@ XML
|
|
284
288
|
subject.get '/example' do
|
285
289
|
c = Class.new do
|
286
290
|
attr_reader :name
|
291
|
+
|
287
292
|
def initialize(args)
|
288
293
|
@name = args[:name] || 'no name set'
|
289
294
|
end
|
@@ -302,6 +307,7 @@ XML
|
|
302
307
|
it 'present with multiple entities using optional symbol' do
|
303
308
|
user = Class.new do
|
304
309
|
attr_reader :name
|
310
|
+
|
305
311
|
def initialize(args)
|
306
312
|
@name = args[:name] || 'no name set'
|
307
313
|
end
|
@@ -4,12 +4,16 @@ require 'spec_helper'
|
|
4
4
|
|
5
5
|
describe Rack::Sendfile do
|
6
6
|
subject do
|
7
|
-
|
7
|
+
content_object = file_object
|
8
8
|
app = Class.new(Grape::API) do
|
9
9
|
use Rack::Sendfile
|
10
10
|
format :json
|
11
11
|
get do
|
12
|
-
|
12
|
+
if content_object.is_a?(String)
|
13
|
+
sendfile content_object
|
14
|
+
else
|
15
|
+
stream content_object
|
16
|
+
end
|
13
17
|
end
|
14
18
|
end
|
15
19
|
|
@@ -22,9 +26,9 @@ describe Rack::Sendfile do
|
|
22
26
|
app.call(env)
|
23
27
|
end
|
24
28
|
|
25
|
-
context do
|
26
|
-
let(:
|
27
|
-
|
29
|
+
context 'when calling sendfile' do
|
30
|
+
let(:file_object) do
|
31
|
+
'/accel/mapping/some/path'
|
28
32
|
end
|
29
33
|
|
30
34
|
it 'contains Sendfile headers' do
|
@@ -33,9 +37,9 @@ describe Rack::Sendfile do
|
|
33
37
|
end
|
34
38
|
end
|
35
39
|
|
36
|
-
context do
|
37
|
-
let(:
|
38
|
-
double(:
|
40
|
+
context 'when streaming non file content' do
|
41
|
+
let(:file_object) do
|
42
|
+
double(:file_object, each: nil)
|
39
43
|
end
|
40
44
|
|
41
45
|
it 'not contains Sendfile headers' do
|
@@ -36,7 +36,7 @@ describe Grape::Middleware::Auth::Strategies do
|
|
36
36
|
RSpec::Matchers.define :be_challenge do
|
37
37
|
match do |actual_response|
|
38
38
|
actual_response.status == 401 &&
|
39
|
-
actual_response['WWW-Authenticate']
|
39
|
+
actual_response['WWW-Authenticate'].start_with?('Digest ') &&
|
40
40
|
actual_response.body.empty?
|
41
41
|
end
|
42
42
|
end
|
@@ -380,7 +380,7 @@ describe Grape::Middleware::Formatter do
|
|
380
380
|
|
381
381
|
context 'send file' do
|
382
382
|
let(:file) { double(File) }
|
383
|
-
let(:file_body) { Grape::
|
383
|
+
let(:file_body) { Grape::ServeStream::StreamResponse.new(file) }
|
384
384
|
let(:app) { ->(_env) { [200, {}, file_body] } }
|
385
385
|
|
386
386
|
it 'returns a file response' 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
|
@@ -633,6 +633,32 @@ describe Grape::Validations::ParamsScope do
|
|
633
633
|
expect(last_response.status).to eq(200)
|
634
634
|
end
|
635
635
|
|
636
|
+
it 'detect unmet nested dependency' do
|
637
|
+
subject.params do
|
638
|
+
requires :a, type: String, allow_blank: false, values: %w[x y z]
|
639
|
+
given a: ->(val) { val == 'z' } do
|
640
|
+
requires :inner3, type: Array, allow_blank: false do
|
641
|
+
requires :bar, type: String, allow_blank: false
|
642
|
+
given bar: ->(val) { val == 'b' } do
|
643
|
+
requires :baz, type: Array do
|
644
|
+
optional :baz_category, type: String
|
645
|
+
end
|
646
|
+
end
|
647
|
+
given bar: ->(val) { val == 'c' } do
|
648
|
+
requires :baz, type: Array do
|
649
|
+
requires :baz_category, type: String
|
650
|
+
end
|
651
|
+
end
|
652
|
+
end
|
653
|
+
end
|
654
|
+
end
|
655
|
+
subject.get('/nested-dependency') { declared(params).to_json }
|
656
|
+
|
657
|
+
get '/nested-dependency', a: 'z', inner3: [{ bar: 'c', baz: [{ unrelated: 'nope' }] }]
|
658
|
+
expect(last_response.status).to eq(400)
|
659
|
+
expect(last_response.body).to eq 'inner3[0][baz][0][baz_category] is missing'
|
660
|
+
end
|
661
|
+
|
636
662
|
it 'includes the parameter within #declared(params)' do
|
637
663
|
get '/test', a: true, b: true
|
638
664
|
|
@@ -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
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Grape::Validations::Types::ArrayCoercer do
|
6
|
+
subject { described_class.new(type) }
|
7
|
+
|
8
|
+
describe '#call' do
|
9
|
+
context 'an array of primitives' do
|
10
|
+
let(:type) { Array[String] }
|
11
|
+
|
12
|
+
it 'coerces elements in the array' do
|
13
|
+
expect(subject.call([10, 20])).to eq(%w[10 20])
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'an array of arrays' do
|
18
|
+
let(:type) { Array[Array[Integer]] }
|
19
|
+
|
20
|
+
it 'coerces elements in the nested array' do
|
21
|
+
expect(subject.call([%w[10 20]])).to eq([[10, 20]])
|
22
|
+
expect(subject.call([['10'], ['20']])).to eq([[10], [20]])
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'an array of sets' do
|
27
|
+
let(:type) { Array[Set[Integer]] }
|
28
|
+
|
29
|
+
it 'coerces elements in the nested set' do
|
30
|
+
expect(subject.call([%w[10 20]])).to eq([Set[10, 20]])
|
31
|
+
expect(subject.call([['10'], ['20']])).to eq([Set[10], Set[20]])
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -7,7 +7,19 @@ describe Grape::Validations::Types::PrimitiveCoercer do
|
|
7
7
|
|
8
8
|
subject { described_class.new(type, strict) }
|
9
9
|
|
10
|
-
describe '
|
10
|
+
describe '#call' do
|
11
|
+
context 'BigDecimal' do
|
12
|
+
let(:type) { BigDecimal }
|
13
|
+
|
14
|
+
it 'coerces to BigDecimal' do
|
15
|
+
expect(subject.call(5)).to eq(BigDecimal(5))
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'coerces an empty string to nil' do
|
19
|
+
expect(subject.call('')).to be_nil
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
11
23
|
context 'Boolean' do
|
12
24
|
let(:type) { Grape::API::Boolean }
|
13
25
|
|
@@ -26,6 +38,50 @@ describe Grape::Validations::Types::PrimitiveCoercer do
|
|
26
38
|
it 'returns an error when the given value cannot be coerced' do
|
27
39
|
expect(subject.call(123)).to be_instance_of(Grape::Validations::Types::InvalidValue)
|
28
40
|
end
|
41
|
+
|
42
|
+
it 'coerces an empty string to nil' do
|
43
|
+
expect(subject.call('')).to be_nil
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'DateTime' do
|
48
|
+
let(:type) { DateTime }
|
49
|
+
|
50
|
+
it 'coerces an empty string to nil' do
|
51
|
+
expect(subject.call('')).to be_nil
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'Float' do
|
56
|
+
let(:type) { Float }
|
57
|
+
|
58
|
+
it 'coerces an empty string to nil' do
|
59
|
+
expect(subject.call('')).to be_nil
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context 'Integer' do
|
64
|
+
let(:type) { Integer }
|
65
|
+
|
66
|
+
it 'coerces an empty string to nil' do
|
67
|
+
expect(subject.call('')).to be_nil
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'Numeric' do
|
72
|
+
let(:type) { Numeric }
|
73
|
+
|
74
|
+
it 'coerces an empty string to nil' do
|
75
|
+
expect(subject.call('')).to be_nil
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context 'Time' do
|
80
|
+
let(:type) { Time }
|
81
|
+
|
82
|
+
it 'coerces an empty string to nil' do
|
83
|
+
expect(subject.call('')).to be_nil
|
84
|
+
end
|
29
85
|
end
|
30
86
|
|
31
87
|
context 'String' do
|
@@ -34,13 +90,17 @@ describe Grape::Validations::Types::PrimitiveCoercer do
|
|
34
90
|
it 'coerces to String' do
|
35
91
|
expect(subject.call(10)).to eq('10')
|
36
92
|
end
|
93
|
+
|
94
|
+
it 'does not coerce an empty string to nil' do
|
95
|
+
expect(subject.call('')).to eq('')
|
96
|
+
end
|
37
97
|
end
|
38
98
|
|
39
|
-
context '
|
40
|
-
let(:type) {
|
99
|
+
context 'Symbol' do
|
100
|
+
let(:type) { Symbol }
|
41
101
|
|
42
|
-
it 'coerces to
|
43
|
-
expect(subject.call(
|
102
|
+
it 'coerces an empty string to nil' do
|
103
|
+
expect(subject.call('')).to be_nil
|
44
104
|
end
|
45
105
|
end
|
46
106
|
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Grape::Validations::Types::SetCoercer do
|
6
|
+
subject { described_class.new(type) }
|
7
|
+
|
8
|
+
describe '#call' do
|
9
|
+
context 'a set of primitives' do
|
10
|
+
let(:type) { Set[String] }
|
11
|
+
|
12
|
+
it 'coerces elements to the set' do
|
13
|
+
expect(subject.call([10, 20])).to eq(Set['10', '20'])
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'a set of sets' do
|
18
|
+
let(:type) { Set[Set[Integer]] }
|
19
|
+
|
20
|
+
it 'coerces elements in the nested set' do
|
21
|
+
expect(subject.call([%w[10 20]])).to eq(Set[Set[10, 20]])
|
22
|
+
expect(subject.call([['10'], ['20']])).to eq(Set[Set[10], Set[20]])
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'a set of sets of arrays' do
|
27
|
+
let(:type) { Set[Set[Array[Integer]]] }
|
28
|
+
|
29
|
+
it 'coerces elements in the nested set' do
|
30
|
+
expect(subject.call([[['10'], ['20']]])).to eq(Set[Set[Array[10], Array[20]]])
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -227,23 +227,51 @@ describe Grape::Validations::CoerceValidator do
|
|
227
227
|
expect(last_response.body).to eq('NilClass')
|
228
228
|
end
|
229
229
|
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
230
|
+
context 'a custom type' do
|
231
|
+
it 'coerces the given value' do
|
232
|
+
subject.params do
|
233
|
+
requires :uri, coerce: SecureURIOnly
|
234
|
+
end
|
235
|
+
subject.get '/secure_uri' do
|
236
|
+
params[:uri].class
|
237
|
+
end
|
238
|
+
|
239
|
+
get 'secure_uri', uri: 'https://www.example.com'
|
240
|
+
|
241
|
+
expect(last_response.status).to eq(200)
|
242
|
+
expect(last_response.body).to eq('URI::HTTPS')
|
243
|
+
|
244
|
+
get 'secure_uri', uri: 'http://www.example.com'
|
245
|
+
|
246
|
+
expect(last_response.status).to eq(400)
|
247
|
+
expect(last_response.body).to eq('uri is invalid')
|
236
248
|
end
|
237
249
|
|
238
|
-
|
250
|
+
context 'returning the InvalidValue instance when invalid' do
|
251
|
+
let(:custom_type) do
|
252
|
+
Class.new do
|
253
|
+
def self.parse(_val)
|
254
|
+
Grape::Types::InvalidValue.new('must be unique')
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
239
258
|
|
240
|
-
|
241
|
-
|
259
|
+
it 'uses a custom message added to the invalid value' do
|
260
|
+
type = custom_type
|
242
261
|
|
243
|
-
|
262
|
+
subject.params do
|
263
|
+
requires :name, type: type
|
264
|
+
end
|
265
|
+
subject.get '/whatever' do
|
266
|
+
params[:name].class
|
267
|
+
end
|
244
268
|
|
245
|
-
|
246
|
-
|
269
|
+
get 'whatever', name: 'Bob'
|
270
|
+
|
271
|
+
expect(last_response.status).to eq(400)
|
272
|
+
expect(last_response.body).to eq('name must be unique')
|
273
|
+
end
|
274
|
+
end
|
247
275
|
end
|
248
276
|
|
249
277
|
context 'Array' do
|
@@ -424,6 +452,165 @@ describe Grape::Validations::CoerceValidator do
|
|
424
452
|
expect(last_response.status).to eq(200)
|
425
453
|
expect(last_response.body).to eq(integer_class_name)
|
426
454
|
end
|
455
|
+
|
456
|
+
context 'nil values' do
|
457
|
+
context 'primitive types' do
|
458
|
+
Grape::Validations::Types::PRIMITIVES.each do |type|
|
459
|
+
it 'respects the nil value' do
|
460
|
+
subject.params do
|
461
|
+
requires :param, type: type
|
462
|
+
end
|
463
|
+
subject.get '/nil_value' do
|
464
|
+
params[:param].class
|
465
|
+
end
|
466
|
+
|
467
|
+
get '/nil_value', param: nil
|
468
|
+
expect(last_response.status).to eq(200)
|
469
|
+
expect(last_response.body).to eq('NilClass')
|
470
|
+
end
|
471
|
+
end
|
472
|
+
end
|
473
|
+
|
474
|
+
context 'structures types' do
|
475
|
+
Grape::Validations::Types::STRUCTURES.each do |type|
|
476
|
+
it 'respects the nil value' do
|
477
|
+
subject.params do
|
478
|
+
requires :param, type: type
|
479
|
+
end
|
480
|
+
subject.get '/nil_value' do
|
481
|
+
params[:param].class
|
482
|
+
end
|
483
|
+
|
484
|
+
get '/nil_value', param: nil
|
485
|
+
expect(last_response.status).to eq(200)
|
486
|
+
expect(last_response.body).to eq('NilClass')
|
487
|
+
end
|
488
|
+
end
|
489
|
+
end
|
490
|
+
|
491
|
+
context 'special types' do
|
492
|
+
Grape::Validations::Types::SPECIAL.each_key do |type|
|
493
|
+
it 'respects the nil value' do
|
494
|
+
subject.params do
|
495
|
+
requires :param, type: type
|
496
|
+
end
|
497
|
+
subject.get '/nil_value' do
|
498
|
+
params[:param].class
|
499
|
+
end
|
500
|
+
|
501
|
+
get '/nil_value', param: nil
|
502
|
+
expect(last_response.status).to eq(200)
|
503
|
+
expect(last_response.body).to eq('NilClass')
|
504
|
+
end
|
505
|
+
end
|
506
|
+
|
507
|
+
context 'variant-member-type collections' do
|
508
|
+
[
|
509
|
+
Array[Integer, String],
|
510
|
+
[Integer, String, Array[Integer, String]]
|
511
|
+
].each do |type|
|
512
|
+
it 'respects the nil value' do
|
513
|
+
subject.params do
|
514
|
+
requires :param, type: type
|
515
|
+
end
|
516
|
+
subject.get '/nil_value' do
|
517
|
+
params[:param].class
|
518
|
+
end
|
519
|
+
|
520
|
+
get '/nil_value', param: nil
|
521
|
+
expect(last_response.status).to eq(200)
|
522
|
+
expect(last_response.body).to eq('NilClass')
|
523
|
+
end
|
524
|
+
end
|
525
|
+
end
|
526
|
+
end
|
527
|
+
end
|
528
|
+
|
529
|
+
context 'empty string' do
|
530
|
+
context 'primitive types' do
|
531
|
+
(Grape::Validations::Types::PRIMITIVES - [String]).each do |type|
|
532
|
+
it "is coerced to nil for type #{type}" do
|
533
|
+
subject.params do
|
534
|
+
requires :param, type: type
|
535
|
+
end
|
536
|
+
subject.get '/empty_string' do
|
537
|
+
params[:param].class
|
538
|
+
end
|
539
|
+
|
540
|
+
get '/empty_string', param: ''
|
541
|
+
expect(last_response.status).to eq(200)
|
542
|
+
expect(last_response.body).to eq('NilClass')
|
543
|
+
end
|
544
|
+
end
|
545
|
+
|
546
|
+
it 'is not coerced to nil for type String' do
|
547
|
+
subject.params do
|
548
|
+
requires :param, type: String
|
549
|
+
end
|
550
|
+
subject.get '/empty_string' do
|
551
|
+
params[:param].class
|
552
|
+
end
|
553
|
+
|
554
|
+
get '/empty_string', param: ''
|
555
|
+
expect(last_response.status).to eq(200)
|
556
|
+
expect(last_response.body).to eq('String')
|
557
|
+
end
|
558
|
+
end
|
559
|
+
|
560
|
+
context 'structures types' do
|
561
|
+
(Grape::Validations::Types::STRUCTURES - [Hash]).each do |type|
|
562
|
+
it "is coerced to nil for type #{type}" do
|
563
|
+
subject.params do
|
564
|
+
requires :param, type: type
|
565
|
+
end
|
566
|
+
subject.get '/empty_string' do
|
567
|
+
params[:param].class
|
568
|
+
end
|
569
|
+
|
570
|
+
get '/empty_string', param: ''
|
571
|
+
expect(last_response.status).to eq(200)
|
572
|
+
expect(last_response.body).to eq('NilClass')
|
573
|
+
end
|
574
|
+
end
|
575
|
+
end
|
576
|
+
|
577
|
+
context 'special types' do
|
578
|
+
(Grape::Validations::Types::SPECIAL.keys - [File, Rack::Multipart::UploadedFile]).each do |type|
|
579
|
+
it "is coerced to nil for type #{type}" do
|
580
|
+
subject.params do
|
581
|
+
requires :param, type: type
|
582
|
+
end
|
583
|
+
subject.get '/empty_string' do
|
584
|
+
params[:param].class
|
585
|
+
end
|
586
|
+
|
587
|
+
get '/empty_string', param: ''
|
588
|
+
expect(last_response.status).to eq(200)
|
589
|
+
expect(last_response.body).to eq('NilClass')
|
590
|
+
end
|
591
|
+
end
|
592
|
+
|
593
|
+
context 'variant-member-type collections' do
|
594
|
+
[
|
595
|
+
Array[Integer, String],
|
596
|
+
[Integer, String, Array[Integer, String]]
|
597
|
+
].each do |type|
|
598
|
+
it "is coerced to nil for type #{type}" do
|
599
|
+
subject.params do
|
600
|
+
requires :param, type: type
|
601
|
+
end
|
602
|
+
subject.get '/empty_string' do
|
603
|
+
params[:param].class
|
604
|
+
end
|
605
|
+
|
606
|
+
get '/empty_string', param: ''
|
607
|
+
expect(last_response.status).to eq(200)
|
608
|
+
expect(last_response.body).to eq('NilClass')
|
609
|
+
end
|
610
|
+
end
|
611
|
+
end
|
612
|
+
end
|
613
|
+
end
|
427
614
|
end
|
428
615
|
|
429
616
|
context 'using coerce_with' do
|
@@ -461,6 +648,30 @@ describe Grape::Validations::CoerceValidator do
|
|
461
648
|
expect(JSON.parse(last_response.body)).to eq(%w[a b c d])
|
462
649
|
end
|
463
650
|
|
651
|
+
it 'parses parameters with Array[Array[String]] type and coerce_with' do
|
652
|
+
subject.params do
|
653
|
+
requires :values, type: Array[Array[String]], coerce_with: ->(val) { val.is_a?(String) ? [val.split(/,/).map(&:strip)] : val }
|
654
|
+
end
|
655
|
+
subject.post '/coerce_nested_strings' do
|
656
|
+
params[:values]
|
657
|
+
end
|
658
|
+
|
659
|
+
post '/coerce_nested_strings', ::Grape::Json.dump(values: 'a,b,c,d'), 'CONTENT_TYPE' => 'application/json'
|
660
|
+
expect(last_response.status).to eq(201)
|
661
|
+
expect(JSON.parse(last_response.body)).to eq([%w[a b c d]])
|
662
|
+
|
663
|
+
post '/coerce_nested_strings', ::Grape::Json.dump(values: [%w[a c], %w[b]]), 'CONTENT_TYPE' => 'application/json'
|
664
|
+
expect(last_response.status).to eq(201)
|
665
|
+
expect(JSON.parse(last_response.body)).to eq([%w[a c], %w[b]])
|
666
|
+
|
667
|
+
post '/coerce_nested_strings', ::Grape::Json.dump(values: [[]]), 'CONTENT_TYPE' => 'application/json'
|
668
|
+
expect(last_response.status).to eq(201)
|
669
|
+
expect(JSON.parse(last_response.body)).to eq([[]])
|
670
|
+
|
671
|
+
post '/coerce_nested_strings', ::Grape::Json.dump(values: [['a', { bar: 0 }], ['b']]), 'CONTENT_TYPE' => 'application/json'
|
672
|
+
expect(last_response.status).to eq(400)
|
673
|
+
end
|
674
|
+
|
464
675
|
it 'parses parameters with Array[Integer] type' do
|
465
676
|
subject.params do
|
466
677
|
requires :values, type: Array[Integer], coerce_with: ->(val) { val.split(/\s+/).map(&:to_i) }
|
@@ -752,19 +963,6 @@ describe Grape::Validations::CoerceValidator do
|
|
752
963
|
expect(last_response.body).to eq('String')
|
753
964
|
end
|
754
965
|
|
755
|
-
it 'respects nil values' do
|
756
|
-
subject.params do
|
757
|
-
optional :a, types: [File, String]
|
758
|
-
end
|
759
|
-
subject.get '/' do
|
760
|
-
params[:a].class.to_s
|
761
|
-
end
|
762
|
-
|
763
|
-
get '/', a: nil
|
764
|
-
expect(last_response.status).to eq(200)
|
765
|
-
expect(last_response.body).to eq('NilClass')
|
766
|
-
end
|
767
|
-
|
768
966
|
it 'fails when no coercion is possible' do
|
769
967
|
subject.params do
|
770
968
|
requires :a, types: [Boolean, Integer]
|