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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -1
- data/Gemfile.lock +16 -15
- data/README.md +41 -47
- data/UPGRADING.md +62 -0
- data/gemfiles/rails_3.gemfile.lock +225 -0
- data/grape.gemspec +2 -2
- data/lib/grape.rb +31 -26
- data/lib/grape/api.rb +39 -23
- data/lib/grape/dsl/inside_route.rb +8 -4
- data/lib/grape/dsl/routing.rb +2 -1
- data/lib/grape/endpoint.rb +43 -62
- data/lib/grape/error_formatter.rb +4 -2
- data/lib/grape/error_formatter/base.rb +10 -6
- data/lib/grape/formatter.rb +4 -2
- data/lib/grape/http/headers.rb +1 -0
- data/lib/grape/middleware/formatter.rb +2 -2
- data/lib/grape/middleware/versioner/accept_version_header.rb +2 -2
- data/lib/grape/middleware/versioner/header.rb +2 -2
- data/lib/grape/middleware/versioner/path.rb +2 -2
- data/lib/grape/namespace.rb +1 -1
- data/lib/grape/parser.rb +4 -2
- data/lib/grape/path.rb +3 -3
- data/lib/grape/request.rb +2 -2
- data/lib/grape/router.rb +156 -0
- data/lib/grape/router/attribute_translator.rb +40 -0
- data/lib/grape/router/pattern.rb +55 -0
- data/lib/grape/router/route.rb +105 -0
- data/lib/grape/serve_file/file_body.rb +34 -0
- data/lib/grape/{util → serve_file}/file_response.rb +1 -1
- data/lib/grape/{util → serve_file}/sendfile_response.rb +1 -1
- data/lib/grape/util/env.rb +1 -1
- data/lib/grape/util/registrable.rb +13 -0
- data/lib/grape/validations/types/custom_type_coercer.rb +2 -0
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api/invalid_format_spec.rb +43 -0
- data/spec/grape/api/recognize_path_spec.rb +21 -0
- data/spec/grape/api/required_parameters_with_invalid_method_spec.rb +26 -0
- data/spec/grape/api_spec.rb +110 -38
- data/spec/grape/dsl/inside_route_spec.rb +267 -240
- data/spec/grape/endpoint_spec.rb +10 -0
- data/spec/grape/entity_spec.rb +2 -2
- data/spec/grape/middleware/formatter_spec.rb +23 -4
- data/spec/grape/middleware/versioner/header_spec.rb +1 -1
- data/spec/grape/middleware/versioner/path_spec.rb +1 -1
- data/spec/grape/parser_spec.rb +82 -0
- data/spec/grape/request_spec.rb +2 -2
- data/spec/grape/validations/params_scope_spec.rb +2 -2
- data/spec/grape/validations/validators/coerce_spec.rb +51 -0
- data/spec/grape/validations_spec.rb +1 -1
- data/tmp/Gemfile.lock +63 -0
- metadata +70 -55
- 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
|
-
|
20
|
-
|
21
|
+
describe Grape::Endpoint do
|
22
|
+
subject { Grape::DSL::InsideRouteSpec::Dummy.new }
|
21
23
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
24
|
+
describe '#version' do
|
25
|
+
it 'defaults to nil' do
|
26
|
+
expect(subject.version).to be nil
|
27
|
+
end
|
26
28
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
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
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
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
|
-
|
58
|
-
|
63
|
+
describe '#redirect' do
|
64
|
+
describe 'default' do
|
65
|
+
before do
|
66
|
+
subject.redirect '/'
|
59
67
|
end
|
60
68
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
subject.redirect '/'
|
65
|
-
end
|
69
|
+
it 'sets status to 302' do
|
70
|
+
expect(subject.status).to eq 302
|
71
|
+
end
|
66
72
|
|
67
|
-
|
68
|
-
|
69
|
-
|
73
|
+
it 'sets location header' do
|
74
|
+
expect(subject.header['Location']).to eq '/'
|
75
|
+
end
|
76
|
+
end
|
70
77
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
78
|
+
describe 'permanent' do
|
79
|
+
before do
|
80
|
+
subject.redirect '/', permanent: true
|
81
|
+
end
|
75
82
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
end
|
83
|
+
it 'sets status to 301' do
|
84
|
+
expect(subject.status).to eq 301
|
85
|
+
end
|
80
86
|
|
81
|
-
|
82
|
-
|
83
|
-
|
87
|
+
it 'sets location header' do
|
88
|
+
expect(subject.header['Location']).to eq '/'
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
84
92
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
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
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
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
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
end
|
108
|
+
it 'returns status set' do
|
109
|
+
subject.status 501
|
110
|
+
expect(subject.status).to eq 501
|
111
|
+
end
|
105
112
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
113
|
+
it 'accepts symbol for status' do
|
114
|
+
subject.status :see_other
|
115
|
+
expect(subject.status).to eq 303
|
116
|
+
end
|
110
117
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
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
|
-
|
117
|
-
|
118
|
-
|
119
|
-
end
|
123
|
+
it 'accepts unknown Fixnum status codes' do
|
124
|
+
expect { subject.status 210 }.to_not raise_error
|
125
|
+
end
|
120
126
|
|
121
|
-
|
122
|
-
|
123
|
-
|
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
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
133
|
+
describe '#content_type' do
|
134
|
+
describe 'set' do
|
135
|
+
before do
|
136
|
+
subject.content_type 'text/plain'
|
129
137
|
end
|
130
138
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
end
|
139
|
+
it 'returns value' do
|
140
|
+
expect(subject.content_type).to eq 'text/plain'
|
141
|
+
end
|
142
|
+
end
|
136
143
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
144
|
+
it 'returns default' do
|
145
|
+
expect(subject.content_type).to be nil
|
146
|
+
end
|
147
|
+
end
|
141
148
|
|
142
|
-
|
143
|
-
|
144
|
-
|
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
|
-
|
148
|
-
|
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
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
end
|
166
|
+
describe 'false' do
|
167
|
+
before do
|
168
|
+
subject.body false
|
169
|
+
end
|
158
170
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
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
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
177
|
+
it 'returns default' do
|
178
|
+
expect(subject.body).to be nil
|
179
|
+
end
|
180
|
+
end
|
168
181
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
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
|
-
|
176
|
-
|
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
|
-
|
181
|
-
|
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
|
-
|
187
|
-
|
188
|
-
|
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
|
192
|
-
expect(subject.file).to
|
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
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
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
|
-
|
206
|
-
|
207
|
-
|
223
|
+
describe '#stream' do
|
224
|
+
describe 'set' do
|
225
|
+
let(:file_object) { Class.new }
|
208
226
|
|
209
|
-
|
210
|
-
|
211
|
-
|
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
|
-
|
214
|
-
|
215
|
-
|
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
|
-
|
218
|
-
|
219
|
-
|
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
|
-
|
222
|
-
|
223
|
-
|
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
|
-
|
227
|
-
|
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
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
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
|
-
|
238
|
-
|
239
|
-
|
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
|
-
|
243
|
-
|
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 '
|
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
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
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
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
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
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
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
|
-
|
295
|
-
|
296
|
-
|
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
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
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
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
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
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
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
|
-
|
326
|
-
|
354
|
+
describe '#declared' do
|
355
|
+
# see endpoint_spec.rb#declared for spec coverage
|
327
356
|
|
328
|
-
|
329
|
-
|
330
|
-
|
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
|