grape 0.15.0 → 0.16.1

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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -1
  3. data/Gemfile.lock +16 -15
  4. data/README.md +41 -47
  5. data/UPGRADING.md +62 -0
  6. data/gemfiles/rails_3.gemfile.lock +225 -0
  7. data/grape.gemspec +2 -2
  8. data/lib/grape.rb +31 -26
  9. data/lib/grape/api.rb +39 -23
  10. data/lib/grape/dsl/inside_route.rb +8 -4
  11. data/lib/grape/dsl/routing.rb +2 -1
  12. data/lib/grape/endpoint.rb +43 -62
  13. data/lib/grape/error_formatter.rb +4 -2
  14. data/lib/grape/error_formatter/base.rb +10 -6
  15. data/lib/grape/formatter.rb +4 -2
  16. data/lib/grape/http/headers.rb +1 -0
  17. data/lib/grape/middleware/formatter.rb +2 -2
  18. data/lib/grape/middleware/versioner/accept_version_header.rb +2 -2
  19. data/lib/grape/middleware/versioner/header.rb +2 -2
  20. data/lib/grape/middleware/versioner/path.rb +2 -2
  21. data/lib/grape/namespace.rb +1 -1
  22. data/lib/grape/parser.rb +4 -2
  23. data/lib/grape/path.rb +3 -3
  24. data/lib/grape/request.rb +2 -2
  25. data/lib/grape/router.rb +156 -0
  26. data/lib/grape/router/attribute_translator.rb +40 -0
  27. data/lib/grape/router/pattern.rb +55 -0
  28. data/lib/grape/router/route.rb +105 -0
  29. data/lib/grape/serve_file/file_body.rb +34 -0
  30. data/lib/grape/{util → serve_file}/file_response.rb +1 -1
  31. data/lib/grape/{util → serve_file}/sendfile_response.rb +1 -1
  32. data/lib/grape/util/env.rb +1 -1
  33. data/lib/grape/util/registrable.rb +13 -0
  34. data/lib/grape/validations/types/custom_type_coercer.rb +2 -0
  35. data/lib/grape/version.rb +1 -1
  36. data/spec/grape/api/invalid_format_spec.rb +43 -0
  37. data/spec/grape/api/recognize_path_spec.rb +21 -0
  38. data/spec/grape/api/required_parameters_with_invalid_method_spec.rb +26 -0
  39. data/spec/grape/api_spec.rb +110 -38
  40. data/spec/grape/dsl/inside_route_spec.rb +267 -240
  41. data/spec/grape/endpoint_spec.rb +10 -0
  42. data/spec/grape/entity_spec.rb +2 -2
  43. data/spec/grape/middleware/formatter_spec.rb +23 -4
  44. data/spec/grape/middleware/versioner/header_spec.rb +1 -1
  45. data/spec/grape/middleware/versioner/path_spec.rb +1 -1
  46. data/spec/grape/parser_spec.rb +82 -0
  47. data/spec/grape/request_spec.rb +2 -2
  48. data/spec/grape/validations/params_scope_spec.rb +2 -2
  49. data/spec/grape/validations/validators/coerce_spec.rb +51 -0
  50. data/spec/grape/validations_spec.rb +1 -1
  51. data/tmp/Gemfile.lock +63 -0
  52. metadata +70 -55
  53. data/lib/grape/route.rb +0 -32
@@ -15,321 +15,348 @@ module Grape
15
15
  end
16
16
  end
17
17
  end
18
+ end
19
+ end
18
20
 
19
- describe Endpoint do
20
- subject { InsideRouteSpec::Dummy.new }
21
+ describe Grape::Endpoint do
22
+ subject { Grape::DSL::InsideRouteSpec::Dummy.new }
21
23
 
22
- describe '#version' do
23
- it 'defaults to nil' do
24
- expect(subject.version).to be nil
25
- end
24
+ describe '#version' do
25
+ it 'defaults to nil' do
26
+ expect(subject.version).to be nil
27
+ end
26
28
 
