grape 0.5.0 → 0.6.0

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.

Files changed (38) hide show
  1. checksums.yaml +15 -0
  2. data/.travis.yml +2 -6
  3. data/CHANGELOG.md +20 -0
  4. data/README.md +43 -11
  5. data/lib/grape.rb +6 -0
  6. data/lib/grape/api.rb +27 -14
  7. data/lib/grape/endpoint.rb +33 -34
  8. data/lib/grape/exceptions/base.rb +4 -2
  9. data/lib/grape/exceptions/validation.rb +13 -3
  10. data/lib/grape/exceptions/validation_errors.rb +42 -0
  11. data/lib/grape/http/request.rb +1 -1
  12. data/lib/grape/locale/en.yml +4 -3
  13. data/lib/grape/middleware/auth/base.rb +30 -0
  14. data/lib/grape/middleware/auth/basic.rb +2 -19
  15. data/lib/grape/middleware/auth/digest.rb +2 -19
  16. data/lib/grape/middleware/error.rb +10 -1
  17. data/lib/grape/middleware/formatter.rb +1 -1
  18. data/lib/grape/middleware/versioner/accept_version_header.rb +1 -1
  19. data/lib/grape/middleware/versioner/header.rb +2 -2
  20. data/lib/grape/path.rb +72 -0
  21. data/lib/grape/route.rb +6 -1
  22. data/lib/grape/validations.rb +25 -8
  23. data/lib/grape/validations/coerce.rb +1 -2
  24. data/lib/grape/validations/presence.rb +6 -2
  25. data/lib/grape/validations/regexp.rb +1 -2
  26. data/lib/grape/version.rb +1 -1
  27. data/spec/grape/api_spec.rb +71 -6
  28. data/spec/grape/entity_spec.rb +5 -5
  29. data/spec/grape/middleware/base_spec.rb +1 -1
  30. data/spec/grape/middleware/formatter_spec.rb +3 -3
  31. data/spec/grape/middleware/versioner/header_spec.rb +25 -0
  32. data/spec/grape/path_spec.rb +219 -0
  33. data/spec/grape/validations/coerce_spec.rb +31 -13
  34. data/spec/grape/validations/presence_spec.rb +12 -12
  35. data/spec/grape/validations/zh-CN.yml +4 -3
  36. data/spec/grape/validations_spec.rb +154 -10
  37. data/spec/support/versioned_helpers.rb +5 -2
  38. metadata +10 -45
@@ -25,7 +25,7 @@ describe Grape::Entity do
25
25
 
26
26
  it 'pulls a representation from the class options if it exists' do
27
27
  entity = Class.new(Grape::Entity)
28
- entity.stub!(:represent).and_return("Hiya")
28
+ entity.stub(:represent).and_return("Hiya")
29
29
 
30
30
  subject.represent Object, :with => entity
31
31
  subject.get '/example' do
@@ -37,7 +37,7 @@ describe Grape::Entity do
37
37
 
38
38
  it 'pulls a representation from the class options if the presented object is a collection of objects' do
39
39
  entity = Class.new(Grape::Entity)
40
- entity.stub!(:represent).and_return("Hiya")
40
+ entity.stub(:represent).and_return("Hiya")
41
41
 
42
42
  class TestObject; end
43
43
  class FakeCollection
@@ -62,7 +62,7 @@ describe Grape::Entity do
62
62
 
63
63
  it 'pulls a representation from the class ancestor if it exists' do
64
64
  entity = Class.new(Grape::Entity)
65
- entity.stub!(:represent).and_return("Hiya")
65
+ entity.stub(:represent).and_return("Hiya")
66
66
 
67
67
  subclass = Class.new(Object)
68
68
 
@@ -77,7 +77,7 @@ describe Grape::Entity do
77
77
  it 'automatically uses Klass::Entity if that exists' do
78
78
  some_model = Class.new
79
79
  entity = Class.new(Grape::Entity)
80
- entity.stub!(:represent).and_return("Auto-detect!")
80
+ entity.stub(:represent).and_return("Auto-detect!")
81
81
 
82
82
  some_model.const_set :Entity, entity
83
83
 
@@ -91,7 +91,7 @@ describe Grape::Entity do
91
91
  it 'automatically uses Klass::Entity based on the first object in the collection being presented' do
92
92
  some_model = Class.new
93
93
  entity = Class.new(Grape::Entity)
94
- entity.stub!(:represent).and_return("Auto-detect!")
94
+ entity.stub(:represent).and_return("Auto-detect!")
95
95
 
