grape 0.2.6 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of grape might be problematic. Click here for more details.

Files changed (50) hide show
  1. data/{CHANGELOG.markdown → CHANGELOG.md} +21 -1
  2. data/Gemfile +1 -0
  3. data/{README.markdown → README.md} +178 -125
  4. data/grape.gemspec +1 -1
  5. data/lib/grape.rb +25 -3
  6. data/lib/grape/api.rb +43 -20
  7. data/lib/grape/endpoint.rb +32 -13
  8. data/lib/grape/exceptions/base.rb +50 -1
  9. data/lib/grape/exceptions/invalid_formatter.rb +13 -0
  10. data/lib/grape/exceptions/invalid_versioner_option.rb +14 -0
  11. data/lib/grape/exceptions/invalid_with_option_for_represent.rb +15 -0
  12. data/lib/grape/exceptions/missing_mime_type.rb +14 -0
  13. data/lib/grape/exceptions/missing_option.rb +13 -0
  14. data/lib/grape/exceptions/missing_vendor_option.rb +13 -0
  15. data/lib/grape/exceptions/unknown_options.rb +14 -0
  16. data/lib/grape/exceptions/unknown_validator.rb +12 -0
  17. data/lib/grape/exceptions/{validation_error.rb → validation.rb} +3 -1
  18. data/lib/grape/formatter/xml.rb +2 -1
  19. data/lib/grape/locale/en.yml +20 -0
  20. data/lib/grape/middleware/base.rb +0 -5
  21. data/lib/grape/middleware/error.rb +1 -2
  22. data/lib/grape/middleware/formatter.rb +9 -5
  23. data/lib/grape/middleware/versioner.rb +1 -1
  24. data/lib/grape/middleware/versioner/header.rb +16 -6
  25. data/lib/grape/middleware/versioner/param.rb +1 -1
  26. data/lib/grape/middleware/versioner/path.rb +1 -1
  27. data/lib/grape/util/content_types.rb +0 -2
  28. data/lib/grape/validations.rb +7 -14
  29. data/lib/grape/validations/coerce.rb +2 -1
  30. data/lib/grape/validations/presence.rb +2 -1
  31. data/lib/grape/validations/regexp.rb +2 -1
  32. data/lib/grape/version.rb +1 -1
  33. data/spec/grape/api_spec.rb +150 -5
  34. data/spec/grape/endpoint_spec.rb +51 -157
  35. data/spec/grape/entity_spec.rb +142 -520
  36. data/spec/grape/exceptions/invalid_formatter_spec.rb +18 -0
  37. data/spec/grape/exceptions/invalid_versioner_option_spec.rb +18 -0
  38. data/spec/grape/exceptions/missing_mime_type_spec.rb +24 -0
  39. data/spec/grape/exceptions/missing_option_spec.rb +18 -0
  40. data/spec/grape/exceptions/unknown_options_spec.rb +18 -0
  41. data/spec/grape/exceptions/unknown_validator_spec.rb +18 -0
  42. data/spec/grape/middleware/formatter_spec.rb +40 -34
  43. data/spec/grape/middleware/versioner/header_spec.rb +78 -20
  44. data/spec/grape/middleware/versioner/path_spec.rb +12 -8
  45. data/spec/grape/validations/coerce_spec.rb +1 -0
  46. data/spec/grape/validations/presence_spec.rb +8 -8
  47. data/spec/grape/validations_spec.rb +26 -3
  48. data/spec/spec_helper.rb +3 -6
  49. metadata +44 -9
  50. data/lib/grape/entity.rb +0 -386
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe Grape::Exceptions::InvalidFormatter do
5
+ describe "#message" do
6
+ let(:error) do
7
+ described_class.new(String, 'xml')
8
+ end
9
+
10
+ it "contains the problem in the message" do
11
+ error.message.should include(
12
+ "cannot convert String to xml"
13
+ )
14
+ end
15
+
16
+ end
17
+
18
+ end
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe Grape::Exceptions::InvalidVersionerOption do
5
+ describe "#message" do
6
+ let(:error) do
7
+ described_class.new("headers")
8
+ end
9
+
10
+ it "contains the problem in the message" do
11
+ error.message.should include(
12
+ "Unknown :using for versioner: headers"
13
+ )
14
+ end
15
+
16
+ end
17
+
18
+ end
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+ describe Grape::Exceptions::MissingMimeType do
4
+ describe "#message" do
5
+
6
+ let(:error) do
7
+ described_class.new("new_json")
8
+ end
9
+
10
+ it "contains the problem in the message" do
11
+ error.message.should include(
12
+ "missing mime type for new_json"
13
+ )
14
+ end
15
+
16
+ it "contains the resolution in the message" do
17
+ error.message.should include(
18
+ "or add your own with content_type :new_json, 'application/new_json' "
19
+ )
20
+ end
21
+ end
22
+
23
+
24
+ end
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe Grape::Exceptions::MissingOption do
5
+ describe "#message" do
6
+ let(:error) do
7
+ described_class.new(:path)
8
+ end
9
+
10
+ it "contains the problem in the message" do
11
+ error.message.should include(
12
+ "You must specify :path options."
13
+ )
14
+ end
15
+
16
+ end
17
+
18
+ end
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe Grape::Exceptions::UnknownOptions do
5
+ describe "#message" do
6
+ let(:error) do
7
+ described_class.new([:a, :b])
8
+ end
9
+
10
+ it "contains the problem in the message" do
11
+ error.message.should include(
12
+ "unknown options: "
13
+ )
14
+ end
15
+
16
+ end
17
+
18
+ end
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe Grape::Exceptions::UnknownValidator do
5
+ describe "#message" do
6
+ let(:error) do
7
+ described_class.new('gt_10')
8
+ end
9
+
10
+ it "contains the problem in the message" do
11
+ error.message.should include(
12
+ "unknown validator: gt_10"
13
+ )
14
+ end
15
+
16
+ end
17
+
18
+ end
@@ -4,7 +4,7 @@ describe Grape::Middleware::Formatter do
4
4
  subject{ Grape::Middleware::Formatter.new(app) }
