grape 1.3.0 → 1.3.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 +38 -0
- data/README.md +4 -6
- data/UPGRADING.md +61 -16
- data/lib/grape/api/instance.rb +11 -7
- data/lib/grape/api.rb +2 -2
- data/lib/grape/content_types.rb +34 -0
- data/lib/grape/dsl/helpers.rb +1 -1
- data/lib/grape/dsl/inside_route.rb +10 -9
- data/lib/grape/dsl/parameters.rb +4 -4
- data/lib/grape/dsl/routing.rb +6 -4
- data/lib/grape/exceptions/base.rb +0 -4
- data/lib/grape/exceptions/validation_errors.rb +11 -12
- data/lib/grape/http/headers.rb +26 -0
- data/lib/grape/middleware/base.rb +1 -3
- data/lib/grape/middleware/stack.rb +2 -1
- data/lib/grape/middleware/versioner/header.rb +4 -4
- data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
- data/lib/grape/middleware/versioner/path.rb +1 -1
- data/lib/grape/namespace.rb +12 -2
- data/lib/grape/path.rb +13 -3
- data/lib/grape/request.rb +12 -7
- data/lib/grape/router/pattern.rb +17 -16
- data/lib/grape/router/route.rb +4 -5
- data/lib/grape/router.rb +24 -14
- data/lib/grape/util/base_inheritable.rb +13 -6
- data/lib/grape/util/cache.rb +20 -0
- data/lib/grape/util/lazy_object.rb +43 -0
- data/lib/grape/util/reverse_stackable_values.rb +2 -0
- data/lib/grape/util/stackable_values.rb +7 -20
- data/lib/grape/validations/params_scope.rb +1 -1
- data/lib/grape/validations/types/build_coercer.rb +4 -3
- data/lib/grape/validations/types/custom_type_coercer.rb +1 -1
- data/lib/grape/validations/types/file.rb +15 -12
- data/lib/grape/validations/types/json.rb +40 -36
- data/lib/grape/validations/types/primitive_coercer.rb +6 -3
- data/lib/grape/validations/types.rb +6 -5
- data/lib/grape/validations/validators/coerce.rb +14 -12
- data/lib/grape/validations/validators/exactly_one_of.rb +4 -2
- data/lib/grape/validations/validators/regexp.rb +1 -1
- data/lib/grape/version.rb +1 -1
- data/lib/grape.rb +2 -3
- data/spec/grape/api_spec.rb +7 -6
- data/spec/grape/exceptions/validation_errors_spec.rb +2 -2
- data/spec/grape/middleware/formatter_spec.rb +2 -2
- data/spec/grape/middleware/stack_spec.rb +9 -0
- data/spec/grape/path_spec.rb +4 -4
- data/spec/grape/validations/instance_behaivour_spec.rb +1 -1
- data/spec/grape/validations/types/primitive_coercer_spec.rb +75 -0
- data/spec/grape/validations/types_spec.rb +1 -1
- data/spec/grape/validations/validators/coerce_spec.rb +160 -78
- data/spec/grape/validations/validators/exactly_one_of_spec.rb +12 -12
- data/spec/grape/validations_spec.rb +8 -12
- data/spec/spec_helper.rb +3 -0
- data/spec/support/eager_load.rb +19 -0
- metadata +12 -6
- data/lib/grape/util/content_types.rb +0 -28
@@ -42,7 +42,6 @@ module Grape
|
|
42
42
|
Grape::API::Boolean,
|
43
43
|
String,
|
44
44
|
Symbol,
|
45
|
-
Rack::Multipart::UploadedFile,
|
46
45
|
TrueClass,
|
47
46
|
FalseClass
|
48
47
|
].freeze
|
@@ -54,8 +53,7 @@ module Grape
|
|
54
53
|
Set
|
55
54
|
].freeze
|
56
55
|
|
57
|
-
#
|
58
|
-
# and type-checking logic.
|
56
|
+
# Special custom types provided by Grape.
|
59
57
|
SPECIAL = {
|
60
58
|
JSON => Json,
|
61
59
|
Array[JSON] => JsonArray,
|
@@ -130,7 +128,6 @@ module Grape
|
|
130
128
|
!primitive?(type) &&
|
131
129
|
!structure?(type) &&
|
132
130
|
!multiple?(type) &&
|
133
|
-
!special?(type) &&
|
134
131
|
type.respond_to?(:parse) &&
|
135
132
|
type.method(:parse).arity == 1
|
136
133
|
end
|
@@ -143,7 +140,11 @@ module Grape
|
|
143
140
|
def self.collection_of_custom?(type)
|
144
141
|
(type.is_a?(Array) || type.is_a?(Set)) &&
|
145
142
|
type.length == 1 &&
|
146
|
-
custom?(type.first)
|
143
|
+
(custom?(type.first) || special?(type.first))
|
144
|
+
end
|
145
|
+
|
146
|
+
def self.map_special(type)
|
147
|
+
SPECIAL.fetch(type, type)
|
147
148
|
end
|
148
149
|
end
|
149
150
|
end
|
@@ -47,7 +47,9 @@ module Grape
|
|
47
47
|
# h[:list] = list
|
48
48
|
# h
|
49
49
|
# => #<Hashie::Mash list=[1, 2, 3, 4]>
|
50
|
-
params[attr_name]
|
50
|
+
return if params[attr_name].class == new_value.class && params[attr_name] == new_value
|
51
|
+
|
52
|
+
params[attr_name] = new_value
|
51
53
|
end
|
52
54
|
|
53
55
|
private
|
@@ -65,21 +67,21 @@ module Grape
|
|
65
67
|
end
|
66
68
|
|
67
69
|
def coerce_value(val)
|
68
|
-
|
69
|
-
# for coercion doesn't accept nil as a value, so it would fail
|
70
|
-
if val.nil?
|
71
|
-
return [] if type == Array || type.is_a?(Array)
|
72
|
-
return Set.new if type == Set
|
73
|
-
return {} if type == Hash
|
74
|
-
end
|
75
|
-
|
76
|
-
converter.call(val)
|
77
|
-
|
70
|
+
val.nil? ? coerce_nil(val) : converter.call(val)
|
78
71
|
# Some custom types might fail, so it should be treated as an invalid value
|
79
|
-
rescue
|
72
|
+
rescue StandardError
|
80
73
|
Types::InvalidValue.new
|
81
74
|
end
|
82
75
|
|
76
|
+
def coerce_nil(val)
|
77
|
+
# define default values for structures, the dry-types lib which is used
|
78
|
+
# for coercion doesn't accept nil as a value, so it would fail
|
79
|
+
return [] if type == Array
|
80
|
+
return Set.new if type == Set
|
81
|
+
return {} if type == Hash
|
82
|
+
val
|
83
|
+
end
|
84
|
+
|
83
85
|
# Type to which the parameter will be coerced.
|
84
86
|
#
|
85
87
|
# @return [Class]
|
@@ -6,8 +6,10 @@ module Grape
|
|
6
6
|
module Validations
|
7
7
|
class ExactlyOneOfValidator < MultipleParamsBase
|
8
8
|
def validate_params!(params)
|
9
|
-
|
10
|
-
|
9
|
+
keys = keys_in_common(params)
|
10
|
+
return if keys.length == 1
|
11
|
+
raise Grape::Exceptions::Validation.new(params: all_keys, message: message(:exactly_one)) if keys.length.zero?
|
12
|
+
raise Grape::Exceptions::Validation.new(params: keys, message: message(:mutual_exclusion))
|
11
13
|
end
|
12
14
|
end
|
13
15
|
end
|
@@ -5,7 +5,7 @@ module Grape
|
|
5
5
|
class RegexpValidator < Base
|
6
6
|
def validate_param!(attr_name, params)
|
7
7
|
return unless params.respond_to?(:key?) && params.key?(attr_name)
|
8
|
-
return if Array.wrap(params[attr_name]).all? { |param| param.nil? ||
|
8
|
+
return if Array.wrap(params[attr_name]).all? { |param| param.nil? || param.to_s.match?((options_key?(:value) ? @option[:value] : @option)) }
|
9
9
|
raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:regexp))
|
10
10
|
end
|
11
11
|
end
|
data/lib/grape/version.rb
CHANGED
data/lib/grape.rb
CHANGED
@@ -20,7 +20,6 @@ require 'active_support/core_ext/hash/conversions'
|
|
20
20
|
require 'active_support/dependencies/autoload'
|
21
21
|
require 'active_support/notifications'
|
22
22
|
require 'i18n'
|
23
|
-
require 'thread'
|
24
23
|
|
25
24
|
I18n.load_path << File.expand_path('../grape/locale/en.yml', __FILE__)
|
26
25
|
|
@@ -84,7 +83,6 @@ module Grape
|
|
84
83
|
eager_autoload do
|
85
84
|
autoload :DeepMergeableHash
|
86
85
|
autoload :DeepSymbolizeHash
|
87
|
-
autoload :DeepHashWithIndifferentAccess
|
88
86
|
autoload :Hash
|
89
87
|
end
|
90
88
|
module ActiveSupport
|
@@ -219,7 +217,8 @@ module Grape
|
|
219
217
|
end
|
220
218
|
|
221
219
|
require 'grape/config'
|
222
|
-
require 'grape/
|
220
|
+
require 'grape/content_types'
|
221
|
+
|
223
222
|
require 'grape/util/lazy_value'
|
224
223
|
require 'grape/util/lazy_block'
|
225
224
|
require 'grape/util/endpoint_configuration'
|
data/spec/grape/api_spec.rb
CHANGED
@@ -935,7 +935,7 @@ XML
|
|
935
935
|
|
936
936
|
it 'adds a before filter to current and child namespaces only' do
|
937
937
|
subject.get '/' do
|
938
|
-
"root - #{@foo}"
|
938
|
+
"root - #{instance_variable_defined?(:@foo) ? @foo : nil}"
|
939
939
|
end
|
940
940
|
subject.namespace :blah do
|
941
941
|
before { @foo = 'foo' }
|
@@ -3677,12 +3677,13 @@ XML
|
|
3677
3677
|
end
|
3678
3678
|
end
|
3679
3679
|
context ':serializable_hash' do
|
3680
|
-
|
3681
|
-
|
3682
|
-
def
|
3683
|
-
{ abc: 'def' }
|
3684
|
-
end
|
3680
|
+
class SerializableHashExample
|
3681
|
+
def serializable_hash
|
3682
|
+
{ abc: 'def' }
|
3685
3683
|
end
|
3684
|
+
end
|
3685
|
+
|
3686
|
+
before(:each) do
|
3686
3687
|
subject.format :serializable_hash
|
3687
3688
|
end
|
3688
3689
|
it 'instance' do
|
@@ -81,8 +81,8 @@ describe Grape::Exceptions::ValidationErrors do
|
|
81
81
|
expect(last_response.status).to eq(400)
|
82
82
|
expect(JSON.parse(last_response.body)).to eq(
|
83
83
|
[
|
84
|
-
'params' => %w[beer wine
|
85
|
-
'messages' => ['are
|
84
|
+
'params' => %w[beer wine],
|
85
|
+
'messages' => ['are mutually exclusive']
|
86
86
|
]
|
87
87
|
)
|
88
88
|
end
|
@@ -402,10 +402,10 @@ describe Grape::Middleware::Formatter do
|
|
402
402
|
let(:app) { ->(_env) { [200, {}, ['']] } }
|
403
403
|
before do
|
404
404
|
Grape::Formatter.register :invalid, InvalidFormatter
|
405
|
-
Grape::ContentTypes
|
405
|
+
Grape::ContentTypes.register :invalid, 'application/x-invalid'
|
406
406
|
end
|
407
407
|
after do
|
408
|
-
Grape::ContentTypes
|
408
|
+
Grape::ContentTypes.default_elements.delete(:invalid)
|
409
409
|
Grape::Formatter.default_elements.delete(:invalid)
|
410
410
|
end
|
411
411
|
|
@@ -111,6 +111,15 @@ describe Grape::Middleware::Stack do
|
|
111
111
|
expect(subject[1]).to eq(StackSpec::BlockMiddleware)
|
112
112
|
expect(subject[2]).to eq(StackSpec::BarMiddleware)
|
113
113
|
end
|
114
|
+
|
115
|
+
context 'middleware spec with proc declaration exists' do
|
116
|
+
let(:middleware_spec_with_proc) { [:use, StackSpec::FooMiddleware, proc] }
|
117
|
+
|
118
|
+
it 'properly forwards spec arguments' do
|
119
|
+
expect(subject).to receive(:use).with(StackSpec::FooMiddleware)
|
120
|
+
subject.merge_with([middleware_spec_with_proc])
|
121
|
+
end
|
122
|
+
end
|
114
123
|
end
|
115
124
|
|
116
125
|
describe '#build' do
|
data/spec/grape/path_spec.rb
CHANGED
@@ -87,12 +87,12 @@ module Grape
|
|
87
87
|
describe '#namespace?' do
|
88
88
|
it 'is false when the namespace is nil' do
|
89
89
|
path = Path.new(anything, nil, anything)
|
90
|
-
expect(path.namespace?).to
|
90
|
+
expect(path.namespace?).to be_falsey
|
91
91
|
end
|
92
92
|
|
93
93
|
it 'is false when the namespace starts with whitespace' do
|
94
94
|
path = Path.new(anything, ' /foo', anything)
|
95
|
-
expect(path.namespace?).to
|
95
|
+
expect(path.namespace?).to be_falsey
|
96
96
|
end
|
97
97
|
|
98
98
|
it 'is false when the namespace is the root path' do
|
@@ -109,12 +109,12 @@ module Grape
|
|
109
109
|
describe '#path?' do
|
110
110
|
it 'is false when the path is nil' do
|
111
111
|
path = Path.new(nil, anything, anything)
|
112
|
-
expect(path.path?).to
|
112
|
+
expect(path.path?).to be_falsey
|
113
113
|
end
|
114
114
|
|
115
115
|
it 'is false when the path starts with whitespace' do
|
116
116
|
path = Path.new(' /foo', anything, anything)
|
117
|
-
expect(path.path?).to
|
117
|
+
expect(path.path?).to be_falsey
|
118
118
|
end
|
119
119
|
|
120
120
|
it 'is false when the path is the root path' do
|
@@ -6,7 +6,7 @@ describe 'Validator with instance variables' do
|
|
6
6
|
let(:validator_type) do
|
7
7
|
Class.new(Grape::Validations::Base) do
|
8
8
|
def validate_param!(_attr_name, _params)
|
9
|
-
if @instance_variable
|
9
|
+
if instance_variable_defined?(:@instance_variable) && @instance_variable
|
10
10
|
raise Grape::Exceptions::Validation.new(params: ['params'],
|
11
11
|
message: 'This should never happen')
|
12
12
|
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Grape::Validations::Types::PrimitiveCoercer do
|
6
|
+
let(:strict) { false }
|
7
|
+
|
8
|
+
subject { described_class.new(type, strict) }
|
9
|
+
|
10
|
+
describe '.call' do
|
11
|
+
context 'Boolean' do
|
12
|
+
let(:type) { Grape::API::Boolean }
|
13
|
+
|
14
|
+
[true, 'true', 1].each do |val|
|
15
|
+
it "coerces '#{val}' to true" do
|
16
|
+
expect(subject.call(val)).to eq(true)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
[false, 'false', 0].each do |val|
|
21
|
+
it "coerces '#{val}' to false" do
|
22
|
+
expect(subject.call(val)).to eq(false)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'returns an error when the given value cannot be coerced' do
|
27
|
+
expect(subject.call(123)).to be_instance_of(Grape::Validations::Types::InvalidValue)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'String' do
|
32
|
+
let(:type) { String }
|
33
|
+
|
34
|
+
it 'coerces to String' do
|
35
|
+
expect(subject.call(10)).to eq('10')
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context 'BigDecimal' do
|
40
|
+
let(:type) { BigDecimal }
|
41
|
+
|
42
|
+
it 'coerces to BigDecimal' do
|
43
|
+
expect(subject.call(5)).to eq(BigDecimal(5))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'the strict mode' do
|
48
|
+
let(:strict) { true }
|
49
|
+
|
50
|
+
context 'Boolean' do
|
51
|
+
let(:type) { Grape::API::Boolean }
|
52
|
+
|
53
|
+
it 'returns an error when the given value is not Boolean' do
|
54
|
+
expect(subject.call(1)).to be_instance_of(Grape::Validations::Types::InvalidValue)
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'returns a value as it is when the given value is Boolean' do
|
58
|
+
expect(subject.call(true)).to eq(true)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context 'BigDecimal' do
|
63
|
+
let(:type) { BigDecimal }
|
64
|
+
|
65
|
+
it 'returns an error when the given value is not BigDecimal' do
|
66
|
+
expect(subject.call(1)).to be_instance_of(Grape::Validations::Types::InvalidValue)
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'returns a value as it is when the given value is BigDecimal' do
|
70
|
+
expect(subject.call(BigDecimal(0))).to eq(BigDecimal(0))
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -17,7 +17,7 @@ describe Grape::Validations::Types do
|
|
17
17
|
[
|
18
18
|
Integer, Float, Numeric, BigDecimal,
|
19
19
|
Grape::API::Boolean, String, Symbol,
|
20
|
-
Date, DateTime, Time
|
20
|
+
Date, DateTime, Time
|
21
21
|
].each do |type|
|
22
22
|
it "recognizes #{type} as a primitive" do
|
23
23
|
expect(described_class.primitive?(type)).to be_truthy
|
@@ -154,6 +154,49 @@ describe Grape::Validations::CoerceValidator do
|
|
154
154
|
end
|
155
155
|
|
156
156
|
context 'coerces' do
|
157
|
+
context 'json' do
|
158
|
+
let(:headers) { { 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json' } }
|
159
|
+
|
160
|
+
it 'BigDecimal' do
|
161
|
+
subject.params do
|
162
|
+
requires :bigdecimal, type: BigDecimal
|
163
|
+
end
|
164
|
+
subject.post '/bigdecimal' do
|
165
|
+
"#{params[:bigdecimal].class} #{params[:bigdecimal].to_f}"
|
166
|
+
end
|
167
|
+
|
168
|
+
post '/bigdecimal', { bigdecimal: 45.1 }.to_json, headers
|
169
|
+
expect(last_response.status).to eq(201)
|
170
|
+
expect(last_response.body).to eq('BigDecimal 45.1')
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'Boolean' do
|
174
|
+
subject.params do
|
175
|
+
requires :boolean, type: Boolean
|
176
|
+
end
|
177
|
+
subject.post '/boolean' do
|
178
|
+
params[:boolean]
|
179
|
+
end
|
180
|
+
|
181
|
+
post '/boolean', { boolean: 'true' }.to_json, headers
|
182
|
+
expect(last_response.status).to eq(201)
|
183
|
+
expect(last_response.body).to eq('true')
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
it 'BigDecimal' do
|
188
|
+
subject.params do
|
189
|
+
requires :bigdecimal, coerce: BigDecimal
|
190
|
+
end
|
191
|
+
subject.get '/bigdecimal' do
|
192
|
+
params[:bigdecimal].class
|
193
|
+
end
|
194
|
+
|
195
|
+
get '/bigdecimal', bigdecimal: '45'
|
196
|
+
expect(last_response.status).to eq(200)
|
197
|
+
expect(last_response.body).to eq('BigDecimal')
|
198
|
+
end
|
199
|
+
|
157
200
|
it 'Integer' do
|
158
201
|
subject.params do
|
159
202
|
requires :int, coerce: Integer
|
@@ -167,6 +210,23 @@ describe Grape::Validations::CoerceValidator do
|
|
167
210
|
expect(last_response.body).to eq(integer_class_name)
|
168
211
|
end
|
169
212
|
|
213
|
+
it 'String' do
|
214
|
+
subject.params do
|
215
|
+
requires :string, coerce: String
|
216
|
+
end
|
217
|
+
subject.get '/string' do
|
218
|
+
params[:string].class
|
219
|
+
end
|
220
|
+
|
221
|
+
get '/string', string: 45
|
222
|
+
expect(last_response.status).to eq(200)
|
223
|
+
expect(last_response.body).to eq('String')
|
224
|
+
|
225
|
+
get '/string', string: nil
|
226
|
+
expect(last_response.status).to eq(200)
|
227
|
+
expect(last_response.body).to eq('NilClass')
|
228
|
+
end
|
229
|
+
|
170
230
|
it 'is a custom type' do
|
171
231
|
subject.params do
|
172
232
|
requires :uri, coerce: SecureURIOnly
|
@@ -281,104 +341,73 @@ describe Grape::Validations::CoerceValidator do
|
|
281
341
|
end
|
282
342
|
end
|
283
343
|
|
284
|
-
it 'Bool' do
|
285
|
-
subject.params do
|
286
|
-
requires :bool, coerce: Grape::API::Boolean
|
287
|
-
end
|
288
|
-
subject.get '/bool' do
|
289
|
-
params[:bool].class
|
290
|
-
end
|
291
|
-
|
292
|
-
get '/bool', bool: 1
|
293
|
-
expect(last_response.status).to eq(200)
|
294
|
-
expect(last_response.body).to eq('TrueClass')
|
295
|
-
|
296
|
-
get '/bool', bool: 0
|
297
|
-
expect(last_response.status).to eq(200)
|
298
|
-
expect(last_response.body).to eq('FalseClass')
|
299
|
-
|
300
|
-
get '/bool', bool: 'false'
|
301
|
-
expect(last_response.status).to eq(200)
|
302
|
-
expect(last_response.body).to eq('FalseClass')
|
303
|
-
|
304
|
-
get '/bool', bool: 'true'
|
305
|
-
expect(last_response.status).to eq(200)
|
306
|
-
expect(last_response.body).to eq('TrueClass')
|
307
|
-
end
|
308
|
-
|
309
344
|
it 'Boolean' do
|
310
345
|
subject.params do
|
311
|
-
|
346
|
+
requires :boolean, type: Boolean
|
312
347
|
end
|
313
348
|
subject.get '/boolean' do
|
314
349
|
params[:boolean].class
|
315
350
|
end
|
316
351
|
|
317
|
-
get '/boolean'
|
318
|
-
expect(last_response.status).to eq(200)
|
319
|
-
expect(last_response.body).to eq('TrueClass')
|
320
|
-
|
321
|
-
get '/boolean', boolean: true
|
322
|
-
expect(last_response.status).to eq(200)
|
323
|
-
expect(last_response.body).to eq('TrueClass')
|
324
|
-
|
325
|
-
get '/boolean', boolean: false
|
326
|
-
expect(last_response.status).to eq(200)
|
327
|
-
expect(last_response.body).to eq('FalseClass')
|
328
|
-
|
329
|
-
get '/boolean', boolean: 'true'
|
352
|
+
get '/boolean', boolean: 1
|
330
353
|
expect(last_response.status).to eq(200)
|
331
354
|
expect(last_response.body).to eq('TrueClass')
|
355
|
+
end
|
332
356
|
|
333
|
-
|
334
|
-
|
335
|
-
|
357
|
+
context 'File' do
|
358
|
+
let(:file) { Rack::Test::UploadedFile.new(__FILE__) }
|
359
|
+
let(:filename) { File.basename(__FILE__).to_s }
|
336
360
|
|
337
|
-
|
338
|
-
|
339
|
-
|
361
|
+
it 'Rack::Multipart::UploadedFile' do
|
362
|
+
subject.params do
|
363
|
+
requires :file, type: Rack::Multipart::UploadedFile
|
364
|
+
end
|
365
|
+
subject.post '/upload' do
|
366
|
+
params[:file][:filename]
|
367
|
+
end
|
340
368
|
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
end
|
369
|
+
post '/upload', file: file
|
370
|
+
expect(last_response.status).to eq(201)
|
371
|
+
expect(last_response.body).to eq(filename)
|
345
372
|
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
end
|
350
|
-
subject.post '/upload' do
|
351
|
-
params[:file][:filename]
|
373
|
+
post '/upload', file: 'not a file'
|
374
|
+
expect(last_response.status).to eq(400)
|
375
|
+
expect(last_response.body).to eq('file is invalid')
|
352
376
|
end
|
353
377
|
|
354
|
-
|
355
|
-
|
356
|
-
|
378
|
+
it 'File' do
|
379
|
+
subject.params do
|
380
|
+
requires :file, coerce: File
|
381
|
+
end
|
382
|
+
subject.post '/upload' do
|
383
|
+
params[:file][:filename]
|
384
|
+
end
|
357
385
|
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
end
|
386
|
+
post '/upload', file: file
|
387
|
+
expect(last_response.status).to eq(201)
|
388
|
+
expect(last_response.body).to eq(filename)
|
362
389
|
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
end
|
367
|
-
subject.post '/upload' do
|
368
|
-
params[:file][:filename]
|
369
|
-
end
|
390
|
+
post '/upload', file: 'not a file'
|
391
|
+
expect(last_response.status).to eq(400)
|
392
|
+
expect(last_response.body).to eq('file is invalid')
|
370
393
|
|
371
|
-
|
372
|
-
|
373
|
-
|
394
|
+
post '/upload', file: { filename: 'fake file', tempfile: '/etc/passwd' }
|
395
|
+
expect(last_response.status).to eq(400)
|
396
|
+
expect(last_response.body).to eq('file is invalid')
|
397
|
+
end
|
374
398
|
|
375
|
-
|
376
|
-
|
377
|
-
|
399
|
+
it 'collection' do
|
400
|
+
subject.params do
|
401
|
+
requires :files, type: Array[File]
|
402
|
+
end
|
403
|
+
subject.post '/upload' do
|
404
|
+
params[:files].first[:filename]
|
405
|
+
end
|
378
406
|
|
379
|
-
|
380
|
-
|
381
|
-
|
407
|
+
post '/upload', files: [file]
|
408
|
+
expect(last_response.status).to eq(201)
|
409
|
+
expect(last_response.body).to eq(filename)
|
410
|
+
end
|
382
411
|
end
|
383
412
|
|
384
413
|
it 'Nests integers' do
|
@@ -514,6 +543,46 @@ describe Grape::Validations::CoerceValidator do
|
|
514
543
|
expect(last_response.body).to eq('3')
|
515
544
|
end
|
516
545
|
|
546
|
+
context 'Integer type and coerce_with potentially returning nil' do
|
547
|
+
before do
|
548
|
+
subject.params do
|
549
|
+
requires :int, type: Integer, coerce_with: (lambda do |val|
|
550
|
+
if val == '0'
|
551
|
+
nil
|
552
|
+
elsif val.match?(/^-?\d+$/)
|
553
|
+
val.to_i
|
554
|
+
else
|
555
|
+
val
|
556
|
+
end
|
557
|
+
end)
|
558
|
+
end
|
559
|
+
subject.get '/' do
|
560
|
+
params[:int].class.to_s
|
561
|
+
end
|
562
|
+
end
|
563
|
+
|
564
|
+
it 'accepts value that coerces to nil' do
|
565
|
+
get '/', int: '0'
|
566
|
+
|
567
|
+
expect(last_response.status).to eq(200)
|
568
|
+
expect(last_response.body).to eq('NilClass')
|
569
|
+
end
|
570
|
+
|
571
|
+
it 'coerces to Integer' do
|
572
|
+
get '/', int: '1'
|
573
|
+
|
574
|
+
expect(last_response.status).to eq(200)
|
575
|
+
expect(last_response.body).to eq('Integer')
|
576
|
+
end
|
577
|
+
|
578
|
+
it 'returns invalid value if coercion returns a wrong type' do
|
579
|
+
get '/', int: 'lol'
|
580
|
+
|
581
|
+
expect(last_response.status).to eq(400)
|
582
|
+
expect(last_response.body).to eq('int is invalid')
|
583
|
+
end
|
584
|
+
end
|
585
|
+
|
517
586
|
it 'must be supplied with :type or :coerce' do
|
518
587
|
expect do
|
519
588
|
subject.params do
|
@@ -683,6 +752,19 @@ describe Grape::Validations::CoerceValidator do
|
|
683
752
|
expect(last_response.body).to eq('String')
|
684
753
|
end
|
685
754
|
|
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
|
+
|
686
768
|
it 'fails when no coercion is possible' do
|
687
769
|
subject.params do
|
688
770
|
requires :a, types: [Boolean, Integer]
|