Skip to content

Commit

Permalink
Adding custom validation error messages
Browse files Browse the repository at this point in the history
  • Loading branch information
railsmith committed Feb 28, 2016
1 parent df3e4ae commit e722eab
Show file tree
Hide file tree
Showing 40 changed files with 803 additions and 60 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
* [#1255](https://github.com/ruby-grape/grape/pull/1255): Allow param type definition in `route_param` - [@namusyaka](https://github.com/namusyaka).
* [#1257](https://github.com/ruby-grape/grape/pull/1257): Allow Proc, Symbol or String in `rescue_from with: ...` - [@namusyaka](https://github.com/namusyaka).
* [#1285](https://github.com/ruby-grape/grape/pull/1285): Add a warning for errors appearing in `after` callbacks - [@gregormelhorn](https://github.com/gregormelhorn).
* Your contribution here.
* [#1295](https://github.com/ruby-grape/grape/pull/1295): Add custom validation messages for parameter exceptions - [@railsmith](https://github.com/railsmith).

#### Fixes

Expand Down
114 changes: 114 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
- [Custom Validators](#custom-validators)
- [Validation Errors](#validation-errors)
- [I18n](#i18n)
- [Custom Validation Messages](#custom-validation-messages)
- [Headers](#headers)
- [Routes](#routes)
- [Helpers](#helpers)
Expand Down Expand Up @@ -1237,6 +1238,119 @@ end
Grape supports I18n for parameter-related error messages, but will fallback to English if
translations for the default locale have not been provided. See [en.yml](lib/grape/locale/en.yml) for message keys.

### Custom Validation messages

Grape supports custom validation messages for parameter-related and coerce-related error messages.

#### `presence`, `allow_blank`, `values`, `regexp`

```ruby
params do
requires :name, values: { value: 1..10, message: 'not in range from 1 to 10' }, allow_blank: { value: false, message: 'cannot be blank' }, regexp: { value: /^[a-z]+$/, message: 'format is invalid' }, message: 'is required'
end
```
#### `all_or_none_of`

```ruby
params do
optional :beer
optional :wine
optional :juice
all_or_none_of :beer, :wine, :juice, message: "all params are required or none is required"
end
```

#### `mutually_exclusive`

```ruby
params do
optional :beer
optional :wine
optional :juice
mutually_exclusive :beer, :wine, :juice, message: "are mutually exclusive cannot pass both params"
end
```
#### `exactly_one_of`

```ruby
params do
optional :beer
optional :wine
optional :juice
exactly_one_of :beer, :wine, :juice, message: {exactly_one: "are missing, exactly one parameter is required", mutual_exclusion: "are mutually exclusive, exactly one parameter is required"}
end
```
#### `at_least_one_of`

```ruby
params do
optional :beer
optional :wine
optional :juice
at_least_one_of :beer, :wine, :juice, message: "are missing, please specify at least one param"
end
```
#### `Coerce`

```ruby
params do
requires :int, type: {value: Integer, message: "type cast is invalid" }
end
```
#### `With Lambdas`

```ruby
params do
requires :name, values: { value: -> { (1..10).to_a }, message: 'not in range from 1 to 10' }
end
```
#### `Pass symbols for i18n translations`

You can pass a symbol if you want i18n translations for your custom validation messages.

```ruby
params do
requires :name, message: :name_required
end
```
```ruby
# en.yml

en:
grape:
errors:
format: ! '%{attributes} %{message}'
messages:
name_required: 'must be present'
```

#### `Overriding attribute names`

You can also override attribute names.

```ruby
# en.yml

en:
grape:
errors:
format: ! '%{attributes} %{message}'
messages:
name_required: 'must be present'
attributes:
name: 'Oops! Name'
```
Will produce 'Oops! Name must be present'

#### `With Default`

You cannot set a custom message option for Default as it requires interpolation `%{option1}: %{value1} is incompatible with %{option2}: %{value2}`. You can change the default error message for Default by changing the `incompatible_option_values` message key inside [en.yml](lib/grape/locale/en.yml)

```ruby
params do
requires :name, values: { value: -> { (1..10).to_a }, message: 'not in range from 1 to 10' }, default: 5
end
```
## Headers

Request headers are available through the `headers` helper or from `env` in their original form.
Expand Down
10 changes: 5 additions & 5 deletions lib/grape/dsl/parameters.rb
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ def requires(*attrs, &block)
orig_attrs = attrs.clone

opts = attrs.extract_options!.clone
opts[:presence] = true
opts[:presence] = { value: true, message: opts[:message] }

if opts[:using]
require_required_and_optional_fields(attrs.first, opts)
Expand Down Expand Up @@ -137,25 +137,25 @@ def optional(*attrs, &block)
# Disallow the given parameters to be present in the same request.
# @param attrs [*Symbol] parameters to validate
def mutually_exclusive(*attrs)
validates(attrs, mutual_exclusion: true)
validates(attrs, mutual_exclusion: { value: true, message: extract_message_option(attrs) })
end

# Require exactly one of the given parameters to be present.
# @param (see #mutually_exclusive)
def exactly_one_of(*attrs)
validates(attrs, exactly_one_of: true)
validates(attrs, exactly_one_of: { value: true, message: extract_message_option(attrs) })
end

# Require at least one of the given parameters to be present.
# @param (see #mutually_exclusive)
def at_least_one_of(*attrs)
validates(attrs, at_least_one_of: true)
validates(attrs, at_least_one_of: { value: true, message: extract_message_option(attrs) })
end

# Require that either all given params are present, or none are.
# @param (see #mutually_exclusive)
def all_or_none_of(*attrs)
validates(attrs, all_or_none_of: true)
validates(attrs, all_or_none_of: { value: true, message: extract_message_option(attrs) })
end

# Define a block of validations which should be applied if and only if
Expand Down
15 changes: 11 additions & 4 deletions lib/grape/exceptions/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,15 @@ def compose_message(key, attributes = {})
end

def problem(key, attributes)
translate_message("#{key}.problem", attributes)
translate_message("#{key}.problem".to_sym, attributes)
end

def summary(key, attributes)
translate_message("#{key}.summary", attributes)
translate_message("#{key}.summary".to_sym, attributes)
end

def resolution(key, attributes)
translate_message("#{key}.resolution", attributes)
translate_message("#{key}.resolution".to_sym, attributes)
end

def translate_attributes(keys, options = {})
Expand All @@ -60,7 +60,14 @@ def translate_attribute(key, options = {})
end

def translate_message(key, options = {})
translate("#{BASE_MESSAGES_KEY}.#{key}", options.reverse_merge(default: ''))
case key
when Symbol
translate("#{BASE_MESSAGES_KEY}.#{key}", options.reverse_merge(default: ''))
when Proc
key.call
else
key
end
end

def translate(key, options = {})
Expand Down
2 changes: 1 addition & 1 deletion lib/grape/exceptions/incompatible_option_values.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module Grape
module Exceptions
class IncompatibleOptionValues < Base
def initialize(option1, value1, option2, value2)
super(message: compose_message('incompatible_option_values', option1: option1, value1: value1, option2: option2, value2: value2))
super(message: compose_message(:incompatible_option_values, option1: option1, value1: value1, option2: option2, value2: value2))
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/grape/exceptions/invalid_accept_header.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module Grape
module Exceptions
class InvalidAcceptHeader < Base
def initialize(message, headers)
super(message: compose_message('invalid_accept_header', message: message), status: 406, headers: headers)
super(message: compose_message(:invalid_accept_header, message: message), status: 406, headers: headers)
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/grape/exceptions/invalid_formatter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module Grape
module Exceptions
class InvalidFormatter < Base
def initialize(klass, to_format)
super(message: compose_message('invalid_formatter', klass: klass, to_format: to_format))
super(message: compose_message(:invalid_formatter, klass: klass, to_format: to_format))
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/grape/exceptions/invalid_message_body.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module Grape
module Exceptions
class InvalidMessageBody < Base
def initialize(body_format)
super(message: compose_message('invalid_message_body', body_format: body_format), status: 400)
super(message: compose_message(:invalid_message_body, body_format: body_format), status: 400)
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/grape/exceptions/invalid_version_header.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module Grape
module Exceptions
class InvalidVersionHeader < Base
def initialize(message, headers)
super(message: compose_message('invalid_version_header', message: message), status: 406, headers: headers)
super(message: compose_message(:invalid_version_header, message: message), status: 406, headers: headers)
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/grape/exceptions/invalid_versioner_option.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module Grape
module Exceptions
class InvalidVersionerOption < Base
def initialize(strategy)
super(message: compose_message('invalid_versioner_option', strategy: strategy))
super(message: compose_message(:invalid_versioner_option, strategy: strategy))
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/grape/exceptions/invalid_with_option_for_represent.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module Grape
module Exceptions
class InvalidWithOptionForRepresent < Base
def initialize
super(message: compose_message('invalid_with_option_for_represent'))
super(message: compose_message(:invalid_with_option_for_represent))
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/grape/exceptions/missing_group_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module Grape
module Exceptions
class MissingGroupTypeError < Base
def initialize
super(message: compose_message('missing_group_type'))
super(message: compose_message(:missing_group_type))
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/grape/exceptions/missing_mime_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module Grape
module Exceptions
class MissingMimeType < Base
def initialize(new_format)
super(message: compose_message('missing_mime_type', new_format: new_format))
super(message: compose_message(:missing_mime_type, new_format: new_format))
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/grape/exceptions/missing_option.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module Grape
module Exceptions
class MissingOption < Base
def initialize(option)
super(message: compose_message('missing_option', option: option))
super(message: compose_message(:missing_option, option: option))
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/grape/exceptions/missing_vendor_option.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module Grape
module Exceptions
class MissingVendorOption < Base
def initialize
super(message: compose_message('missing_vendor_option'))
super(message: compose_message(:missing_vendor_option))
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/grape/exceptions/unknown_options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module Grape
module Exceptions
class UnknownOptions < Base
def initialize(options)
super(message: compose_message('unknown_options', options: options))
super(message: compose_message(:unknown_options, options: options))
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/grape/exceptions/unknown_parameter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module Grape
module Exceptions
class UnknownParameter < Base
def initialize(param)
super(message: compose_message('unknown_parameter', param: param))
super(message: compose_message(:unknown_parameter, param: param))
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/grape/exceptions/unknown_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module Grape
module Exceptions
class UnknownValidator < Base
def initialize(validator_type)
super(message: compose_message('unknown_validator', validator_type: validator_type))
super(message: compose_message(:unknown_validator, validator_type: validator_type))
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/grape/exceptions/unsupported_group_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module Grape
module Exceptions
class UnsupportedGroupTypeError < Base
def initialize
super(message: compose_message('unsupported_group_type'))
super(message: compose_message(:unsupported_group_type))
end
end
end
Expand Down
3 changes: 1 addition & 2 deletions lib/grape/exceptions/validation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ class Validation < Grape::Exceptions::Base
def initialize(args = {})
fail 'Params are missing:' unless args.key? :params
@params = args[:params]
@message_key = args[:message_key]
args[:message] = translate_message(args[:message_key]) if args.key? :message_key
args[:message] = translate_message(args[:message]) if args.key? :message
super
end

Expand Down
Loading

0 comments on commit e722eab

Please sign in to comment.