Skip to content

Commit 242ecea

Browse files
Matt ZukowskiMatt Zukowski
Matt Zukowski
authored and
Matt Zukowski
committed
allow custom param coercion failure messages
Custom param types can now report why coercion failed in their .parse method by returning an InvalidValue initialized with an error message. For example: Grape::Validations::Types::InvalidValue.new "is too short"
1 parent 5936ee6 commit 242ecea

File tree

4 files changed

+72
-6
lines changed

4 files changed

+72
-6
lines changed

README.md

+8-3
Original file line numberDiff line numberDiff line change
@@ -803,7 +803,9 @@ Aside from the default set of supported types listed above, any class can be
803803
used as a type so long as an explicit coercion method is supplied. If the type
804804
implements a class-level `parse` method, Grape will use it automatically.
805805
This method must take one string argument and return an instance of the correct
806-
type, or raise an exception to indicate the value was invalid. E.g.,
806+
type. An exception raised inside the `parse` method will be reported as a validation
807+
failure with a generic error message. To report a custom error message, return an
808+
`InvalidValue` initialized with the custom message. E.g.,
807809

808810
```ruby
809811
class Color
@@ -813,8 +815,11 @@ class Color
813815
end
814816

815817
def self.parse(value)
816-
fail 'Invalid color' unless %w(blue red green).include?(value)
817-
new(value)
818+
if %w(blue red green).include?(value)
819+
new(value)
820+
else
821+
Grape::Validations::Types::InvalidValue.new "is not a valid color"
822+
end
818823
end
819824
end
820825

lib/grape/validations/types.rb

+6-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,12 @@ module Validations
2424
module Types
2525
# Instances of this class may be used as tokens to denote that
2626
# a parameter value could not be coerced.
27-
class InvalidValue; end
27+
class InvalidValue
28+
attr_reader :message
29+
def initialize(message = nil)
30+
@message = message
31+
end
32+
end
2833

2934
# Types representing a single value, which are coerced through Virtus
3035
# or special logic in Grape.

lib/grape/validations/validators/coerce.rb

+10-2
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,16 @@ def initialize(*_args)
1313
def validate_param!(attr_name, params)
1414
raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: message(:coerce) unless params.is_a? Hash
1515
new_value = coerce_value(params[attr_name])
16-
raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: message(:coerce) unless valid_type?(new_value)
17-
params[attr_name] = new_value
16+
if valid_type?(new_value)
17+
params[attr_name] = new_value
18+
else
19+
bad_value = new_value
20+
if bad_value.is_a?(Types::InvalidValue) && !bad_value.message.nil?
21+
fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: bad_value.message.to_s
22+
else
23+
fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message_key: :coerce
24+
end
25+
end
1826
end
1927

2028
private

spec/grape/validations/params_scope_spec.rb

+48
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,54 @@ def initialize(value)
119119
expect(last_response.status).to eq(400)
120120
expect(last_response.body).to match(/foo is invalid/)
121121
end
122+
123+
context 'when the parse method returns an InvalidValue' do
124+
module ParamsScopeSpec
125+
class AnotherCustomType
126+
attr_reader :value
127+
def self.parse(value)
128+
case value
129+
when 'invalid with message'
130+
Grape::Validations::Types::InvalidValue.new 'is not correct'
131+
when 'invalid without message'
132+
Grape::Validations::Types::InvalidValue.new
133+
else
134+
new(value)
135+
end
136+
end
137+
138+
def initialize(value)
139+
@value = value
140+
end
141+
end
142+
end
143+
144+
context 'with a message' do
145+
it 'fails with the InvalidValue\'s error message' do
146+
subject.params do
147+
requires :foo, type: ParamsScopeSpec::AnotherCustomType
148+
end
149+
subject.get('/types') { params[:foo].value }
150+
151+
get '/types', foo: 'invalid with message'
152+
expect(last_response.status).to eq(400)
153+
expect(last_response.body).to match(/foo is not correct/)
154+
end
155+
end
156+
157+
context 'without a message' do
158+
it 'fails with the default coercion failure message' do
159+
subject.params do
160+
requires :foo, type: ParamsScopeSpec::AnotherCustomType
161+
end
162+
subject.get('/types') { params[:foo].value }
163+
164+
get '/types', foo: 'invalid without message'
165+
expect(last_response.status).to eq(400)
166+
expect(last_response.body).to match(/foo is invalid/)
167+
end
168+
end
169+
end
122170
end
123171

124172
context 'array without coerce type explicitly given' do

0 commit comments

Comments
 (0)