Skip to content

Commit d0d7872

Browse files
committed
reworked validations
- extracted validators - auto-register validators - added params scope
1 parent 79435d6 commit d0d7872

File tree

6 files changed

+145
-83
lines changed

6 files changed

+145
-83
lines changed

lib/grape/api.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ module Grape
88
# creating Grape APIs.Users should subclass this
99
# class in order to build an API.
1010
class API
11-
include Validations
11+
extend Validations::ClassMethods
1212

1313
class << self
1414
attr_reader :route_set

lib/grape/validations.rb

Lines changed: 85 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,95 +1,76 @@
11
require 'virtus'
22
Boolean = Virtus::Attribute::Boolean
3+
34
module Grape
4-
5-
class Validator
6-
def initialize(attrs, options)
7-
@attrs = Array(attrs)
8-
9-
if options.is_a?(Hash) && !options.empty?
10-
raise "unknown options: #{options.keys}"
11-
end
12-
end
135

14-
def validate!(params)
15-
@attrs.each do |attr_name|
16-
validate_param!(attr_name, params)
17-
end
18-
end
19-
end
20-
21-
22-
class SingleOptionValidator < Validator
23-
def initialize(attrs, options)
24-
@option = options
25-
super
26-
end
6+
module Validations
277

28-
end
29-
30-
31-
class PresenceValidator < Validator
32-
def validate_param!(attr_name, params)
33-
unless params.has_key?(attr_name)
34-
throw :error, :status => 400, :message => "missing parameter: #{attr_name}"
8+
##
9+
# All validators must inherit from this class.
10+
#
11+
class Validator
12+
def initialize(attrs, options)
13+
@attrs = Array(attrs)
14+
15+
if options.is_a?(Hash) && !options.empty?
16+
raise "unknown options: #{options.keys}"
17+
end
18+
end
19+
20+
def validate!(params)
21+
@attrs.each do |attr_name|
22+
validate_param!(attr_name, params)
23+
end
24+
end
25+
26+
private
27+
28+
def self.convert_to_short_name(klass)
29+
ret = klass.name.gsub(/::/, '/').
30+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
31+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
32+
tr("-", "_").
33+
downcase
34+
File.basename(ret, '_validator')
3535
end
3636
end
37-
38-
end
39-
40-
class CoerceValidator < SingleOptionValidator
41-
def validate_param!(attr_name, params)
42-
params[attr_name] = coerce_value(@option, params[attr_name])
43-
end
44-
45-
private
46-
def coerce_value(type, val)
47-
converter = Virtus::Attribute.build(:a, type)
48-
converter.coerce(val)
37+
38+
##
39+
# Base class for all validators taking only one param.
40+
class SingleOptionValidator < Validator
41+
def initialize(attrs, options)
42+
@option = options
43+
super
44+
end
45+
4946
end
50-
end
51-
52-
class RegExpValidator < SingleOptionValidator
53-
def validate_param!(attr_name, params)
54-
if params[attr_name] && !( params[attr_name].to_s =~ @option )
55-
throw :error, :status => 400, :message => "invalid parameter: #{attr_name}"
47+
48+
# we define Validator::inherited here so SingleOptionValidator
49+
# will not be considered a validator.
50+
class Validator
51+
def self.inherited(klass)
52+
short_name = convert_to_short_name(klass)
53+
Validations::register_validator(short_name, klass)
5654
end
5755
end
58-
end
5956

60-
61-
62-
module Validations
57+
6358

6459
class <<self
6560
attr_accessor :validators
6661
end
6762

6863
self.validators = {}
69-
self.validators[:presence] = PresenceValidator
70-
self.validators[:regexp] = RegExpValidator
71-
self.validators[:coerce] = CoerceValidator
7264

73-
def self.included(klass)
74-
klass.instance_eval do
75-
extend ClassMethods
76-
end
65+
def self.register_validator(short_name, klass)
66+
validators[short_name] = klass
7767
end
7868

79-
module ClassMethods
80-
def reset_validations!
81-
settings[:validations] = []
82-
end
83-
84-
def document_attribute(names, opts)
85-
if @last_description
86-
@last_description[:params] ||= {}
87-
88-
Array(names).each do |name|
89-
@last_description[:params][name.to_sym] ||= {}
90-
@last_description[:params][name.to_sym].merge!(opts)
91-
end
92-
end
69+
70+
class ParamsScope
71+
def initialize(api, &block)
72+
@api = api
73+
instance_eval(&block)
9374
end
9475

