Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implemented except in values validator #1486

Merged
merged 4 commits into from
Sep 9, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
==================

* [#1480](https://github.com/ruby-grape/grape/pull/1480): Use the ruby-grape-danger gem for PR linting - [@dblock](https://github.com/dblock).
* [#1486](https://github.com/ruby-grape/grape/pull/1486): Implemented except in values validator - [@jonmchan](https://github.com/jonmchan).
* Your contribution here.

#### Fixes
Expand Down
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1065,6 +1065,26 @@ params do
end
```

The values validator can also validate that the value is explicitly not within a specific
set of values by passing ```except```. ```except``` accepts the same types of parameters as
values (Procs, ranges, etc.).

```ruby
params do
requires :browsers, values: { except: [ 'ie6', 'ie7', 'ie8' ] }
end
```

Values and except can be combined to define a range of accepted values while not allowing
certain values within the set. Custom error messages can be defined for both when the parameter
passed falls within the ```except``` list or when it falls entirely outside the ```value``` list.

```ruby
params do
requires :number, type: Integer, values: { value: 1..20 except: [4,13], except_message: 'includes unsafe numbers', message: 'is outside the range of numbers allowed' }
end
```

#### `regexp`

Parameters can be restricted to match a specific regular expression with the `:regexp` option. If the value
Expand Down
1 change: 1 addition & 0 deletions lib/grape/locale/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ en:
regexp: 'is invalid'
blank: 'is empty'
values: 'does not have a valid value'
except: 'has a value not allowed'
missing_vendor_option:
problem: 'missing :vendor option.'
summary: 'when version using header, you must specify :vendor option. '
Expand Down
18 changes: 16 additions & 2 deletions lib/grape/validations/validators/values.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ module Grape
module Validations
class ValuesValidator < Base
def initialize(attrs, options, required, scope)
@values = (options_key?(:value, options) ? options[:value] : options)
@excepts = (options_key?(:except, options) ? options[:except] : [])
@values = (options_key?(:value, options) ? options[:value] : [])

@values = options if @excepts == [] && @values == []
super
end

Expand All @@ -11,13 +14,24 @@ def validate_param!(attr_name, params)
return unless params[attr_name] || required_for_root_scope?

values = @values.is_a?(Proc) ? @values.call : @values
excepts = @excepts.is_a?(Proc) ? @excepts.call : @excepts
param_array = params[attr_name].nil? ? [nil] : Array.wrap(params[attr_name])
return if param_array.all? { |param| values.include?(param) }

if param_array.all? { |param| excepts.include?(param) }
raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: except_message
end

return if (values.is_a?(Array) && values.empty?) || param_array.all? { |param| values.include?(param) }
raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: message(:values)
end

private

def except_message
options = instance_variable_get(:@option)
options_key?(:except_message) ? options[:except_message] : message(:except)
end

def required_for_root_scope?
@required && @scope.root?
end
Expand Down
89 changes: 89 additions & 0 deletions spec/grape/validations/validators/values_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
module ValidationsSpec
class ValuesModel
DEFAULT_VALUES = ['valid-type1', 'valid-type2', 'valid-type3'].freeze
DEFAULT_EXCEPTS = ['invalid-type1', 'invalid-type2', 'invalid-type3'].freeze
class << self
def values
@values ||= []
Expand All @@ -14,6 +15,16 @@ def add_value(value)
@values ||= []
@values << value
end

def excepts
@excepts ||= []
[DEFAULT_EXCEPTS + @excepts].flatten.uniq
end

def add_except(except)
@excepts ||= []
@excepts << except
end
end
end

Expand All @@ -35,6 +46,20 @@ class API < Grape::API
get '/lambda' do
{ type: params[:type] }
end

params do
requires :type, values: { except: ValuesModel.excepts, except_message: 'value is on exclusions list', message: 'default exclude message' }
end
get '/exclude/exclude_message' do
{ type: params[:type] }
end

params do
requires :type, values: { except: ValuesModel.excepts, message: 'default exclude message' }
end
get '/exclude/fallback_message' do
{ type: params[:type] }
end
end

params do
Expand Down Expand Up @@ -99,6 +124,20 @@ class API < Grape::API
end
end
get '/optional_with_required_values'

params do
requires :type, values: { except: ValuesModel.excepts }
end
get '/except/exclusive' do
{ type: params[:type] }
end

params do
requires :type, type: Integer, values: { value: 1..5, except: [3] }
end
get '/mixed/value/except' do
{ type: params[:type] }
end
end
end
end
Expand Down Expand Up @@ -135,6 +174,22 @@ def app
end
end

context 'with a custom exclude validation message' do
it 'does not allow an invalid value for a parameter' do
get('/custom_message/exclude/exclude_message', type: 'invalid-type1')
expect(last_response.status).to eq 400
expect(last_response.body).to eq({ error: 'type value is on exclusions list' }.to_json)
end
end

context 'exclude with a standard custom validation message' do
it 'does not allow an invalid value for a parameter' do
get('/custom_message/exclude/fallback_message', type: 'invalid-type1')
expect(last_response.status).to eq 400
expect(last_response.body).to eq({ error: 'type default exclude message' }.to_json)
end
end

it 'allows a valid value for a parameter' do
get('/', type: 'valid-type1')
expect(last_response.status).to eq 200
Expand Down Expand Up @@ -321,4 +376,38 @@ def app
expect(last_response.body).to eq('values does not have a valid value')
end
end

context 'exclusive excepts' do
it 'allows any other value outside excepts' do
get '/except/exclusive', type: 'value'
expect(last_response.status).to eq 200
expect(last_response.body).to eq({ type: 'value' }.to_json)
end

it 'rejects values that matches except' do
get '/except/exclusive', type: 'invalid-type1'
expect(last_response.status).to eq 400
expect(last_response.body).to eq({ error: 'type has a value not allowed' }.to_json)
end
end

context 'with mixed values and excepts' do
it 'allows value, but not in except' do
get '/mixed/value/except', type: 2
expect(last_response.status).to eq 200
expect(last_response.body).to eq({ type: 2 }.to_json)
end

it 'rejects except' do
get '/mixed/value/except', type: 3
expect(last_response.status).to eq 400
expect(last_response.body).to eq({ error: 'type has a value not allowed' }.to_json)
end

it 'rejects outside except and outside value' do
get '/mixed/value/except', type: 10
expect(last_response.status).to eq 400
expect(last_response.body).to eq({ error: 'type does not have a valid value' }.to_json)
end
end
end