5
5
  before{ subject.stub!(:dup).and_return(subject) }
6
6
 
7
- let(:app){ lambda{|env| [200, {}, [@body]]} }
7
+ let(:app){ lambda{|env| [200, {}, [@body || { "foo" => "bar" }]]} }
8
8
 
9
9
  context 'serialization' do
10
10
  it 'looks at the bodies for possibly serializable data' do
@@ -37,6 +37,7 @@ describe Grape::Middleware::Formatter do
37
37
  end
38
38
 
39
39
  context 'detection' do
40
+
40
41
  it 'uses the extension if one is provided' do
41
42
  subject.call({'PATH_INFO' => '/info.xml'})
42
43
  subject.env['api.format'].should == :xml
@@ -45,9 +46,9 @@ describe Grape::Middleware::Formatter do
45
46
  end
46
47
 
47
48
  it 'uses the format parameter if one is provided' do
48
- subject.call({'PATH_INFO' => '/somewhere','QUERY_STRING' => 'format=json'})
49
+ subject.call({'PATH_INFO' => '/info','QUERY_STRING' => 'format=json'})
49
50
  subject.env['api.format'].should == :json
50
- subject.call({'PATH_INFO' => '/somewhere','QUERY_STRING' => 'format=xml'})
51
+ subject.call({'PATH_INFO' => '/info','QUERY_STRING' => 'format=xml'})
51
52
  subject.env['api.format'].should == :xml
52
53
  end
53
54
 
@@ -148,43 +149,48 @@ describe Grape::Middleware::Formatter do
148
149
  end
149
150
 
150
151
  context 'input' do