27
- it 'returns env[api.version]' do
28
- subject.env['api.version'] = 'dummy'
29
- expect(subject.version).to eq 'dummy'
30
- end
31
- end
29
+ it 'returns env[api.version]' do
30
+ subject.env['api.version'] = 'dummy'
31
+ expect(subject.version).to eq 'dummy'
32
+ end
33
+ end
32
34
 
33
- describe '#error!' do
34
- it 'throws :error' do
35
- expect { subject.error! 'Not Found', 404 }.to throw_symbol(:error)
36
- end
35
+ describe '#error!' do
36
+ it 'throws :error' do
37
+ expect { subject.error! 'Not Found', 404 }.to throw_symbol(:error)
38
+ end
37
39
 
38
- describe 'thrown' do
39
- before do
40
- catch(:error) { subject.error! 'Not Found', 404 }
41
- end
42
- it 'sets status' do
43
- expect(subject.status).to eq 404
44
- end
45
- end
40
+ describe 'thrown' do
41
+ before do
42
+ catch(:error) { subject.error! 'Not Found', 404 }
43
+ end
44
+ it 'sets status' do
45
+ expect(subject.status).to eq 404
46
+ end
47
+ end
46
48
 
47
- describe 'default_error_status' do
48
- before do
49
- subject.namespace_inheritable(:default_error_status, 500)
50
- catch(:error) { subject.error! 'Unknown' }
51
- end
52
- it 'sets status to default_error_status' do
53
- expect(subject.status).to eq 500
54
- end
55
- end
49
+ describe 'default_error_status' do
50
+ before do
51
+ subject.namespace_inheritable(:default_error_status, 500)
52
+ catch(:error) { subject.error! 'Unknown' }
53
+ end
54
+ it 'sets status to default_error_status' do
55
+ expect(subject.status).to eq 500
56
+ end
57
+ end
58
+
59
+ # self.status(status || settings[:default_error_status])
60
+ # throw :error, message: message, status: self.status, headers: headers
61
+ end
56
62
 
57
- # self.status(status || settings[:default_error_status])
58
- # throw :error, message: message, status: self.status, headers: headers
63
+ describe '#redirect' do
64
+ describe 'default' do
65
+ before do
66
+ subject.redirect '/'
59
67
  end
60
68
 
61
- describe '#redirect' do
62
- describe 'default' do
63
- before do
64
- subject.redirect '/'
65
- end
69
+ it 'sets status to 302' do
70
+ expect(subject.status).to eq 302
71
+ end
66
72
 
67
- it 'sets status to 302' do
68
- expect(subject.status).to eq 302
69
- end
73
+ it 'sets location header' do
74
+ expect(subject.header['Location']).to eq '/'
75
+ end
76
+ end
70
77
 
71
- it 'sets location header' do
72
- expect(subject.header['Location']).to eq '/'
73
- end
74
- end
78
+ describe 'permanent' do
79
+ before do
80
+ subject.redirect '/', permanent: true
81
+ end
75
82
 
76
- describe 'permanent' do
77
- before do
78
- subject.redirect '/', permanent: true
79
- end
83
+ it 'sets status to 301' do
84
+ expect(subject.status).to eq 301
85
+ end
80
86
 
81
- it 'sets status to 301' do
82
- expect(subject.status).to eq 301
83
- end
87
+ it 'sets location header' do
88
+ expect(subject.header['Location']).to eq '/'
89
+ end
90
+ end
91
+ end
84
92
 
85
- it 'sets location header' do
86
- expect(subject.header['Location']).to eq '/'
87
- end
88
- end
93
+ describe '#status' do
94
+ %w(GET PUT DELETE OPTIONS).each do |method|
95
+ it 'defaults to 200 on GET' do
96
+ request = Grape::Request.new(Rack::MockRequest.env_for('/', method: method))
97
+ expect(subject).to receive(:request).and_return(request)
98
+ expect(subject.status).to eq 200
89
99
  end
