Skip to content

Commit 3540ea2

Browse files
jonmchandblock
authored andcommitted
implemented except in values validator (#1486)
1 parent 7d1eb5c commit 3540ea2

File tree

5 files changed

+127
-2
lines changed

5 files changed

+127
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
==================
33

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

78
#### Fixes

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1065,6 +1065,26 @@ params do
10651065
end
10661066
```
10671067

1068+
The values validator can also validate that the value is explicitly not within a specific
1069+
set of values by passing ```except```. ```except``` accepts the same types of parameters as
1070+
values (Procs, ranges, etc.).
1071+
1072+
```ruby
1073+
params do
1074+
requires :browsers, values: { except: [ 'ie6', 'ie7', 'ie8' ] }
1075+
end
1076+
```
1077+
1078+
Values and except can be combined to define a range of accepted values while not allowing
1079+
certain values within the set. Custom error messages can be defined for both when the parameter
1080+
passed falls within the ```except``` list or when it falls entirely outside the ```value``` list.
1081+
1082+
```ruby
1083+
params do
1084+
requires :number, type: Integer, values: { value: 1..20 except: [4,13], except_message: 'includes unsafe numbers', message: 'is outside the range of numbers allowed' }
1085+
end
1086+
```
1087+
10681088
#### `regexp`
10691089

10701090
Parameters can be restricted to match a specific regular expression with the `:regexp` option. If the value

lib/grape/locale/en.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ en:
88
regexp: 'is invalid'
99
blank: 'is empty'
1010
values: 'does not have a valid value'
11+
except: 'has a value not allowed'
1112
missing_vendor_option:
1213
problem: 'missing :vendor option.'
1314
summary: 'when version using header, you must specify :vendor option. '

lib/grape/validations/validators/values.rb

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ module Grape
22
module Validations
33
class ValuesValidator < Base
44
def initialize(attrs, options, required, scope)
5-
@values = (options_key?(:value, options) ? options[:value] : options)
5+
@excepts = (options_key?(:except, options) ? options[:except] : [])
6+
@values = (options_key?(:value, options) ? options[:value] : [])
7+
8+
@values = options if @excepts == [] && @values == []
69
super
710
end
811

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

1316
values = @values.is_a?(Proc) ? @values.call : @values
17+
excepts = @excepts.is_a?(Proc) ? @excepts.call : @excepts
1418
param_array = params[attr_name].nil? ? [nil] : Array.wrap(params[attr_name])
15-
return if param_array.all? { |param| values.include?(param) }
19+
20+
if param_array.all? { |param| excepts.include?(param) }
21+
raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: except_message
22+
end
23+
24+
return if (values.is_a?(Array) && values.empty?) || param_array.all? { |param| values.include?(param) }
1625
raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: message(:values)
1726
end
1827

1928
private
2029

30+
def except_message
31+
options = instance_variable_get(:@option)
32+
options_key?(:except_message) ? options[:except_message] : message(:except)
33+
end
34+
2135
def required_for_root_scope?
2236
@required && @scope.root?
2337
end

spec/grape/validations/validators/values_spec.rb

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
module ValidationsSpec
55
class ValuesModel
66
DEFAULT_VALUES = ['valid-type1', 'valid-type2', 'valid-type3'].freeze
7+
DEFAULT_EXCEPTS = ['invalid-type1', 'invalid-type2', 'invalid-type3'].freeze
78
class << self
89
def values
910
@values ||= []
@@ -14,6 +15,16 @@ def add_value(value)
1415
@values ||= []
1516
@values << value
1617
end
18+
19+
def excepts
20+
@excepts ||= []
21+
[DEFAULT_EXCEPTS + @excepts].flatten.uniq
22+
end
23+
24+
def add_except(except)
25+
@excepts ||= []
26+
@excepts << except
27+
end
1728
end
1829
end
1930

@@ -35,6 +46,20 @@ class API < Grape::API
3546
get '/lambda' do
3647
{ type: params[:type] }
3748
end
49+
50+
params do
51+
requires :type, values: { except: ValuesModel.excepts, except_message: 'value is on exclusions list', message: 'default exclude message' }
52+
end
53+
get '/exclude/exclude_message' do
54+
{ type: params[:type] }
55+
end
56+
57+
params do
58+
requires :type, values: { except: ValuesModel.excepts, message: 'default exclude message' }
59+
end
60+
get '/exclude/fallback_message' do
61+
{ type: params[:type] }
62+
end
3863
end
3964

4065
params do
@@ -99,6 +124,20 @@ class API < Grape::API
99124
end
100125
end
101126
get '/optional_with_required_values'
127+
128+
params do
129+
requires :type, values: { except: ValuesModel.excepts }
130+
end
131+
get '/except/exclusive' do
132+
{ type: params[:type] }
133+
end
134+
135+
params do
136+
requires :type, type: Integer, values: { value: 1..5, except: [3] }
137+
end
138+
get '/mixed/value/except' do
139+
{ type: params[:type] }
140+
end
102141
end
103142
end
104143
end
@@ -135,6 +174,22 @@ def app
135174
end
136175
end
137176

177+
context 'with a custom exclude validation message' do
178+
it 'does not allow an invalid value for a parameter' do
179+
get('/custom_message/exclude/exclude_message', type: 'invalid-type1')
180+
expect(last_response.status).to eq 400
181+
expect(last_response.body).to eq({ error: 'type value is on exclusions list' }.to_json)
182+
end
183+
end
184+
185+
context 'exclude with a standard custom validation message' do
186+
it 'does not allow an invalid value for a parameter' do
187+
get('/custom_message/exclude/fallback_message', type: 'invalid-type1')
188+
expect(last_response.status).to eq 400
189+
expect(last_response.body).to eq({ error: 'type default exclude message' }.to_json)
190+
end
191+
end
192+
138193
it 'allows a valid value for a parameter' do
139194
get('/', type: 'valid-type1')
140195
expect(last_response.status).to eq 200
@@ -321,4 +376,38 @@ def app
321376
expect(last_response.body).to eq('values does not have a valid value')
322377
end
323378
end
379+
380+
context 'exclusive excepts' do
381+
it 'allows any other value outside excepts' do
382+
get '/except/exclusive', type: 'value'
383+
expect(last_response.status).to eq 200
384+
expect(last_response.body).to eq({ type: 'value' }.to_json)
385+
end
386+
387+
it 'rejects values that matches except' do
388+
get '/except/exclusive', type: 'invalid-type1'
389+
expect(last_response.status).to eq 400
390+
expect(last_response.body).to eq({ error: 'type has a value not allowed' }.to_json)
391+
end
392+
end
393+
394+
context 'with mixed values and excepts' do
395+
it 'allows value, but not in except' do
396+
get '/mixed/value/except', type: 2
397+
expect(last_response.status).to eq 200
398+
expect(last_response.body).to eq({ type: 2 }.to_json)
399+
end
400+
401+
it 'rejects except' do
402+
get '/mixed/value/except', type: 3
403+
expect(last_response.status).to eq 400
404+
expect(last_response.body).to eq({ error: 'type has a value not allowed' }.to_json)
405+
end
406+
407+
it 'rejects outside except and outside value' do
408+
get '/mixed/value/except', type: 10
409+
expect(last_response.status).to eq 400
410+
expect(last_response.body).to eq({ error: 'type does not have a valid value' }.to_json)
411+
end
412+
end
324413
end

0 commit comments

Comments
 (0)