151
- [ "application/json", "application/json; charset=utf-8" ].each do |content_type|
152
- it 'parses the body from a POST/PUT and put the contents into rack.request.form_hash for #{content_type}' do
153
- io = StringIO.new('{"is_boolean":true,"string":"thing"}')
154
- subject.call({
155
- 'PATH_INFO' => '/info',
156
- 'REQUEST_METHOD' => 'POST',
157
- 'CONTENT_TYPE' => content_type,
158
- 'rack.input' => io,
159
- 'CONTENT_LENGTH' => io.length
160
- })
161
- subject.env['rack.request.form_hash']['is_boolean'].should be_true
162
- subject.env['rack.request.form_hash']['string'].should == 'thing'
152
+ [ "POST", "PATCH", "PUT" ].each do |method|
153
+ [ "application/json", "application/json; charset=utf-8" ].each do |content_type|
154
+ context content_type do
155
+ it 'parses the body from #{method} and copies values into rack.request.form_hash' do
156
+ io = StringIO.new('{"is_boolean":true,"string":"thing"}')
157
+ subject.call({
158
+ 'PATH_INFO' => '/info',
159
+ 'REQUEST_METHOD' => method,
160
+ 'CONTENT_TYPE' => content_type,
161
+ 'rack.input' => io,
162
+ 'CONTENT_LENGTH' => io.length
163
+ })
164
+ subject.env['rack.request.form_hash']['is_boolean'].should be_true
165
+ subject.env['rack.request.form_hash']['string'].should == 'thing'
166
+ end
167
+ end
163
168
  end
164
- end
165
- it 'parses the body from an xml POST/PUT and put the contents into rack.request.from_hash' do
166
- io = StringIO.new('<thing><name>Test</name></thing>')
167
- subject.call({
168
- 'PATH_INFO' => '/info.xml',
169
- 'REQUEST_METHOD' => 'POST',
170
- 'CONTENT_TYPE' => 'application/xml',
171
- 'rack.input' => io,
172
- 'CONTENT_LENGTH' => io.length
173
- })
174
- subject.env['rack.request.form_hash']['thing']['name'].should == 'Test'
175
- end
176
- [ Rack::Request::FORM_DATA_MEDIA_TYPES, Rack::Request::PARSEABLE_DATA_MEDIA_TYPES ].flatten.each do |content_type|
177
- it "ignores #{content_type}" do
178
- io = StringIO.new('name=Other+Test+Thing')
169
+ it 'parses the body from an xml #{method} and copies values into rack.request.from_hash' do
170
+ io = StringIO.new('<thing><name>Test</name></thing>')
179
171
  subject.call({
180
- 'PATH_INFO' => '/info',
181
- 'REQUEST_METHOD' => 'POST',
182
- 'CONTENT_TYPE' => content_type,
172
+ 'PATH_INFO' => '/info.xml',
173
+ 'REQUEST_METHOD' => method,
174
+ 'CONTENT_TYPE' => 'application/xml',
183
175
  'rack.input' => io,
184
176
  'CONTENT_LENGTH' => io.length
185
177
  })
186
- subject.env['rack.request.form_hash'].should be_nil
178
+ subject.env['rack.request.form_hash']['thing']['name'].should == 'Test'
179
+ end
180
+ [ Rack::Request::FORM_DATA_MEDIA_TYPES, Rack::Request::PARSEABLE_DATA_MEDIA_TYPES ].flatten.each do |content_type|
181
+ it "ignores #{content_type}" do
182
+ io = StringIO.new('name=Other+Test+Thing')
183
+ subject.call({
184
+ 'PATH_INFO' => '/info',
185
+ 'REQUEST_METHOD' => method,
186
+ 'CONTENT_TYPE' => content_type,
187
+ 'rack.input' => io,
188
+ 'CONTENT_LENGTH' => io.length
189
+ })
190
+ subject.env['rack.request.form_hash'].should be_nil
191
+ end
187
192
  end
188
193
  end
189
194
  end
195
+
190
196
  end
@@ -49,21 +49,23 @@ describe Grape::Middleware::Versioner::Header do
49
49
  status.should == 200
50
50
  end
51
51
 
52
- context 'when version is set' do
53
- before do
54
- @options[:versions] = ['v1']
55
- end
52
+ [ 'v1', :v1 ].each do |version|
53
+ context 'when version is set to #{version{' do
54
+ before do
55
+ @options[:versions] = [ version ]
56
+ end
56
57
 
