Skip to content

Commit fa6b90d

Browse files
author
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 a9a29a4 commit fa6b90d

File tree

5 files changed

+70
-6
lines changed

5 files changed

+70
-6
lines changed

Diff for: CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
* Your contribution here.
77

8+
* [#1203](https://github.com/ruby-grape/grape/pull/1203): Allow custom coercion failure messages - [@zuk](https://github.com/zuk).
89
* [#1196](https://github.com/ruby-grape/grape/pull/1196): Allow multiple `before_each` blocks - [@huynhquancam](https://github.com/huynhquancam).
910
* [#1190](https://github.com/ruby-grape/grape/putt/1190): Bypass formatting for statuses with no entity-body - [@tylerdooling](https://github.com/tylerdooling).
1011
* [#1188](https://github.com/ruby-grape/grape/putt/1188): Allow parameters with more than one type - [@dslh](https://github.com/dslh).

Diff for: README.md

+9-4
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,7 @@ desc 'Returns your public timeline.' do
408408
description: 'Not really needed',
409409
required: false
410410
}
411-
411+
412412
end
413413
get :public_timeline do
414414
Status.limit(20)
@@ -751,7 +751,9 @@ Aside from the default set of supported types listed above, any class can be
751751
used as a type so long as an explicit coercion method is supplied. If the type
752752
implements a class-level `parse` method, Grape will use it automatically.
753753
This method must take one string argument and return an instance of the correct
754-
type, or raise an exception to indicate the value was invalid. E.g.,
754+
type. An exception raised inside the `parse` method will be reported as a validation
755+
failure with a generic error message. To report a custom error message, return an
756+
`InvalidValue` initialized with the custom message. E.g.,
755757

756758
```ruby
757759
class Color
@@ -761,8 +763,11 @@ class Color
761763
end
762764

763765
def self.parse(value)
764-
fail 'Invalid color' unless %w(blue red green).include?(value)
765-
new(value)
766+
if %w(blue red green).include?(value)
767+
new(value)
768+
else
769+
Grape::Validations::Types::InvalidValue.new "is not a valid color"
770+
end
766771
end
767772
end
768773

Diff for: 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.

Diff for: lib/grape/validations/validators/coerce.rb

+6-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@ def validate_param!(attr_name, params)
1111
if valid_type?(new_value)
1212
params[attr_name] = new_value
1313
else
14-
fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message_key: :coerce
14+
bad_value = new_value
15+
if bad_value.is_a?(Types::InvalidValue) && !bad_value.message.nil?
16+
fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: bad_value.message.to_s
17+
else
18+
fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message_key: :coerce
19+
end
1520
end
1621
end
1722

Diff for: spec/grape/validations/params_scope_spec.rb

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

123171
context 'array without coerce type explicitly given' do

0 commit comments

Comments
 (0)