Skip to content

Commit b5db980

Browse files
committed
added validation behind the coercion
1 parent 8f89489 commit b5db980

File tree

3 files changed

+93
-13
lines changed

3 files changed

+93
-13
lines changed

README.markdown

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -207,14 +207,8 @@ get ':id' do
207207
end
208208
```
209209

210-
Declaring a parameter type causes the value to be coerced into that type, where possible.
211-
There is now implicit validation meaning that invalid input such as "foo" for an Integer will be left unmodified and passed through into the API,
212-
this might change in a future release.
210+
When a type is specified an implicit validation is done after the coercion to ensure the output type is what you asked.
213211

214-
However validations are executed in the order defined which allows you to do this:
215-
```ruby
216-
requires :id, regexp: /^[0-9]+$/, type: Integer
217-
```
218212

219213
## Headers
220214

lib/grape/validations/coerce.rb

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,48 @@ module Validations
88

99
class CoerceValidator < SingleOptionValidator
1010
def validate_param!(attr_name, params)
11-
params[attr_name] = coerce_value(@option, params[attr_name])
11+
new_value = coerce_value(@option, params[attr_name])
12+
if valid_type?(new_value)
13+
params[attr_name] = new_value
14+
else
15+
throw :error, :status => 400, :message => "invalid parameter: #{attr_name}"
16+
end
1217
end
1318

1419
private
20+
def _valid_array_type?(type, values)
21+
values.all? do |val|
22+
_valid_single_type?(type, val)
23+
end
24+
end
25+
26+
27+
def _valid_single_type?(klass, val)
28+
if klass == Virtus::Attribute::Boolean
29+
val.is_a?(TrueClass) || val.is_a?(FalseClass)
30+
else
31+
val.is_a?(klass)
32+
end
33+
end
34+
35+
def valid_type?(val)
36+
if @option.is_a?(Array)
37+
_valid_array_type?(@option[0], val)
38+
else
39+
_valid_single_type?(@option, val)
40+
end
41+
end
42+
1543
def coerce_value(type, val)
1644
converter = Virtus::Attribute.build(:a, type)
1745
converter.coerce(val)
46+
47+
# not the prettiest but some invalid coercion can currently trigger
48+
# errors in Virtus (see coerce_spec.rb)
49+
rescue => err
50+
nil
1851
end
52+
1953
end
2054

2155
end

spec/grape/validations/coerce_spec.rb

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,40 @@
11
require 'spec_helper'
22

3+
require 'virtus'
4+
5+
module CoerceTest
6+
class User
7+
include Virtus
8+
9+
attribute :id, Integer
10+
attribute :name, String
11+
end
12+
13+
end
14+
15+
316
class CoerceAPI < Grape::API
417
default_format :json
518

19+
20+
params do
21+
requires :int, :coerce => Integer
22+
end
23+
get '/single' do
24+
end
25+
26+
params do
27+
requires :ids, type: Array[Integer]
28+
end
29+
get '/arr' do
30+
end
31+
32+
params do
33+
requires :user, type: CoerceTest::User
34+
end
35+
get '/user' do
36+
end
37+
638
params do
739
requires :int, :coerce => Integer
840
optional :arr, :coerce => Array[Integer]
@@ -24,11 +56,30 @@ def app; @app; end
2456
@app = CoerceAPI
2557
end
2658

27-
# TOOD: Later when virtus can tell us that an input IS invalid
28-
# it "should return an error on malformed input" do
29-
# get '/coerce', :int => "43a"
30-
# last_response.status.should == 400
31-
# end
59+
it "should return an error on malformed input" do
60+
get '/single', :int => "43a"
61+
last_response.status.should == 400
62+
63+
get '/single', :int => "43"
64+
last_response.status.should == 200
65+
end
66+
67+
it "should return an error on malformed input (array)" do
68+
get '/arr', :ids => ["1", "2", "az"]
69+
last_response.status.should == 400
70+
71+
get '/arr', :ids => ["1", "2", "890"]
72+
last_response.status.should == 200
73+
end
74+
75+
it "should return an error on malformed input (complex object)" do
76+
# this request does raise an error inside Virtus
77+
get '/user', :user => "32"
78+
last_response.status.should == 400
79+
80+
get '/user', :user => {id: 32, name: "Bob"}
81+
last_response.status.should == 200
82+
end
3283

3384
it 'should coerce inputs' do
3485
get('/coerce', :int => "43")
@@ -37,6 +88,7 @@ def app; @app; end
3788
ret["int"].should == "Fixnum"
3889

3990
get('/coerce', :int => "40", :arr => ["1","20","3"], :bool => [1, 0])
91+
# last_response.body.should == ""
4092
last_response.status.should == 200
4193
ret = MultiJson.load(last_response.body)
4294
ret["int"].should == "Fixnum"

0 commit comments

Comments
 (0)