Skip to content

Commit d124df7

Browse files
authored
Merge pull request #1568 from jlfaber/check_with
Add proc option to values validator
2 parents 081d09b + 5d520ba commit d124df7

File tree

4 files changed

+67
-9
lines changed

4 files changed

+67
-9
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
* [#1555](https://github.com/ruby-grape/grape/pull/1555): Added code coverage w/Coveralls - [@dblock](https://github.com/dblock).
6+
* [#1568](https://github.com/ruby-grape/grape/pull/1568): Add `proc` option to `values` validator to allow custom checks - [@jlfaber](https://github.com/jlfaber).
67
* Your contribution here.
78

89
#### Fixes

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1152,6 +1152,20 @@ params do
11521152
end
11531153
```
11541154

1155+
Finally, for even greater control, an explicit validation Proc may be supplied using ```proc```.
1156+
It will be called with a single argument (the input value), and should return
1157+
a truthy value if the value passes validation. If the input is an array, the Proc will be called
1158+
multiple times, once for each element in the array.
1159+
1160+
```ruby
1161+
params do
1162+
requires :number, type: Integer, values: { proc: ->(v) { v.even? && v < 25 }, message: 'is odd or greater than 25' }
1163+
end
1164+
```
1165+
1166+
While ```proc``` is convenient for single cases, consider using [Custom Validators](#custom-validators) in cases where a validation is used more than once.
1167+
1168+
11551169
#### `regexp`
11561170

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

lib/grape/validations/validators/values.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ def initialize(attrs, options, required, scope, opts = {})
55
if options.is_a?(Hash)
66
@excepts = options[:except]
77
@values = options[:value]
8+
@proc = options[:proc]
9+
raise ArgumentError, 'proc must be a Proc' if @proc && !@proc.is_a?(Proc)
810
else
911
@values = options
1012
end
@@ -24,6 +26,9 @@ def validate_param!(attr_name, params)
2426

2527
raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: message(:values) \
2628
if !values.nil? && !param_array.all? { |param| values.include?(param) }
29+
30+
raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: message(:values) \
31+
if @proc && !param_array.all? { |param| @proc.call(param) }
2732
end
2833

2934
private

spec/grape/validations/validators/values_spec.rb

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,23 +50,17 @@ class API < Grape::API
5050
params do
5151
requires :type, values: { except: ValuesModel.excepts, except_message: 'value is on exclusions list', message: 'default exclude message' }
5252
end
53-
get '/exclude/exclude_message' do
54-
{ type: params[:type] }
55-
end
53+
get '/exclude/exclude_message'
5654

5755
params do
5856
requires :type, values: { except: -> { ValuesModel.excepts }, except_message: 'value is on exclusions list' }
5957
end
60-
get '/exclude/lambda/exclude_message' do
61-
{ type: params[:type] }
62-
end
58+
get '/exclude/lambda/exclude_message'
6359

6460
params do
6561
requires :type, values: { except: ValuesModel.excepts, message: 'default exclude message' }
6662
end
67-
get '/exclude/fallback_message' do
68-
{ type: params[:type] }
69-
end
63+
get '/exclude/fallback_message'
7064
end
7165

7266
params do
@@ -174,6 +168,18 @@ class API < Grape::API
174168
optional :optional, type: Array[String], values: %w(a b c)
175169
end
176170
put '/optional_with_array_of_string_values'
171+
172+
params do
173+
requires :type, values: { proc: ->(v) { ValuesModel.values.include? v } }
174+
end
175+
get '/proc' do
176+
{ type: params[:type] }
177+
end
178+
179+
params do
180+
requires :type, values: { proc: ->(v) { ValuesModel.values.include? v }, message: 'failed check' }
181+
end
182+
get '/proc/message'
177183
end
178184
end
179185
end
@@ -505,4 +511,36 @@ def app
505511
expect(last_response.body).to eq({ error: 'type does not have a valid value' }.to_json)
506512
end
507513
end
514+
515+
context 'custom validation using proc' do
516+
it 'accepts a single valid value' do
517+
get '/proc', type: 'valid-type1'
518+
expect(last_response.status).to eq 200
519+
expect(last_response.body).to eq({ type: 'valid-type1' }.to_json)
520+
end
521+
522+
it 'accepts multiple valid values' do
523+
get '/proc', type: ['valid-type1', 'valid-type3']
524+
expect(last_response.status).to eq 200
525+
expect(last_response.body).to eq({ type: ['valid-type1', 'valid-type3'] }.to_json)
526+
end
527+
528+
it 'rejects a single invalid value' do
529+
get '/proc', type: 'invalid-type1'
530+
expect(last_response.status).to eq 400
531+
expect(last_response.body).to eq({ error: 'type does not have a valid value' }.to_json)
532+
end
533+
534+
it 'rejects an invalid value among valid ones' do
535+
get '/proc', type: ['valid-type1', 'invalid-type1', 'valid-type3']
536+
expect(last_response.status).to eq 400
537+
expect(last_response.body).to eq({ error: 'type does not have a valid value' }.to_json)
538+
end
539+
540+
it 'uses supplied message' do
541+
get '/proc/message', type: 'invalid-type1'
542+
expect(last_response.status).to eq 400
543+
expect(last_response.body).to eq({ error: 'type failed check' }.to_json)
544+
end
545+
end
508546
end

0 commit comments

Comments
 (0)