96
96
  some_model.const_set :Entity, entity
97
97
 
@@ -6,7 +6,7 @@ describe Grape::Middleware::Base do
6
6
 
7
7
  before do
8
8
  # Keep it one object for testing.
9
- subject.stub!(:dup).and_return(subject)
9
+ subject.stub(:dup).and_return(subject)
10
10
  end
11
11
 
12
12
  it 'has the app as an accessor' do
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  describe Grape::Middleware::Formatter do
4
4
  subject{ Grape::Middleware::Formatter.new(app) }
5
- before{ subject.stub!(:dup).and_return(subject) }
5
+ before{ subject.stub(:dup).and_return(subject) }
6
6
 
7
7
  let(:app){ lambda{|env| [200, {}, [@body || { "foo" => "bar" }]]} }
8
8
 
@@ -37,7 +37,7 @@ describe Grape::Middleware::Formatter do
37
37
  end
38
38
 
39
39
  context 'error handling' do
40
- let(:formatter) { stub(:formatter) }
40
+ let(:formatter) { double(:formatter) }
41
41
  before do
42
42
  Grape::Formatter::Base.stub(:formatter_for) { formatter }
43
43
  end
@@ -172,7 +172,7 @@ describe Grape::Middleware::Formatter do
172
172
  end
173
173
 
174
174
  context 'input' do
175
- [ "POST", "PATCH", "PUT" ].each do |method|
175
+ [ "POST", "PATCH", "PUT", "DELETE" ].each do |method|
176
176
  [ "application/json", "application/json; charset=utf-8" ].each do |content_type|
177
177
  context content_type do
178
178
  it 'parses the body from #{method} and copies values into rack.request.form_hash' do
@@ -274,4 +274,29 @@ describe Grape::Middleware::Versioner::Header do
274
274
  subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1+json').first.should == 200
275
275
  end
276
276
  end
277
+
278
+ context 'when multiple versions are specified' do
279
+ before do
280
+ @options[:versions] = ['v1', 'v2']
281
+ end
282
+
283
+ it 'succeeds with v1' do
284
+ subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1+json').first.should == 200
285
+ end
286
+
287
+ it 'succeeds with v2' do
288
+ subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v2+json').first.should == 200
289
+ end
290
+
291
+ it 'fails with another version' do
292
+ expect {
293
+ subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v3+json')
294
+ }.to throw_symbol(
295
+ :error,
296
+ :status => 406,
297
+ :headers => {'X-Cascade' => 'pass'},
298
+ :message => 'API vendor or version not found.'
299
+ )
300
+ end
301
+ end
277
302
  end