100
+ end
90
101
 
91
- describe '#status' do
92
- %w(GET PUT DELETE OPTIONS).each do |method|
93
- it 'defaults to 200 on GET' do
94
- request = Grape::Request.new(Rack::MockRequest.env_for('/', method: method))
95
- expect(subject).to receive(:request).and_return(request)
96
- expect(subject.status).to eq 200
97
- end
98
- end
102
+ it 'defaults to 201 on POST' do
103
+ request = Grape::Request.new(Rack::MockRequest.env_for('/', method: 'POST'))
104
+ expect(subject).to receive(:request).and_return(request)
105
+ expect(subject.status).to eq 201
106
+ end
99
107
 
100
- it 'defaults to 201 on POST' do
101
- request = Grape::Request.new(Rack::MockRequest.env_for('/', method: 'POST'))
102
- expect(subject).to receive(:request).and_return(request)
103
- expect(subject.status).to eq 201
104
- end
108
+ it 'returns status set' do
109
+ subject.status 501
110
+ expect(subject.status).to eq 501
111
+ end
105
112
 
106
- it 'returns status set' do
107
- subject.status 501
108
- expect(subject.status).to eq 501
109
- end
113
+ it 'accepts symbol for status' do
114
+ subject.status :see_other
115
+ expect(subject.status).to eq 303
116
+ end
110
117
 
111
- it 'accepts symbol for status' do
112
- subject.status :see_other
113
- expect(subject.status).to eq 303
114
- end
118
+ it 'raises error if unknow symbol is passed' do
119
+ expect { subject.status :foo_bar }
120
+ .to raise_error(ArgumentError, 'Status code :foo_bar is invalid.')
121
+ end
115
122
 
116
- it 'raises error if unknow symbol is passed' do
117
- expect { subject.status :foo_bar }
118
- .to raise_error(ArgumentError, 'Status code :foo_bar is invalid.')
119
- end
123
+ it 'accepts unknown Fixnum status codes' do
124
+ expect { subject.status 210 }.to_not raise_error
125
+ end
120
126
 
121
- it 'accepts unknown Fixnum status codes' do
122
- expect { subject.status 210 }.to_not raise_error
123
- end
127
+ it 'raises error if status is not a fixnum or symbol' do
128
+ expect { subject.status Object.new }
129
+ .to raise_error(ArgumentError, 'Status code must be Fixnum or Symbol.')
130
+ end
131
+ end
124
132
 
125
- it 'raises error if status is not a fixnum or symbol' do
126
- expect { subject.status Object.new }
127
- .to raise_error(ArgumentError, 'Status code must be Fixnum or Symbol.')
128
- end
133
+ describe '#content_type' do
134
+ describe 'set' do
135
+ before do
136
+ subject.content_type 'text/plain'
129
137
  end
130
138
 
131
- describe '#content_type' do
132
- describe 'set' do
133
- before do
134
- subject.content_type 'text/plain'
135
- end
139
+ it 'returns value' do
140
+ expect(subject.content_type).to eq 'text/plain'
141
+ end
142
+ end
136
143
 
137
- it 'returns value' do
138
- expect(subject.content_type).to eq 'text/plain'
139
- end
140
- end
144
+ it 'returns default' do
145
+ expect(subject.content_type).to be nil
146
+ end
147
+ end
141
148
 
142
- it 'returns default' do
143
- expect(subject.content_type).to be nil
144
- end
149
+ describe '#cookies' do
150
+ it 'returns an instance of Cookies' do
151
+ expect(subject.cookies).to be_a Grape::Cookies
152
+ end
153
+ end
154
+
155
+ describe '#body' do
156
+ describe 'set' do
157
+ before do
158
+ subject.body 'body'
145
159
  end