57
- it 'is set' do
58
- status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1+json')
59
- env['api.format'].should eql 'json'
60
- status.should == 200
61
- end
58
+ it 'is set' do
59
+ status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1+json')
60
+ env['api.format'].should eql 'json'
61
+ status.should == 200
62
+ end
62
63
 
63
- it 'is nil if not provided' do
64
- status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1')
65
- env['api.format'].should eql nil
66
- status.should == 200
64
+ it 'is nil if not provided' do
65
+ status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1')
66
+ env['api.format'].should eql nil
67
+ status.should == 200
68
+ end
67
69
  end
68
70
  end
69
71
  end
@@ -88,7 +90,7 @@ describe Grape::Middleware::Versioner::Header do
88
90
  :error,
89
91
  :status => 406,
90
92
  :headers => {'X-Cascade' => 'pass'},
91
- :message => 'API vendor or version not found'
93
+ :message => 'API vendor or version not found.'
92
94
  )
93
95
  end
94
96
 
@@ -116,7 +118,7 @@ describe Grape::Middleware::Versioner::Header do
116
118
  :error,
117
119
  :status => 406,
118
120
  :headers => {'X-Cascade' => 'pass'},
119
- :message => 'API vendor or version not found'
121
+ :message => 'API vendor or version not found.'
120
122
  )
121
123
  end
122
124
  end
@@ -146,7 +148,7 @@ describe Grape::Middleware::Versioner::Header do
146
148
  :error,
147
149
  :status => 406,
148
150
  :headers => {'X-Cascade' => 'pass'},
149
- :message => 'API vendor or version not found'
151
+ :message => 'API vendor or version not found.'
150
152
  )
151
153
  end
152
154
  end
@@ -175,7 +177,7 @@ describe Grape::Middleware::Versioner::Header do
175
177
  :error,
176
178
  :status => 406,
177
179
  :headers => {'X-Cascade' => 'pass'},
178
- :message => 'Accept header must be set'
180
+ :message => 'Accept header must be set.'
179
181
  )
180
182
  end
181
183
 
@@ -186,7 +188,7 @@ describe Grape::Middleware::Versioner::Header do
186
188
  :error,
187
189
  :status => 406,
188
190
  :headers => {'X-Cascade' => 'pass'},
189
- :message => 'Accept header must be set'
191
+ :message => 'Accept header must be set.'
190
192
  )
191
193
  end
192
194
 
@@ -197,7 +199,7 @@ describe Grape::Middleware::Versioner::Header do
197
199
  :error,
198
200
  :status => 406,
199
201
  :headers => {'X-Cascade' => 'pass'},
200
- :message => 'Accept header must not contain ranges ("*")'
202
+ :message => 'Accept header must not contain ranges ("*").'
201
203
  )
202
204
  end
203
205
 
@@ -208,7 +210,63 @@ describe Grape::Middleware::Versioner::Header do
208
210
  :error,
209
211
  :status => 406,
210
212
  :headers => {'X-Cascade' => 'pass'},
211
- :message => 'Accept header must not contain ranges ("*")'
213
+ :message => 'Accept header must not contain ranges ("*").'
214
+ )
215
+ end
216
+
217
+ it 'succeeds if proper header is set' do
218
+ subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1+json').first.should == 200
219
+ end
220
+ end
221
+
222
+ context 'when :strict and :cascade=>false are set' do
223
+ before do
224
+ @options[:versions] = ['v1']
225
+ @options[:version_options][:strict] = true
226
+ @options[:version_options][:cascade] = false
227
+ end
228
+
229
+ it 'fails with 406 Not Acceptable if header is not set' do
230
+ expect {
231
+ env = subject.call({}).last
232
+ }.to throw_symbol(
233
+ :error,
234
+ :status => 406,
235
+ :headers => {},
236
+ :message => 'Accept header must be set.'
237
+ )
238
+ end
239
+
240
+ it 'fails with 406 Not Acceptable if header is empty' do
241
+ expect {
242
+ env = subject.call('HTTP_ACCEPT' => '').last
243
+ }.to throw_symbol(
244
+ :error,
245
+ :status => 406,
246
+ :headers => {},
247
+ :message => 'Accept header must be set.'
248
+ )
249
+ end
250
+
251
+ it 'fails with 406 Not Acceptable if type is a range' do
252
+ expect {
253
+ env = subject.call('HTTP_ACCEPT' => '*/*').last
254
+ }.to throw_symbol(
255
+ :error,
256
+ :status => 406,
257
+ :headers => {},
258
+ :message => 'Accept header must not contain ranges ("*").'
259
+ )
260
+ end
261
+
262
+ it 'fails with 406 Not Acceptable if subtype is a range' do
263
+ expect {
264
+ env = subject.call('HTTP_ACCEPT' => 'application/*').last
265
+ }.to throw_symbol(
266
+ :error,
267
+ :status => 406,
268
+ :headers => {},
269
+ :message => 'Accept header must not contain ranges ("*").'
212
270
  )