@@ -0,0 +1,219 @@
1
+ require 'spec_helper'
2
+
3
+ module Grape
4
+ describe Path do
5
+
6
+ describe "#initialize" do
7
+ it "remembers the path" do
8
+ path = Path.new('/:id', anything, anything)
9
+ expect(path.raw_path).to eql('/:id')
10
+ end
11
+
12
+ it "remembers the namespace" do
13
+ path = Path.new(anything, '/users', anything)
14
+ expect(path.namespace).to eql('/users')
15
+ end
16
+
17
+ it "remebers the settings" do
18
+ path = Path.new(anything, anything, foo: 'bar')
19
+ expect(path.settings).to eql(foo: 'bar')
20
+ end
21
+ end
22
+
23
+ describe "#mount_path" do
24
+ it "is nil when no mount path setting exists" do
25
+ path = Path.new(anything, anything, {})
26
+ expect(path.mount_path).to be_nil
27
+ end
28
+
29
+ it "is nil when the mount path is nil" do
30
+ path = Path.new(anything, anything, mount_path: nil)
31
+ expect(path.mount_path).to be_nil
32
+ end
33
+
34
+ it "splits the mount path" do
35
+ path = Path.new(anything, anything, mount_path: 'foo/bar')
36
+ expect(path.mount_path).to eql(['foo', 'bar'])
37
+ end
38
+ end
39
+
40
+ describe "#root_prefix" do
41
+ it "is nil when no root prefix setting exists" do
42
+ path = Path.new(anything, anything, {})
43
+ expect(path.root_prefix).to be_nil
44
+ end
45
+
46
+ it "is nil when the mount path is nil" do
47
+ path = Path.new(anything, anything, root_prefix: nil)
48
+ expect(path.root_prefix).to be_nil
49
+ end
50
+
51
+ it "splits the mount path" do
52
+ path = Path.new(anything, anything, root_prefix: 'hello/world')
53
+ expect(path.root_prefix).to eql(['hello', 'world'])
54
+ end
55
+ end
56
+
57
+ describe "#uses_path_versioning?" do
58
+ it "is false when the version setting is nil" do
59
+ path = Path.new(anything, anything, version: nil)
60
+ expect(path.uses_path_versioning?).to be_false
61
+ end
62
+
63
+ it "is false when the version option is header" do
64
+ path = Path.new(anything, anything, {
65
+ version: 'v1',
66
+ version_options: { using: :header }
67
+ })
68
+
69
+ expect(path.uses_path_versioning?).to be_false
70
+ end
71
+
72
+ it "is true when the version option is path" do
73
+ path = Path.new(anything, anything, {
74
+ version: 'v1',
75
+ version_options: { using: :path }
76
+ })
77
+
78
+ expect(path.uses_path_versioning?).to be_true
79
+ end
80
+ end
81
+
82
+ describe "#has_namespace?" do
83
+ it "is false when the namespace is nil" do
84
+ path = Path.new(anything, nil, anything)
85
+ expect(path).not_to have_namespace
86
+ end
87
+
88
+ it "is false when the namespace starts with whitespace" do
89
+ path = Path.new(anything, ' /foo', anything)
90
+ expect(path).not_to have_namespace
91
+ end
92
+
93
+ it "is false when the namespace is the root path" do
94
+ path = Path.new(anything, '/', anything)
95
+ expect(path).not_to have_namespace
96
+ end
97
+
98
+ it "is true otherwise" do
99
+ path = Path.new(anything, '/world', anything)
100
+ expect(path).to have_namespace
101
+ end
102
+ end
103
+
104
+ describe "#has_path?" do
105
+ it "is false when the path is nil" do
106
+ path = Path.new(nil, anything, anything)
107
+ expect(path).not_to have_path
108
+ end
109
+
110
+ it "is false when the path starts with whitespace" do
111
+ path = Path.new(' /foo', anything, anything)
112
+ expect(path).not_to have_path
113
+ end
114
+
115
+ it "is false when the path is the root path" do
116
+ path = Path.new('/', anything, anything)
117
+ expect(path).not_to have_path
118
+ end
119
+
120
+ it "is true otherwise" do
121
+ path = Path.new('/hello', anything, anything)
122
+ expect(path).to have_path
123
+ end
124
+ end
125
+
126
+ describe "#path" do
127
+ context "mount_path" do
128
+ it "is not included when it is nil" do
129
+ path = Path.new(nil, nil, { mount_path: '/foo/bar' })
130
+ expect(path.path).to eql '/foo/bar'
131
+ end
132
+
133
+ it "is included when it is not nil" do
134
+ path = Path.new(nil, nil, {})
135
+ expect(path.path).to eql('/')
136
+ end
137
+ end
138
+
139
+ context "root_prefix" do
140
+ it "is not included when it is nil" do
141
+ path = Path.new(nil, nil, {})
142
+ expect(path.path).to eql('/')
143
+ end
144
+
145
+ it "is included after the mount path" do
146
+ path = Path.new(nil, nil, {
147
+ mount_path: '/foo',
148
+ root_prefix: '/hello'
149
+ })
150
+
151
+ expect(path.path).to eql('/foo/hello')
152
+ end
153
+ end
154
+
155
+ it "uses the namespace after the mount path and root prefix" do
156
+ path = Path.new(nil, 'namespace', {
157
+ mount_path: '/foo',
158
+ root_prefix: '/hello'
159
+ })
160
+
161
+ expect(path.path).to eql('/foo/hello/namespace')
162
+ end
163
+
164
+ it "uses the raw path after the namespace" do
165
+ path = Path.new('raw_path', 'namespace', {
166
+ mount_path: '/foo',
167
+ root_prefix: '/hello'
168
+ })
169
+
170
+ expect(path.path).to eql('/foo/hello/namespace/raw_path')
171
+ end
172
+ end
173
+
174
+ describe "#suffix" do
175
+ context "when path versioning is used" do
176
+ it "includes a '/'" do
177
+ path = Path.new(nil, nil, {})
178
+ path.stub(:uses_path_versioning?) { true }
179
+
180
+ expect(path.suffix).to eql('(/.:format)')
181
+ end
182
+ end
183
+
184
+ context "when path versioning is not used" do
185
+ it "does not include a '/' when the path has a namespace" do
186
+ path = Path.new(nil, 'namespace', {})
187
+ path.stub(:uses_path_versioning?) { true }
188
+
189
+ expect(path.suffix).to eql('(.:format)')
190
+ end
191
+
192
+ it "does not include a '/' when the path has a path" do
193
+ path = Path.new('/path', nil, {})
194
+ path.stub(:uses_path_versioning?) { true }
195
+
196
+ expect(path.suffix).to eql('(.:format)')
197
+ end
198
+
199
+ it "includes a '/' otherwise" do
200
+ path = Path.new(nil, nil, {})
201
+ path.stub(:uses_path_versioning?) { true }
202
+
203
+ expect(path.suffix).to eql('(/.:format)')
204
+ end
205
+ end
206
+ end
207
+
208
+ describe "#path_with_suffix" do
209
+ it "combines the path and suffix" do
210
+ path = Path.new(nil, nil, {})
211
+ path.stub(:path) { '/the/path' }
212
+ path.stub(:suffix) { 'suffix' }
213
+
214
+ expect(path.path_with_suffix).to eql('/the/pathsuffix')
215
+ end
216
+ end
217
+
218
+ end
219
+ end
@@ -6,26 +6,44 @@ describe Grape::Validations::CoerceValidator do
6
6
  def app; subject end