146
160
 
147
- describe '#cookies' do
148
- it 'returns an instance of Cookies' do
149
- expect(subject.cookies).to be_a Grape::Cookies
150
- end
161
+ it 'returns value' do
162
+ expect(subject.body).to eq 'body'
151
163
  end
164
+ end
152
165
 
153
- describe '#body' do
154
- describe 'set' do
155
- before do
156
- subject.body 'body'
157
- end
166
+ describe 'false' do
167
+ before do
168
+ subject.body false
169
+ end
158
170
 
159
- it 'returns value' do
160
- expect(subject.body).to eq 'body'
161
- end
162
- end
171
+ it 'sets status to 204' do
172
+ expect(subject.body).to eq ''
173
+ expect(subject.status).to eq 204
174
+ end
175
+ end
163
176
 
164
- describe 'false' do
165
- before do
166
- subject.body false
167
- end
177
+ it 'returns default' do
178
+ expect(subject.body).to be nil
179
+ end
180
+ end
168
181
 
169
- it 'sets status to 204' do
170
- expect(subject.body).to eq ''
171
- expect(subject.status).to eq 204
172
- end
182
+ describe '#file' do
183
+ describe 'set' do
184
+ context 'as file path' do
185
+ let(:file_path) { '/some/file/path' }
186
+
187
+ let(:file_response) do
188
+ file_body = Grape::ServeFile::FileBody.new(file_path)
189
+ Grape::ServeFile::FileResponse.new(file_body)
173
190
  end
174
191
 
175
- it 'returns default' do
176
- expect(subject.body).to be nil
192
+ before do
193
+ subject.file file_path
194
+ end
195
+
196
+ it 'returns value wrapped in FileResponse' do
197
+ expect(subject.file).to eq file_response
177
198
  end
178
199
  end
179
200
 
180
- describe '#file' do
181
- describe 'set' do
182
- before do
183
- subject.file 'file'
184
- end
201
+ context 'as object (backward compatibility)' do
202
+ let(:file_object) { Class.new }
185
203
 
186
- it 'returns value wrapped in FileResponse' do
187
- expect(subject.file).to eq Grape::Util::FileResponse.new('file')
188
- end
204
+ let(:file_response) do
205
+ Grape::ServeFile::FileResponse.new(file_object)
206
+ end
207
+
208
+ before do
209
+ subject.file file_object
189
210
  end
190
211
 
191
- it 'returns default' do
192
- expect(subject.file).to be nil
212
+ it 'returns value wrapped in FileResponse' do
213
+ expect(subject.file).to eq file_response
193
214
  end
194
215
  end
216
+ end
195
217
 
196
- describe '#stream' do
197
- describe 'set' do
198
- before do
199
- subject.header 'Cache-Control', 'cache'
200
- subject.header 'Content-Length', 123
201
- subject.header 'Transfer-Encoding', 'base64'
202
- subject.stream 'file'
203
- end
218
+ it 'returns default' do
219
+ expect(subject.file).to be nil
220
+ end
221
+ end
204
222
 
205
- it 'returns value wrapped in FileResponse' do
206
- expect(subject.stream).to eq Grape::Util::FileResponse.new('file')
207
- end
223
+ describe '#stream' do
224
+ describe 'set' do
225
+ let(:file_object) { Class.new }
208
226
 
209
- it 'also sets result of file to value wrapped in FileResponse' do
210
- expect(subject.file).to eq Grape::Util::FileResponse.new('file')
211
- end
227
+ before do
228
+ subject.header 'Cache-Control', 'cache'
229
+ subject.header 'Content-Length', 123
230
+ subject.header 'Transfer-Encoding', 'base64'
231
+ subject.stream file_object
232
+ end
212
233
 