9576
def requires(*attrs)
@@ -110,6 +91,7 @@ def optional(*attrs)
11091
validates(attrs, validations)
11192
end
11293

94+
private
11395
def validates(attrs, validations)
11496
doc_attrs = { :required => validations.keys.include?(:presence) }
11597

@@ -121,21 +103,48 @@ def validates(attrs, validations)
121103
doc_attrs[:desc] = desc
122104
end
123105

124-
document_attribute(attrs, doc_attrs)
106+
@api.document_attribute(attrs, doc_attrs)
125107

126108
validations.each do |type, options|
127-
validator_class = Validations::validators[type]
109+
validator_class = Validations::validators[type.to_s]
128110
if validator_class
129-
settings[:validations] << validator_class.new(attrs, options)
111+
@api.settings[:validations] << validator_class.new(attrs, options)
130112
else
131113
raise "unknown validator: #{type}"
132114
end
133115
end
116+
117+
end
118+
119+
end
120+
121+
# This module is mixed into the API Class.
122+
module ClassMethods
123+
def reset_validations!
124+
settings[:validations] = []
125+
end
126+
127+
def params(&block)
128+
ParamsScope.new(self, &block)
129+
end
130+
131+
def document_attribute(names, opts)
132+
if @last_description
133+
@last_description[:params] ||= {}
134134

135+
Array(names).each do |name|
136+
@last_description[:params][name.to_sym] ||= {}
137+
@last_description[:params][name.to_sym].merge!(opts)
138+
end
139+
end
135140
end
136-
137141

138142
end
139143

140144
end
141145
end
146+
147+
# load all defined validations
148+
Dir[File.expand_path('../validations/*.rb', __FILE__)].each do |path|
149+
require(path)
150+
end

lib/grape/validations/coerce.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
module Grape
2+
module Validations
3+
4+
class CoerceValidator < SingleOptionValidator
5+
def validate_param!(attr_name, params)
6+
params[attr_name] = coerce_value(@option, params[attr_name])
7+
end
8+
9+
private
10+
def coerce_value(type, val)
11+
converter = Virtus::Attribute.build(:a, type)
12+
converter.coerce(val)
13+
end
14+
end
15+
16+
end
17+
end

lib/grape/validations/presence.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
module Grape
2+
module Validations
3+
4+
class PresenceValidator < Validator
5+
def validate_param!(attr_name, params)
6+
unless params.has_key?(attr_name)
7+
throw :error, :status => 400, :message => "missing parameter: #{attr_name}"
8+
end
9+
end
10+
11+
end
12+
13+
end
14+
end

lib/grape/validations/regexp.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
module Grape
2+
module Validations
3+
4+
class RegexpValidator < SingleOptionValidator
5+
def validate_param!(attr_name, params)
6+
if params[attr_name] && !( params[attr_name].to_s =~ @option )
7+
throw :error, :status => 400, :message => "invalid parameter: #{attr_name}"
8+
end
9+
end
10+
end
11+
12+
end
13+
end

spec/grape/validations_spec.rb

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,29 @@ def app; @app; end
77
@app = Class.new(Grape::API) do
88
default_format :json
99

10-
requires :id, :regexp => /^[0-9]+$/
10+
11+
params do
12+
requires :id, :regexp => /^[0-9]+$/
13+
end
14+
1115
post do
1216
{:ret => params[:id]}
1317
end
1418

15-
requires :name, :company
16-
optional :a_number, :regexp => /^[0-9]+$/
19+
params do
20+
requires :name, :company
21+
optional :a_number, :regexp => /^[0-9]+$/
22+
end
23+
1724
get do
1825
"Hello"
1926
end
2027

21-
requires :int, :coerce => Integer
22-
optional :arr, :coerce => Array[Integer]
23-
optional :bool, :coerce => Array[Boolean]
28+
params do
29+
requires :int, :coerce => Integer
30+
optional :arr, :coerce => Array[Integer]
31+
optional :bool, :coerce => Array[Boolean]
32+
end
2433
get '/coerce' do
2534
{
2635
:int => params[:int].class,

0 commit comments

Comments
 (0)