213
271
  end
214
272
 
@@ -17,7 +17,7 @@ describe Grape::Middleware::Versioner::Path do
17
17
  end
18
18
 
19
19
  context 'with a pattern' do
20
- before{ @options = {:pattern => /v./i} }
20
+ before { @options = {:pattern => /v./i} }
21
21
  it 'sets the version if it matches' do
22
22
  subject.call('PATH_INFO' => '/v1/awesome').last.should == 'v1'
23
23
  end
@@ -27,14 +27,18 @@ describe Grape::Middleware::Versioner::Path do
27
27
  end
28
28
  end
29
29
 
30
- context 'with specified versions' do
31
- before{ @options = {:versions => ['v1', 'v2']}}
32
- it 'throws an error if a non-allowed version is specified' do
33
- catch(:error){subject.call('PATH_INFO' => '/v3/awesome')}[:status].should == 404
34
- end
30
+ [ [ 'v1', 'v2'], [ :v1, :v2 ], [ :v1, 'v2' ], [ 'v1', :v2 ] ].each do |versions|
31
+ context 'with specified versions as #{versions}' do
32
+ before { @options = { :versions => versions } }
33
+
34
+ it 'throws an error if a non-allowed version is specified' do
35
+ catch(:error){subject.call('PATH_INFO' => '/v3/awesome')}[:status].should == 404
36
+ end
35
37
 
36
- it 'allows versions that have been specified' do
37
- subject.call('PATH_INFO' => '/v1/asoasd').last.should == 'v1'
38
+ it 'allows versions that have been specified' do
39
+ subject.call('PATH_INFO' => '/v1/asoasd').last.should == 'v1'
40
+ end
38
41
  end
39
42
  end
43
+
40
44
  end
@@ -8,6 +8,7 @@ describe Grape::Validations::CoerceValidator do
8
8
  describe 'coerce' do
9
9
  it "i18n error on malformed input" do
10
10
  I18n.load_path << File.expand_path('../zh-CN.yml',__FILE__)
11
+ I18n.reload!
11
12
  I18n.locale = :'zh-CN'
12
13
  subject.params { requires :age, :type => Integer }
13
14
  subject.get '/single' do 'int works'; end
@@ -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: first_name"}'
103
+ last_response.body.should == '{"error":"missing parameter: user[first_name]"}'
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: last_name"}'
107
+ last_response.body.should == '{"error":"missing parameter: user[last_name]"}'
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_name"}'
117
+ last_response.body.should == '{"error":"missing parameter: admin[admin_name]"}'
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_name"}'
121
+ last_response.body.should == '{"error":"missing parameter: admin[admin_name]"}'
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_name"}'
125
+ last_response.body.should == '{"error":"missing parameter: admin[admin_name]"}'
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_name"}'
129
+ last_response.body.should == '{"error":"missing parameter: admin[admin_name]"}'
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_name"}'
133
+ last_response.body.should == '{"error":"missing parameter: admin[admin_name]"}'
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: last_name"}'
137
+ last_response.body.should == '{"error":"missing parameter: admin[super][user][last_name]"}'
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