213
- it 'sets Cache-Control header to no-cache' do
214
- expect(subject.header['Cache-Control']).to eq 'no-cache'
215
- end
234
+ it 'returns value wrapped in FileResponse' do
235
+ expect(subject.stream).to eq Grape::ServeFile::FileResponse.new(file_object)
236
+ end
216
237
 
217
- it 'sets Content-Length header to nil' do
218
- expect(subject.header['Content-Length']).to eq nil
219
- end
238
+ it 'also sets result of file to value wrapped in FileResponse' do
239
+ expect(subject.file).to eq Grape::ServeFile::FileResponse.new(file_object)
240
+ end
220
241
 
221
- it 'sets Transfer-Encoding header to nil' do
222
- expect(subject.header['Transfer-Encoding']).to eq nil
223
- end
224
- end
242
+ it 'sets Cache-Control header to no-cache' do
243
+ expect(subject.header['Cache-Control']).to eq 'no-cache'
244
+ end
225
245
 
226
- it 'returns default' do
227
- expect(subject.file).to be nil
228
- end
246
+ it 'sets Content-Length header to nil' do
247
+ expect(subject.header['Content-Length']).to eq nil
229
248
  end
230
249
 
231
- describe '#route' do
232
- before do
233
- subject.env['rack.routing_args'] = {}
234
- subject.env['rack.routing_args'][:route_info] = 'dummy'
235
- end
250
+ it 'sets Transfer-Encoding header to nil' do
251
+ expect(subject.header['Transfer-Encoding']).to eq nil
252
+ end
253
+ end
236
254
 
237
- it 'returns route_info' do
238
- expect(subject.route).to eq 'dummy'
239
- end
255
+ it 'returns default' do
256
+ expect(subject.file).to be nil
257
+ end
258
+ end
259
+
260
+ describe '#route' do
261
+ before do
262
+ subject.env['grape.routing_args'] = {}
263
+ subject.env['grape.routing_args'][:route_info] = 'dummy'
264
+ end
265
+
266
+ it 'returns route_info' do
267
+ expect(subject.route).to eq 'dummy'
268
+ end
269
+ end
270
+
271
+ describe '#present' do
272
+ # see entity_spec.rb for entity representation spec coverage
273
+
274
+ describe 'dummy' do
275
+ before do
276
+ subject.present 'dummy'
240
277
  end
241
278
 
242
- describe '#present' do
243
- # see entity_spec.rb for entity representation spec coverage
279
+ it 'presents dummy object' do
280
+ expect(subject.body).to eq 'dummy'
281
+ end
282
+ end
283
+
284
+ describe 'with' do
285
+ describe 'entity' do
286
+ let(:entity_mock) do
287
+ entity_mock = Object.new
288
+ allow(entity_mock).to receive(:represent).and_return('dummy')
289
+ entity_mock
290
+ end
244
291
 
245
- describe 'dummy' do
292
+ describe 'instance' do
246
293
  before do
247
- subject.present 'dummy'
294
+ subject.present 'dummy', with: entity_mock
248
295
  end
249
296
 
250
297
  it 'presents dummy object' do
251
298
  expect(subject.body).to eq 'dummy'
252
299
  end
253
300
  end
301
+ end
302
+ end
254
303
 
255
- describe 'with' do
256
- describe 'entity' do
257
- let(:entity_mock) do
258
- entity_mock = Object.new
259
- allow(entity_mock).to receive(:represent).and_return('dummy')
260
- entity_mock
261
- end
262
-
263
- describe 'instance' do
264
- before do
265
- subject.present 'dummy', with: entity_mock
266
- end
267
-
268
- it 'presents dummy object' do
269
- expect(subject.body).to eq 'dummy'
270
- end
271
- end
272
- end
273
- end
274
-
275
- describe 'multiple entities' do
276
- let(:entity_mock1) do
277
- entity_mock1 = Object.new
278
- allow(entity_mock1).to receive(:represent).and_return(dummy1: 'dummy1')
279
- entity_mock1
280
- end
304
+ describe 'multiple entities' do
305
+ let(:entity_mock1) do
306
+ entity_mock1 = Object.new
307
+ allow(entity_mock1).to receive(:represent).and_return(dummy1: 'dummy1')
308
+ entity_mock1
309
+ end
281
310
 
