grape 0.2.1.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of grape might be problematic. Click here for more details.
- data/.gitignore +1 -0
- data/CHANGELOG.markdown +23 -2
- data/Gemfile +2 -0
- data/README.markdown +402 -227
- data/grape.gemspec +5 -2
- data/lib/grape.rb +6 -0
- data/lib/grape/api.rb +59 -2
- data/lib/grape/endpoint.rb +49 -9
- data/lib/grape/entity.rb +75 -8
- data/lib/grape/exceptions/base.rb +17 -0
- data/lib/grape/exceptions/validation_error.rb +10 -0
- data/lib/grape/middleware/base.rb +28 -19
- data/lib/grape/middleware/error.rb +11 -3
- data/lib/grape/middleware/formatter.rb +11 -18
- data/lib/grape/middleware/versioner/header.rb +76 -17
- data/lib/grape/util/deep_merge.rb +23 -0
- data/lib/grape/util/hash_stack.rb +12 -3
- data/lib/grape/validations.rb +202 -0
- data/lib/grape/validations/coerce.rb +61 -0
- data/lib/grape/validations/presence.rb +11 -0
- data/lib/grape/validations/regexp.rb +13 -0
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api_spec.rb +281 -123
- data/spec/grape/endpoint_spec.rb +69 -4
- data/spec/grape/entity_spec.rb +204 -16
- data/spec/grape/middleware/exception_spec.rb +21 -0
- data/spec/grape/middleware/formatter_spec.rb +19 -0
- data/spec/grape/middleware/versioner/header_spec.rb +159 -88
- data/spec/grape/validations/coerce_spec.rb +129 -0
- data/spec/grape/validations/presence_spec.rb +138 -0
- data/spec/grape/validations/regexp_spec.rb +33 -0
- data/spec/grape/validations_spec.rb +185 -0
- metadata +65 -74
- data/spec/grape_spec.rb +0 -1
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Grape::Validations::CoerceValidator do
|
4
|
+
subject { Class.new(Grape::API) }
|
5
|
+
def app; subject end
|
6
|
+
|
7
|
+
describe 'coerce' do
|
8
|
+
it 'error on malformed input' do
|
9
|
+
subject.params { requires :int, :type => Integer }
|
10
|
+
subject.get '/single' do 'int works'; end
|
11
|
+
|
12
|
+
get '/single', :int => '43a'
|
13
|
+
last_response.status.should == 400
|
14
|
+
last_response.body.should == 'invalid parameter: int'
|
15
|
+
|
16
|
+
get '/single', :int => '43'
|
17
|
+
last_response.status.should == 200
|
18
|
+
last_response.body.should == 'int works'
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'error on malformed input (Array)' do
|
22
|
+
subject.params { requires :ids, :type => Array[Integer] }
|
23
|
+
subject.get '/array' do 'array int works'; end
|
24
|
+
|
25
|
+
get 'array', { :ids => ['1', '2', 'az'] }
|
26
|
+
last_response.status.should == 400
|
27
|
+
last_response.body.should == 'invalid parameter: ids'
|
28
|
+
|
29
|
+
get 'array', { :ids => ['1', '2', '890'] }
|
30
|
+
last_response.status.should == 200
|
31
|
+
last_response.body.should == 'array int works'
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'complex objects' do
|
35
|
+
module CoerceValidatorSpec
|
36
|
+
class User
|
37
|
+
include Virtus
|
38
|
+
attribute :id, Integer
|
39
|
+
attribute :name, String
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'error on malformed input for complex objects' do
|
44
|
+
subject.params { requires :user, :type => CoerceValidatorSpec::User }
|
45
|
+
subject.get '/user' do 'complex works'; end
|
46
|
+
|
47
|
+
get '/user', :user => "32"
|
48
|
+
last_response.status.should == 400
|
49
|
+
last_response.body.should == 'invalid parameter: user'
|
50
|
+
|
51
|
+
get '/user', :user => { :id => 32, :name => 'Bob' }
|
52
|
+
last_response.status.should == 200
|
53
|
+
last_response.body.should == 'complex works'
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'coerces' do
|
58
|
+
it 'Integer' do
|
59
|
+
subject.params { requires :int, :coerce => Integer }
|
60
|
+
subject.get '/int' do params[:int].class; end
|
61
|
+
|
62
|
+
get '/int', { :int => "45" }
|
63
|
+
last_response.status.should == 200
|
64
|
+
last_response.body.should == 'Fixnum'
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'Array of Integers' do
|
68
|
+
subject.params { requires :arry, :coerce => Array[Integer] }
|
69
|
+
subject.get '/array' do params[:arry][0].class; end
|
70
|
+
|
71
|
+
get '/array', { :arry => [ '1', '2', '3' ] }
|
72
|
+
last_response.status.should == 200
|
73
|
+
last_response.body.should == 'Fixnum'
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'Array of Bools' do
|
77
|
+
subject.params { requires :arry, :coerce => Array[Virtus::Attribute::Boolean] }
|
78
|
+
subject.get '/array' do params[:arry][0].class; end
|
79
|
+
|
80
|
+
get 'array', { :arry => [1, 0] }
|
81
|
+
last_response.status.should == 200
|
82
|
+
last_response.body.should == 'TrueClass'
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'Bool' do
|
86
|
+
subject.params { requires :bool, :coerce => Virtus::Attribute::Boolean }
|
87
|
+
subject.get '/bool' do params[:bool].class; end
|
88
|
+
|
89
|
+
get '/bool', { :bool => 1 }
|
90
|
+
last_response.status.should == 200
|
91
|
+
last_response.body.should == 'TrueClass'
|
92
|
+
|
93
|
+
get '/bool', { :bool => 0 }
|
94
|
+
last_response.status.should == 200
|
95
|
+
last_response.body.should == 'FalseClass'
|
96
|
+
|
97
|
+
get '/bool', { :bool => 'false' }
|
98
|
+
last_response.status.should == 200
|
99
|
+
last_response.body.should == 'FalseClass'
|
100
|
+
|
101
|
+
get '/bool', { :bool => 'true' }
|
102
|
+
last_response.status.should == 200
|
103
|
+
last_response.body.should == 'TrueClass'
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'file' do
|
107
|
+
subject.params { requires :file, :coerce => Rack::Multipart::UploadedFile }
|
108
|
+
subject.post '/upload' do params[:file].filename; end
|
109
|
+
|
110
|
+
post '/upload', { :file => Rack::Test::UploadedFile.new(__FILE__) }
|
111
|
+
last_response.status.should == 201
|
112
|
+
last_response.body.should == File.basename(__FILE__).to_s
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'Nests integers' do
|
116
|
+
subject.params do
|
117
|
+
group :integers do
|
118
|
+
requires :int, :coerce => Integer
|
119
|
+
end
|
120
|
+
end
|
121
|
+
subject.get '/int' do params[:integers][:int].class; end
|
122
|
+
|
123
|
+
get '/int', { :integers => { :int => "45" } }
|
124
|
+
last_response.status.should == 200
|
125
|
+
last_response.body.should == 'Fixnum'
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Grape::Validations::PresenceValidator do
|
4
|
+
|
5
|
+
module ValidationsSpec
|
6
|
+
module PresenceValidatorSpec
|
7
|
+
class API < Grape::API
|
8
|
+
default_format :json
|
9
|
+
|
10
|
+
resource :bacons do
|
11
|
+
get do
|
12
|
+
"All the bacon"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
params do
|
17
|
+
requires :id, :regexp => /^[0-9]+$/
|
18
|
+
end
|
19
|
+
post do
|
20
|
+
{:ret => params[:id]}
|
21
|
+
end
|
22
|
+
|
23
|
+
params do
|
24
|
+
requires :name, :company
|
25
|
+
end
|
26
|
+
get do
|
27
|
+
"Hello"
|
28
|
+
end
|
29
|
+
|
30
|
+
params do
|
31
|
+
group :user do
|
32
|
+
requires :first_name, :last_name
|
33
|
+
end
|
34
|
+
end
|
35
|
+
get '/nested' do
|
36
|
+
"Nested"
|
37
|
+
end
|
38
|
+
|
39
|
+
params do
|
40
|
+
group :admin do
|
41
|
+
requires :admin_name
|
42
|
+
group :super do
|
43
|
+
group :user do
|
44
|
+
requires :first_name, :last_name
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
get '/nested_triple' do
|
50
|
+
"Nested triple"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def app
|
57
|
+
ValidationsSpec::PresenceValidatorSpec::API
|
58
|
+
end
|
59
|
+
|
60
|
+
it "does not validate for any params" do
|
61
|
+
get("/bacons")
|
62
|
+
last_response.status.should == 200
|
63
|
+
last_response.body.should == "All the bacon"
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'validates id' do
|
67
|
+
post('/')
|
68
|
+
last_response.status.should == 400
|
69
|
+
last_response.body.should == "missing parameter: id"
|
70
|
+
|
71
|
+
post('/', {}, 'rack.input' => StringIO.new('{"id" : "a56b"}'))
|
72
|
+
last_response.body.should == 'invalid parameter: id'
|
73
|
+
last_response.status.should == 400
|
74
|
+
|
75
|
+
post('/', {}, 'rack.input' => StringIO.new('{"id" : 56}'))
|
76
|
+
last_response.body.should == '{"ret":56}'
|
77
|
+
last_response.status.should == 201
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'validates name, company' do
|
81
|
+
get('/')
|
82
|
+
last_response.status.should == 400
|
83
|
+
last_response.body.should == "missing parameter: name"
|
84
|
+
|
85
|
+
get('/', :name => "Bob")
|
86
|
+
last_response.status.should == 400
|
87
|
+
last_response.body.should == "missing parameter: company"
|
88
|
+
|
89
|
+
get('/', :name => "Bob", :company => "TestCorp")
|
90
|
+
last_response.status.should == 200
|
91
|
+
last_response.body.should == "Hello"
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'validates nested parameters' do
|
95
|
+
get('/nested')
|
96
|
+
last_response.status.should == 400
|
97
|
+
last_response.body.should == "missing parameter: first_name"
|
98
|
+
|
99
|
+
get('/nested', :user => {:first_name => "Billy"})
|
100
|
+
last_response.status.should == 400
|
101
|
+
last_response.body.should == "missing parameter: last_name"
|
102
|
+
|
103
|
+
get('/nested', :user => {:first_name => "Billy", :last_name => "Bob"})
|
104
|
+
last_response.status.should == 200
|
105
|
+
last_response.body.should == "Nested"
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'validates triple nested parameters' do
|
109
|
+
get('/nested_triple')
|
110
|
+
last_response.status.should == 400
|
111
|
+
last_response.body.should == "missing parameter: admin_name"
|
112
|
+
|
113
|
+
get('/nested_triple', :user => {:first_name => "Billy"})
|
114
|
+
last_response.status.should == 400
|
115
|
+
last_response.body.should == "missing parameter: admin_name"
|
116
|
+
|
117
|
+
get('/nested_triple', :admin => {:super => {:first_name => "Billy"}})
|
118
|
+
last_response.status.should == 400
|
119
|
+
last_response.body.should == "missing parameter: admin_name"
|
120
|
+
|
121
|
+
get('/nested_triple', :super => {:user => {:first_name => "Billy", :last_name => "Bob"}})
|
122
|
+
last_response.status.should == 400
|
123
|
+
last_response.body.should == "missing parameter: admin_name"
|
124
|
+
|
125
|
+
get('/nested_triple', :admin => {:super => {:user => {:first_name => "Billy"}}})
|
126
|
+
last_response.status.should == 400
|
127
|
+
last_response.body.should == "missing parameter: admin_name"
|
128
|
+
|
129
|
+
get('/nested_triple', :admin => { :admin_name => 'admin', :super => {:user => {:first_name => "Billy"}}})
|
130
|
+
last_response.status.should == 400
|
131
|
+
last_response.body.should == "missing parameter: last_name"
|
132
|
+
|
133
|
+
get('/nested_triple', :admin => { :admin_name => 'admin', :super => {:user => {:first_name => "Billy", :last_name => "Bob"}}})
|
134
|
+
last_response.status.should == 200
|
135
|
+
last_response.body.should == "Nested triple"
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Grape::Validations::RegexpValidator do
|
4
|
+
module ValidationsSpec
|
5
|
+
module RegexpValidatorSpec
|
6
|
+
class API < Grape::API
|
7
|
+
default_format :json
|
8
|
+
|
9
|
+
params do
|
10
|
+
requires :name, :regexp => /^[a-z]+$/
|
11
|
+
end
|
12
|
+
get do
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def app
|
20
|
+
ValidationsSpec::RegexpValidatorSpec::API
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should refuse invalid input' do
|
24
|
+
get '/', :name => "invalid name"
|
25
|
+
last_response.status.should == 400
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should accept valid input' do
|
29
|
+
get '/', :name => "bob"
|
30
|
+
last_response.status.should == 200
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,185 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Grape::Validations do
|
4
|
+
subject { Class.new(Grape::API) }
|
5
|
+
def app; subject end
|
6
|
+
|
7
|
+
describe 'params' do
|
8
|
+
context 'optional' do
|
9
|
+
it 'validates when params is present' do
|
10
|
+
subject.params { optional :a_number, :regexp => /^[0-9]+$/ }
|
11
|
+
subject.get '/optional' do 'optional works!'; end
|
12
|
+
|
13
|
+
get '/optional', { :a_number => 'string' }
|
14
|
+
last_response.status.should == 400
|
15
|
+
last_response.body.should == 'invalid parameter: a_number'
|
16
|
+
|
17
|
+
get '/optional', { :a_number => 45 }
|
18
|
+
last_response.status.should == 200
|
19
|
+
last_response.body.should == 'optional works!'
|
20
|
+
end
|
21
|
+
|
22
|
+
it "doesn't validate when param not present" do
|
23
|
+
subject.params { optional :a_number, :regexp => /^[0-9]+$/ }
|
24
|
+
subject.get '/optional' do 'optional works!'; end
|
25
|
+
|
26
|
+
get '/optional'
|
27
|
+
last_response.status.should == 200
|
28
|
+
last_response.body.should == 'optional works!'
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'adds to declared parameters' do
|
32
|
+
subject.params { optional :some_param }
|
33
|
+
subject.settings[:declared_params].should == [:some_param]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'required' do
|
38
|
+
before do
|
39
|
+
subject.params { requires :key }
|
40
|
+
subject.get '/required' do 'required works'; end
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'errors when param not present' do
|
44
|
+
get '/required'
|
45
|
+
last_response.status.should == 400
|
46
|
+
last_response.body.should == 'missing parameter: key'
|
47
|
+
end
|
48
|
+
|
49
|
+
it "doesn't throw a missing param when param is present" do
|
50
|
+
get '/required', { :key => 'cool' }
|
51
|
+
last_response.status.should == 200
|
52
|
+
last_response.body.should == 'required works'
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'adds to declared parameters' do
|
56
|
+
subject.params { requires :some_param }
|
57
|
+
subject.settings[:declared_params].should == [:some_param]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context 'custom validation' do
|
62
|
+
module CustomValidations
|
63
|
+
class Customvalidator < Grape::Validations::Validator
|
64
|
+
def validate_param!(attr_name, params)
|
65
|
+
unless params[attr_name] == 'im custom'
|
66
|
+
throw :error, :status => 400, :message => "#{attr_name}: is not custom!"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context 'when using optional with a custom validator' do
|
73
|
+
before do
|
74
|
+
subject.params { optional :custom, :customvalidator => true }
|
75
|
+
subject.get '/optional_custom' do 'optional with custom works!'; end
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'validates when param is present' do
|
79
|
+
get '/optional_custom', { :custom => 'im custom' }
|
80
|
+
last_response.status.should == 200
|
81
|
+
last_response.body.should == 'optional with custom works!'
|
82
|
+
|
83
|
+
get '/optional_custom', { :custom => 'im wrong' }
|
84
|
+
last_response.status.should == 400
|
85
|
+
last_response.body.should == 'custom: is not custom!'
|
86
|
+
end
|
87
|
+
|
88
|
+
it "skip validation when parameter isn't present" do
|
89
|
+
get '/optional_custom'
|
90
|
+
last_response.status.should == 200
|
91
|
+
last_response.body.should == 'optional with custom works!'
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'validates with custom validator when param present and incorrect type' do
|
95
|
+
subject.params { optional :custom, :type => String, :customvalidator => true }
|
96
|
+
|
97
|
+
get '/optional_custom', { :custom => 123 }
|
98
|
+
last_response.status.should == 400
|
99
|
+
last_response.body.should == 'custom: is not custom!'
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
context 'when using requires with a custom validator' do
|
104
|
+
before do
|
105
|
+
subject.params { requires :custom, :customvalidator => true }
|
106
|
+
subject.get '/required_custom' do 'required with custom works!'; end
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'validates when param is present' do
|
110
|
+
get '/required_custom', { :custom => 'im wrong, validate me' }
|
111
|
+
last_response.status.should == 400
|
112
|
+
last_response.body.should == 'custom: is not custom!'
|
113
|
+
|
114
|
+
get '/required_custom', { :custom => 'im custom' }
|
115
|
+
last_response.status.should == 200
|
116
|
+
last_response.body.should == 'required with custom works!'
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'validates when param is not present' do
|
120
|
+
get '/required_custom'
|
121
|
+
last_response.status.should == 400
|
122
|
+
last_response.body.should == 'missing parameter: custom'
|
123
|
+
end
|
124
|
+
|
125
|
+
context 'nested namespaces' do
|
126
|
+
before do
|
127
|
+
subject.params { requires :custom, :customvalidator => true }
|
128
|
+
subject.namespace 'nested' do
|
129
|
+
get 'one' do 'validation failed' end
|
130
|
+
namespace 'nested' do
|
131
|
+
get 'two' do 'validation failed' end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
subject.namespace 'peer' do
|
135
|
+
get 'one' do 'no validation required' end
|
136
|
+
namespace 'nested' do
|
137
|
+
get 'two' do 'no validation required' end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
subject.namespace 'unrelated' do
|
142
|
+
params{ requires :name }
|
143
|
+
get 'one' do 'validation required'; end
|
144
|
+
|
145
|
+
namespace 'double' do
|
146
|
+
get 'two' do 'no validation required' end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
specify 'the parent namespace should use the validator' do
|
152
|
+
get '/nested/one', { :custom => 'im wrong, validate me'}
|
153
|
+
last_response.status.should == 400
|
154
|
+
last_response.body.should == 'custom: is not custom!'
|
155
|
+
end
|
156
|
+
|
157
|
+
specify 'the nested namesapce should inherit the custom validator' do
|
158
|
+
get '/nested/nested/two', { :custom => 'im wrong, validate me'}
|
159
|
+
last_response.status.should == 400
|
160
|
+
last_response.body.should == 'custom: is not custom!'
|
161
|
+
end
|
162
|
+
|
163
|
+
specify 'peer namesapces should not have the validator' do
|
164
|
+
get '/peer/one', { :custom => 'im not validated' }
|
165
|
+
last_response.status.should == 200
|
166
|
+
last_response.body.should == 'no validation required'
|
167
|
+
end
|
168
|
+
|
169
|
+
specify 'namespaces nested in peers should also not have the validator' do
|
170
|
+
get '/peer/nested/two', { :custom => 'im not validated' }
|
171
|
+
last_response.status.should == 200
|
172
|
+
last_response.body.should == 'no validation required'
|
173
|
+
end
|
174
|
+
|
175
|
+
specify 'when nested, specifying a route should clear out the validations for deeper nested params' do
|
176
|
+
get '/unrelated/one'
|
177
|
+
last_response.status.should == 400
|
178
|
+
get '/unrelated/double/two'
|
179
|
+
last_response.status.should == 200
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end # end custom validation
|
184
|
+
end
|
185
|
+
end
|