7
7
 
8
8
  describe 'coerce' do
9
- it "i18n error on malformed input" do
10
- I18n.load_path << File.expand_path('../zh-CN.yml',__FILE__)
11
- I18n.reload!
12
- I18n.locale = :'zh-CN'
13
- subject.params { requires :age, :type => Integer }
14
- subject.get '/single' do 'int works'; end
15
9
 
16
- get '/single', :age => '43a'
17
- last_response.status.should == 400
18
- last_response.body.should == '年龄格式不正确'
19
- I18n.locale = :en
10
+ context "i18n" do
11
+
12
+ after :each do
13
+ I18n.locale = :en
14
+ end
15
+
16
+ it "i18n error on malformed input" do
17
+ I18n.load_path << File.expand_path('../zh-CN.yml',__FILE__)
18
+ I18n.reload!
19
+ I18n.locale = :'zh-CN'
20
+ subject.params { requires :age, :type => Integer }
21
+ subject.get '/single' do 'int works'; end
22
+
23
+ get '/single', :age => '43a'
24
+ last_response.status.should == 400
25
+ last_response.body.should == '年龄格式不正确'
26
+ end
27
+
28
+ it 'gives an english fallback error when default locale message is blank' do
29
+ I18n.locale = :'pt-BR'
30
+ subject.params { requires :age, :type => Integer }
31
+ subject.get '/single' do 'int works'; end
32
+
33
+ get '/single', :age => '43a'
34
+ last_response.status.should == 400
35
+ last_response.body.should == 'age is invalid'
36
+ end
20
37
 
21
38
  end
39
+
22
40
  it 'error on malformed input' do
23
41
  subject.params { requires :int, :type => Integer }
24
42
  subject.get '/single' do 'int works'; end
25
43
 
26
44
  get '/single', :int => '43a'
27
45
  last_response.status.should == 400
28
- last_response.body.should == 'invalid parameter: int'
46
+ last_response.body.should == 'int is invalid'
29
47
 
30
48
  get '/single', :int => '43'
31
49
  last_response.status.should == 200
@@ -38,7 +56,7 @@ describe Grape::Validations::CoerceValidator do
38
56
 
39
57
  get 'array', { :ids => ['1', '2', 'az'] }
40
58
  last_response.status.should == 400
41
- last_response.body.should == 'invalid parameter: ids'
59
+ last_response.body.should == 'ids is invalid'
42
60
 
43
61
  get 'array', { :ids => ['1', '2', '890'] }
44
62
  last_response.status.should == 200
@@ -60,7 +78,7 @@ describe Grape::Validations::CoerceValidator do
60
78
 
61
79
  get '/user', :user => "32"
62
80
  last_response.status.should == 400
63
- last_response.body.should == 'invalid parameter: user'
81
+ last_response.body.should == 'user is invalid'
64
82
 
65
83
  get '/user', :user => { :id => 32, :name => 'Bob' }
66
84
  last_response.status.should == 200
@@ -66,13 +66,13 @@ describe Grape::Validations::PresenceValidator do
66
66
  it 'validates id' do
67
67
  post '/'
68
68
  last_response.status.should == 400
69
- last_response.body.should == '{"error":"missing parameter: id"}'
69
+ last_response.body.should == '{"error":"id is missing"}'
70
70
 