282
- let(:entity_mock2) do
283
- entity_mock2 = Object.new
284
- allow(entity_mock2).to receive(:represent).and_return(dummy2: 'dummy2')
285
- entity_mock2
286
- end
311
+ let(:entity_mock2) do
312
+ entity_mock2 = Object.new
313
+ allow(entity_mock2).to receive(:represent).and_return(dummy2: 'dummy2')
314
+ entity_mock2
315
+ end
287
316
 
288
- describe 'instance' do
289
- before do
290
- subject.present 'dummy1', with: entity_mock1
291
- subject.present 'dummy2', with: entity_mock2
292
- end
317
+ describe 'instance' do
318
+ before do
319
+ subject.present 'dummy1', with: entity_mock1
320
+ subject.present 'dummy2', with: entity_mock2
321
+ end
293
322
 
294
- it 'presents both dummy objects' do
295
- expect(subject.body[:dummy1]).to eq 'dummy1'
296
- expect(subject.body[:dummy2]).to eq 'dummy2'
297
- end
298
- end
323
+ it 'presents both dummy objects' do
324
+ expect(subject.body[:dummy1]).to eq 'dummy1'
325
+ expect(subject.body[:dummy2]).to eq 'dummy2'
299
326
  end
327
+ end
328
+ end
300
329
 
301
- describe 'non mergeable entity' do
302
- let(:entity_mock1) do
303
- entity_mock1 = Object.new
304
- allow(entity_mock1).to receive(:represent).and_return(dummy1: 'dummy1')
305
- entity_mock1
306
- end
330
+ describe 'non mergeable entity' do
331
+ let(:entity_mock1) do
332
+ entity_mock1 = Object.new
333
+ allow(entity_mock1).to receive(:represent).and_return(dummy1: 'dummy1')
334
+ entity_mock1
335
+ end
307
336
 
308
- let(:entity_mock2) do
309
- entity_mock2 = Object.new
310
- allow(entity_mock2).to receive(:represent).and_return('not a hash')
311
- entity_mock2
312
- end
337
+ let(:entity_mock2) do
338
+ entity_mock2 = Object.new
339
+ allow(entity_mock2).to receive(:represent).and_return('not a hash')
340
+ entity_mock2
341
+ end
313
342
 
314
- describe 'instance' do
315
- it 'fails' do
316
- subject.present 'dummy1', with: entity_mock1
317
- expect do
318
- subject.present 'dummy2', with: entity_mock2
319
- end.to raise_error ArgumentError, 'Representation of type String cannot be merged.'
320
- end
321
- end
343
+ describe 'instance' do
344
+ it 'fails' do
345
+ subject.present 'dummy1', with: entity_mock1
346
+ expect do
347
+ subject.present 'dummy2', with: entity_mock2
348
+ end.to raise_error ArgumentError, 'Representation of type String cannot be merged.'
322
349
  end
323
350
  end
351
+ end
352
+ end
324
353
 
325
- describe '#declared' do
326
- # see endpoint_spec.rb#declared for spec coverage
354
+ describe '#declared' do
355
+ # see endpoint_spec.rb#declared for spec coverage
327
356
 
328
- it 'is not available by default' do
329
- expect { subject.declared({}) }.to raise_error(
330
- Grape::DSL::InsideRoute::MethodNotYetAvailable)
331
- end
332
- end
357
+ it 'is not available by default' do
358
+ expect { subject.declared({}) }.to raise_error(
359
+ Grape::DSL::InsideRoute::MethodNotYetAvailable)
333
360
  end
334
361
  end
335
362
  end