Skip to content

Commit 54b2e11

Browse files
committed
custom types can set a message to be used in the response when invalid
Just return an instance of `Grape::Types::InvalidValue` with the message: class Color def self.parse(value) return value if %w[blue red green].include?(value) Grape::Types::InvalidValue.new('Invalid color') end end Any raised exception will be treated as an invalid value as it was before.
1 parent e7f771e commit 54b2e11

File tree

8 files changed

+83
-22
lines changed

8 files changed

+83
-22
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#### Features
44

55
* Your contribution here.
6+
* [#2157](https://github.com/ruby-grape/grape/pull/2157): Custom types can set a message to be used in the response when invalid - [@dnesteryuk](https://github.com/dnesteryuk).
67
* [#2145](https://github.com/ruby-grape/grape/pull/2145): Ruby 3.0 compatibility - [@ericproulx](https://github.com/ericproulx).
78
* [#2143](https://github.com/ruby-grape/grape/pull/2143): Enable GitHub Actions with updated RuboCop and Danger - [@anakinj](https://github.com/anakinj).
89

Gemfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,7 @@ group :test do
3535
gem 'rspec', '~> 3.0'
3636
gem 'ruby-grape-danger', '~> 0.2.0', require: false
3737
end
38+
39+
platforms :jruby do
40+
gem 'racc'
41+
end

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1183,7 +1183,8 @@ Aside from the default set of supported types listed above, any class can be
11831183
used as a type as long as an explicit coercion method is supplied. If the type
11841184
implements a class-level `parse` method, Grape will use it automatically.
11851185
This method must take one string argument and return an instance of the correct
1186-
type, or raise an exception to indicate the value was invalid. E.g.,
1186+
type, or return an instance of `Grape::Types::InvalidValue` which optionally
1187+
accepts a message to be returned in the response.
11871188

11881189
```ruby
11891190
class Color
@@ -1193,8 +1194,9 @@ class Color
11931194
end
11941195

11951196
def self.parse(value)
1196-
fail 'Invalid color' unless %w(blue red green).include?(value)
1197-
new(value)
1197+
return new(value) if %w[blue red green]).include?(value)
1198+
1199+
Grape::Types::InvalidValue.new('Unsupported color')
11981200
end
11991201
end
12001202

lib/grape/validations/types.rb

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
require_relative 'types/variant_collection_coercer'
88
require_relative 'types/json'
99
require_relative 'types/file'
10+
require_relative 'types/invalid_value'
1011

1112
module Grape
1213
module Validations
@@ -21,10 +22,6 @@ module Validations
2122
# and {Grape::Dsl::Parameters#optional}. The main
2223
# entry point for this process is {Types.build_coercer}.
2324
module Types
24-
# Instances of this class may be used as tokens to denote that
25-
# a parameter value could not be coerced.
26-
class InvalidValue; end
27-
2825
# Types representing a single value, which are coerced.
2926
PRIMITIVES = [
3027
# Numerical

lib/grape/validations/types/custom_type_coercer.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ def call(val)
5555
return if val.nil?
5656

5757
coerced_val = @method.call(val)
58+
59+
return coerced_val if coerced_val.is_a?(InvalidValue)
5860
return InvalidValue.new unless coerced?(coerced_val)
5961
coerced_val
6062
end
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# frozen_string_literal: true
2+
3+
module Grape
4+
module Validations
5+
module Types
6+
# Instances of this class may be used as tokens to denote that a parameter value could not be
7+
# coerced. The given message will be used as a validation error.
8+
class InvalidValue
9+
attr_reader :message
10+
11+
def initialize(message = nil)
12+
@message = message
13+
end
14+
end
15+
end
16+
end
17+
end
18+
19+
# only exists to make it shorter for external use
20+
module Grape
21+
module Types
22+
InvalidValue = Class.new(Grape::Validations::Types::InvalidValue)
23+
end
24+
end

lib/grape/validations/validators/coerce.rb

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def validate_param!(attr_name, params)
3636

3737
new_value = coerce_value(params[attr_name])
3838

39-
raise validation_exception(attr_name) unless valid_type?(new_value)
39+
raise validation_exception(attr_name, new_value.message) unless valid_type?(new_value)
4040

4141
# Don't assign a value if it is identical. It fixes a problem with Hashie::Mash
4242
# which looses wrappers for hashes and arrays after reassigning values
@@ -80,8 +80,11 @@ def type
8080
@option[:type].is_a?(Hash) ? @option[:type][:value] : @option[:type]
8181
end
8282

83-
def validation_exception(attr_name)
84-
Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:coerce))
83+
def validation_exception(attr_name, custom_msg = nil)
84+
Grape::Exceptions::Validation.new(
85+
params: [@scope.full_name(attr_name)],
86+
message: custom_msg || message(:coerce)
87+
)
8588
end
8689
end
8790
end

spec/grape/validations/validators/coerce_spec.rb

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -227,23 +227,51 @@ def self.parsed?(value)
227227
expect(last_response.body).to eq('NilClass')
228228
end
229229

230-
it 'is a custom type' do
231-
subject.params do
232-
requires :uri, coerce: SecureURIOnly
233-
end
234-
subject.get '/secure_uri' do
235-
params[:uri].class
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')
236248
end
237249

238-
get 'secure_uri', uri: 'https://www.example.com'
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
239258

240-
expect(last_response.status).to eq(200)
241-
expect(last_response.body).to eq('URI::HTTPS')
259+
it 'uses a custom message added to the invalid value' do
260+
type = custom_type
261+
262+
subject.params do
263+
requires :name, type: type
264+
end
265+
subject.get '/whatever' do
266+
params[:name].class
267+
end
242268

243-
get 'secure_uri', uri: 'http://www.example.com'
269+
get 'whatever', name: 'Bob'
244270

245-
expect(last_response.status).to eq(400)
246-
expect(last_response.body).to eq('uri is invalid')
271+
expect(last_response.status).to eq(400)
272+
expect(last_response.body).to eq('name must be unique')
273+
end
274+
end
247275
end
248276

249277
context 'Array' do

0 commit comments

Comments
 (0)