71
71
  io = StringIO.new('{"id" : "a56b"}')
72
72
  post '/', {}, 'rack.input' => io,
73
73
  'CONTENT_TYPE' => 'application/json',
74
74
  'CONTENT_LENGTH' => io.length
75
- last_response.body.should == '{"error":"invalid parameter: id"}'
75
+ last_response.body.should == '{"error":"id is invalid"}'
76
76
  last_response.status.should == 400
77
77
 
78
78
  io = StringIO.new('{"id" : 56}')
@@ -86,11 +86,11 @@ describe Grape::Validations::PresenceValidator do
86
86
  it 'validates name, company' do
87
87
  get('/')
88
88
  last_response.status.should == 400
89
- last_response.body.should == '{"error":"missing parameter: name"}'
89
+ last_response.body.should == '{"error":"name is missing"}'
90
90
 
91
91
  get('/', :name => "Bob")
92
92
  last_response.status.should == 400
93
- last_response.body.should == '{"error":"missing parameter: company"}'
93
+ last_response.body.should == '{"error":"company is missing"}'
94
94
 
95
95
  get('/', :name => "Bob", :company => "TestCorp")
96
96
  last_response.status.should == 200
@@ -100,11 +100,11 @@ describe Grape::Validations::PresenceValidator do
100
100
  it 'validates nested parameters' do
101
101
  get('/nested')
102
102
  last_response.status.should == 400
103
- last_response.body.should == '{"error":"missing parameter: user[first_name]"}'
103
+ last_response.body.should == '{"error":"user[first_name] is missing"}'
104
104
 
105
105
  get('/nested', :user => {:first_name => "Billy"})
106
106
  last_response.status.should == 400
107
- last_response.body.should == '{"error":"missing parameter: user[last_name]"}'
107
+ last_response.body.should == '{"error":"user[last_name] is missing"}'
108
108
 
109
109
  get('/nested', :user => {:first_name => "Billy", :last_name => "Bob"})
110
110
  last_response.status.should == 200
@@ -114,27 +114,27 @@ describe Grape::Validations::PresenceValidator do
114
114
  it 'validates triple nested parameters' do
115
115
  get('/nested_triple')
116
116
  last_response.status.should == 400
117
- last_response.body.should == '{"error":"missing parameter: admin[admin_name]"}'
117
+ last_response.body.should == '{"error":"admin[admin_name] is missing, admin[super][user][first_name] is missing"}'
118
118
 
119
119
  get('/nested_triple', :user => {:first_name => "Billy"})
120
120
  last_response.status.should == 400
121
- last_response.body.should == '{"error":"missing parameter: admin[admin_name]"}'
121
+ last_response.body.should == '{"error":"admin[admin_name] is missing, admin[super][user][first_name] is missing"}'
122
122
 
123
123
  get('/nested_triple', :admin => {:super => {:first_name => "Billy"}})
124
124
  last_response.status.should == 400
125
- last_response.body.should == '{"error":"missing parameter: admin[admin_name]"}'
125
+ last_response.body.should == '{"error":"admin[admin_name] is missing, admin[super][user][first_name] is missing"}'
126
126
 
127
127
  get('/nested_triple', :super => {:user => {:first_name => "Billy", :last_name => "Bob"}})
128
128
  last_response.status.should == 400
129
- last_response.body.should == '{"error":"missing parameter: admin[admin_name]"}'
129
+ last_response.body.should == '{"error":"admin[admin_name] is missing, admin[super][user][first_name] is missing"}'
130
130
 
131
131
  get('/nested_triple', :admin => {:super => {:user => {:first_name => "Billy"}}})
132
132
  last_response.status.should == 400
133
- last_response.body.should == '{"error":"missing parameter: admin[admin_name]"}'
133
+ last_response.body.should == '{"error":"admin[admin_name] is missing, admin[super][user][last_name] is missing"}'
134
134
 
135
135
  get('/nested_triple', :admin => { :admin_name => 'admin', :super => {:user => {:first_name => "Billy"}}})
136
136
  last_response.status.should == 400
137
- last_response.body.should == '{"error":"missing parameter: admin[super][user][last_name]"}'
137
+ last_response.body.should == '{"error":"admin[super][user][last_name] is missing"}'
138
138
 
139
139
  get('/nested_triple', :admin => { :admin_name => 'admin', :super => {:user => {:first_name => "Billy", :last_name => "Bob"}}})
140
140
  last_response.status.should == 200