grape 0.2.3 → 0.2.4
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.
- data/.yardopts +2 -0
- data/CHANGELOG.markdown +19 -0
- data/Gemfile +2 -0
- data/README.markdown +166 -131
- data/Rakefile +1 -3
- data/lib/grape.rb +0 -3
- data/lib/grape/api.rb +16 -4
- data/lib/grape/cookies.rb +32 -30
- data/lib/grape/endpoint.rb +25 -11
- data/lib/grape/entity.rb +5 -0
- data/lib/grape/error_formatter/base.rb +2 -1
- data/lib/grape/middleware/base.rb +9 -2
- data/lib/grape/middleware/error.rb +11 -11
- data/lib/grape/middleware/formatter.rb +8 -5
- data/lib/grape/middleware/versioner/header.rb +1 -3
- data/lib/grape/middleware/versioner/path.rb +15 -2
- data/lib/grape/util/deep_merge.rb +4 -4
- data/lib/grape/validations.rb +2 -3
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api_spec.rb +316 -175
- data/spec/grape/endpoint_spec.rb +159 -57
- data/spec/grape/entity_spec.rb +80 -80
- data/spec/grape/middleware/auth/basic_spec.rb +3 -3
- data/spec/grape/middleware/auth/digest_spec.rb +4 -4
- data/spec/grape/middleware/auth/oauth2_spec.rb +4 -4
- data/spec/grape/middleware/base_spec.rb +9 -9
- data/spec/grape/middleware/error_spec.rb +4 -4
- data/spec/grape/middleware/exception_spec.rb +13 -13
- data/spec/grape/middleware/formatter_spec.rb +25 -25
- data/spec/grape/middleware/versioner/header_spec.rb +23 -23
- data/spec/grape/middleware/versioner/param_spec.rb +8 -8
- data/spec/grape/middleware/versioner/path_spec.rb +8 -8
- data/spec/grape/middleware/versioner_spec.rb +6 -3
- data/spec/grape/util/hash_stack_spec.rb +20 -20
- data/spec/grape/validations/presence_spec.rb +1 -1
- data/spec/grape/validations/regexp_spec.rb +2 -2
- data/spec/grape/validations_spec.rb +4 -4
- data/spec/shared/versioning_examples.rb +48 -20
- metadata +5 -7
- data/.document +0 -5
- data/lib/grape/middleware/prefixer.rb +0 -21
- data/spec/grape/middleware/prefixer_spec.rb +0 -30
@@ -3,11 +3,11 @@ class Hash
|
|
3
3
|
# activesupport/lib/active_support/core_ext/hash/deep_merge.rb
|
4
4
|
# Returns a new hash with +self+ and +other_hash+ merged recursively.
|
5
5
|
#
|
6
|
-
#
|
7
|
-
#
|
6
|
+
# h1 = {:x => {:y => [4,5,6]}, :z => [7,8,9]}
|
7
|
+
# h2 = {:x => {:y => [7,8,9]}, :z => "xyz"}
|
8
8
|
#
|
9
|
-
#
|
10
|
-
#
|
9
|
+
# h1.deep_merge(h2) #=> { :x => {:y => [7, 8, 9]}, :z => "xyz" }
|
10
|
+
# h2.deep_merge(h1) #=> { :x => {:y => [4, 5, 6]}, :z => [7, 8, 9] }
|
11
11
|
def deep_merge(other_hash)
|
12
12
|
dup.deep_merge!(other_hash)
|
13
13
|
end
|
data/lib/grape/validations.rb
CHANGED
@@ -184,10 +184,9 @@ module Grape
|
|
184
184
|
def document_attribute(names, opts)
|
185
185
|
@last_description ||= {}
|
186
186
|
@last_description[:params] ||= {}
|
187
|
-
|
188
187
|
Array(names).each do |name|
|
189
|
-
@last_description[:params][name[:
|
190
|
-
@last_description[:params][name[:
|
188
|
+
@last_description[:params][name[:full_name].to_s] ||= {}
|
189
|
+
@last_description[:params][name[:full_name].to_s].merge!(opts)
|
191
190
|
end
|
192
191
|
end
|
193
192
|
|
data/lib/grape/version.rb
CHANGED
data/spec/grape/api_spec.rb
CHANGED
@@ -7,7 +7,18 @@ describe Grape::API do
|
|
7
7
|
def app; subject end
|
8
8
|
|
9
9
|
describe '.prefix' do
|
10
|
-
|
10
|
+
|
11
|
+
it 'routes root through with the prefix' do
|
12
|
+
subject.prefix 'awesome/sauce'
|
13
|
+
subject.get do
|
14
|
+
"Hello there."
|
15
|
+
end
|
16
|
+
|
17
|
+
get 'awesome/sauce/'
|
18
|
+
last_response.body.should eql "Hello there."
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'routes through with the prefix' do
|
11
22
|
subject.prefix 'awesome/sauce'
|
12
23
|
subject.get :hello do
|
13
24
|
"Hello there."
|
@@ -19,18 +30,19 @@ describe Grape::API do
|
|
19
30
|
get '/hello'
|
20
31
|
last_response.status.should eql 404
|
21
32
|
end
|
33
|
+
|
22
34
|
end
|
23
35
|
|
24
36
|
describe '.version' do
|
25
37
|
context 'when defined' do
|
26
|
-
it '
|
38
|
+
it 'returns version value' do
|
27
39
|
subject.version 'v1'
|
28
40
|
subject.version.should == 'v1'
|
29
41
|
end
|
30
42
|
end
|
31
43
|
|
32
44
|
context 'when not defined' do
|
33
|
-
it '
|
45
|
+
it 'returns nil' do
|
34
46
|
subject.version.should be_nil
|
35
47
|
end
|
36
48
|
end
|
@@ -45,7 +57,6 @@ describe Grape::API do
|
|
45
57
|
end
|
46
58
|
end
|
47
59
|
end
|
48
|
-
|
49
60
|
describe '.version using param' do
|
50
61
|
it_should_behave_like 'versioning' do
|
51
62
|
let(:macro_options) do
|
@@ -76,23 +87,23 @@ describe Grape::API do
|
|
76
87
|
# 'hello'
|
77
88
|
# end
|
78
89
|
|
79
|
-
# it '
|
90
|
+
# it 'routes' do
|
80
91
|
# get '/hello'
|
81
92
|
# last_response.status.should eql 200
|
82
93
|
# end
|
83
94
|
end
|
84
95
|
|
85
|
-
it '
|
96
|
+
it 'routes if any media type is allowed' do
|
86
97
|
|
87
98
|
end
|
88
99
|
end
|
89
100
|
|
90
101
|
describe '.represent' do
|
91
|
-
it '
|
102
|
+
it 'requires a :with option' do
|
92
103
|
expect{ subject.represent Object, {} }.to raise_error(ArgumentError)
|
93
104
|
end
|
94
105
|
|
95
|
-
it '
|
106
|
+
it 'adds the association to the :representations setting' do
|
96
107
|
klass = Class.new
|
97
108
|
subject.represent Object, :with => klass
|
98
109
|
subject.settings[:representations][Object].should == klass
|
@@ -100,13 +111,13 @@ describe Grape::API do
|
|
100
111
|
end
|
101
112
|
|
102
113
|
describe '.namespace' do
|
103
|
-
it '
|
114
|
+
it 'is retrievable and converted to a path' do
|
104
115
|
subject.namespace :awesome do
|
105
116
|
namespace.should == '/awesome'
|
106
117
|
end
|
107
118
|
end
|
108
119
|
|
109
|
-
it '
|
120
|
+
it 'comes after the prefix and version' do
|
110
121
|
subject.prefix :rad
|
111
122
|
subject.version 'v1', :using => :path
|
112
123
|
|
@@ -118,7 +129,7 @@ describe Grape::API do
|
|
118
129
|
last_response.body.should == "worked"
|
119
130
|
end
|
120
131
|
|
121
|
-
it '
|
132
|
+
it 'cancels itself after the block is over' do
|
122
133
|
subject.namespace :awesome do
|
123
134
|
namespace.should == '/awesome'
|
124
135
|
end
|
@@ -126,7 +137,7 @@ describe Grape::API do
|
|
126
137
|
subject.namespace.should == '/'
|
127
138
|
end
|
128
139
|
|
129
|
-
it '
|
140
|
+
it 'is stackable' do
|
130
141
|
subject.namespace :awesome do
|
131
142
|
namespace :rad do
|
132
143
|
namespace.should == '/awesome/rad'
|
@@ -136,9 +147,9 @@ describe Grape::API do
|
|
136
147
|
subject.namespace.should == '/'
|
137
148
|
end
|
138
149
|
|
139
|
-
it '
|
150
|
+
it 'accepts path segments correctly' do
|
140
151
|
subject.namespace :members do
|
141
|
-
namespace
|
152
|
+
namespace '/:member_id' do
|
142
153
|
namespace.should == '/members/:member_id'
|
143
154
|
get '/' do
|
144
155
|
params[:member_id]
|
@@ -149,7 +160,7 @@ describe Grape::API do
|
|
149
160
|
last_response.body.should == "23"
|
150
161
|
end
|
151
162
|
|
152
|
-
it '
|
163
|
+
it 'is callable with nil just to push onto the stack' do
|
153
164
|
subject.namespace do
|
154
165
|
version 'v2', :using => :path
|
155
166
|
get('/hello'){ "inner" }
|
@@ -163,7 +174,7 @@ describe Grape::API do
|
|
163
174
|
end
|
164
175
|
|
165
176
|
%w(group resource resources segment).each do |als|
|
166
|
-
it
|
177
|
+
it '`.#{als}` is an alias' do
|
167
178
|
subject.send(als, :awesome) do
|
168
179
|
namespace.should == "/awesome"
|
169
180
|
end
|
@@ -172,7 +183,7 @@ describe Grape::API do
|
|
172
183
|
end
|
173
184
|
|
174
185
|
describe '.route' do
|
175
|
-
it '
|
186
|
+
it 'allows for no path' do
|
176
187
|
subject.namespace :votes do
|
177
188
|
get do
|
178
189
|
"Votes"
|
@@ -189,7 +200,7 @@ describe Grape::API do
|
|
189
200
|
last_response.body.should eql 'Created a Vote'
|
190
201
|
end
|
191
202
|
|
192
|
-
describe
|
203
|
+
describe 'root routes should work with' do
|
193
204
|
before do
|
194
205
|
def subject.enable_root_route!
|
195
206
|
self.get("/") {"root"}
|
@@ -200,43 +211,43 @@ describe Grape::API do
|
|
200
211
|
last_response.body.should eql 'root'
|
201
212
|
end
|
202
213
|
|
203
|
-
describe
|
214
|
+
describe 'path versioned APIs' do
|
204
215
|
before do
|
205
216
|
subject.version 'v1', :using => :path
|
206
217
|
subject.enable_root_route!
|
207
218
|
end
|
208
219
|
|
209
|
-
it
|
220
|
+
it 'without a format' do
|
210
221
|
versioned_get "/", "v1", :using => :path
|
211
222
|
end
|
212
223
|
|
213
|
-
it
|
224
|
+
it 'with a format' do
|
214
225
|
get "/v1/.json"
|
215
226
|
end
|
216
227
|
end
|
217
228
|
|
218
|
-
it
|
229
|
+
it 'header versioned APIs' do
|
219
230
|
subject.version 'v1', :using => :header, :vendor => 'test'
|
220
231
|
subject.enable_root_route!
|
221
232
|
|
222
233
|
versioned_get "/", "v1", :using => :header
|
223
234
|
end
|
224
235
|
|
225
|
-
it
|
236
|
+
it 'param versioned APIs' do
|
226
237
|
subject.version 'v1', :using => :param
|
227
238
|
subject.enable_root_route!
|
228
239
|
|
229
240
|
versioned_get "/", "v1", :using => :param
|
230
241
|
end
|
231
242
|
|
232
|
-
it
|
243
|
+
it 'unversioned APIs' do
|
233
244
|
subject.enable_root_route!
|
234
245
|
|
235
246
|
get "/"
|
236
247
|
end
|
237
248
|
end
|
238
249
|
|
239
|
-
it '
|
250
|
+
it 'allows for multiple paths' do
|
240
251
|
subject.get(["/abc", "/def"]) do
|
241
252
|
"foo"
|
242
253
|
end
|
@@ -247,27 +258,27 @@ describe Grape::API do
|
|
247
258
|
last_response.body.should eql 'foo'
|
248
259
|
end
|
249
260
|
|
250
|
-
context
|
261
|
+
context 'format' do
|
251
262
|
before(:each) do
|
252
263
|
subject.get("/abc") do
|
253
264
|
RSpec::Mocks::Mock.new(:to_json => 'abc', :to_txt => 'def')
|
254
265
|
end
|
255
266
|
end
|
256
267
|
|
257
|
-
it
|
268
|
+
it 'allows .json' do
|
258
269
|
get '/abc.json'
|
259
270
|
last_response.status.should == 200
|
260
271
|
last_response.body.should eql 'abc' # json-encoded symbol
|
261
272
|
end
|
262
273
|
|
263
|
-
it
|
274
|
+
it 'allows .txt' do
|
264
275
|
get '/abc.txt'
|
265
276
|
last_response.status.should == 200
|
266
277
|
last_response.body.should eql 'def' # raw text
|
267
278
|
end
|
268
279
|
end
|
269
280
|
|
270
|
-
it '
|
281
|
+
it 'allows for format without corrupting a param' do
|
271
282
|
subject.get('/:id') do
|
272
283
|
{"id" => params[:id]}
|
273
284
|
end
|
@@ -276,7 +287,7 @@ describe Grape::API do
|
|
276
287
|
last_response.body.should eql '{"id":"awesome"}'
|
277
288
|
end
|
278
289
|
|
279
|
-
it '
|
290
|
+
it 'allows for format in namespace with no path' do
|
280
291
|
subject.namespace :abc do
|
281
292
|
get do
|
282
293
|
["json"]
|
@@ -287,7 +298,7 @@ describe Grape::API do
|
|
287
298
|
last_response.body.should eql '["json"]'
|
288
299
|
end
|
289
300
|
|
290
|
-
it '
|
301
|
+
it 'allows for multiple verbs' do
|
291
302
|
subject.route([:get, :post], '/abc') do
|
292
303
|
"hiya"
|
293
304
|
end
|
@@ -302,7 +313,7 @@ describe Grape::API do
|
|
302
313
|
last_response.body.should eql 'hiya'
|
303
314
|
end
|
304
315
|
|
305
|
-
it '
|
316
|
+
it 'allows for multipart paths' do
|
306
317
|
|
307
318
|
subject.route([:get, :post], '/:id/first') do
|
308
319
|
"first"
|
@@ -328,7 +339,7 @@ describe Grape::API do
|
|
328
339
|
|
329
340
|
end
|
330
341
|
|
331
|
-
it '
|
342
|
+
it 'allows for :any as a verb' do
|
332
343
|
subject.route(:any, '/abc') do
|
333
344
|
"lol"
|
334
345
|
end
|
@@ -341,7 +352,7 @@ describe Grape::API do
|
|
341
352
|
|
342
353
|
verbs = %w(post get head delete put options patch)
|
343
354
|
verbs.each do |verb|
|
344
|
-
it
|
355
|
+
it 'allows and properly constrain a #{verb.upcase} method' do
|
345
356
|
subject.send(verb, '/example') do
|
346
357
|
verb
|
347
358
|
end
|
@@ -353,7 +364,7 @@ describe Grape::API do
|
|
353
364
|
end
|
354
365
|
end
|
355
366
|
|
356
|
-
it '
|
367
|
+
it 'returns a 201 response code for POST by default' do
|
357
368
|
subject.post('example') do
|
358
369
|
"Created"
|
359
370
|
end
|
@@ -363,7 +374,7 @@ describe Grape::API do
|
|
363
374
|
last_response.body.should eql 'Created'
|
364
375
|
end
|
365
376
|
|
366
|
-
it '
|
377
|
+
it 'returns a 405 for an unsupported method' do
|
367
378
|
subject.get 'example' do
|
368
379
|
"example"
|
369
380
|
end
|
@@ -372,7 +383,7 @@ describe Grape::API do
|
|
372
383
|
last_response.body.should eql ''
|
373
384
|
end
|
374
385
|
|
375
|
-
specify '405 responses
|
386
|
+
specify '405 responses includes an Allow header specifying supported methods' do
|
376
387
|
subject.get 'example' do
|
377
388
|
"example"
|
378
389
|
end
|
@@ -383,7 +394,7 @@ describe Grape::API do
|
|
383
394
|
last_response.headers['Allow'].should eql 'OPTIONS, GET, POST'
|
384
395
|
end
|
385
396
|
|
386
|
-
it '
|
397
|
+
it 'adds an OPTIONS route that returns a 204 and an Allow header' do
|
387
398
|
subject.get 'example' do
|
388
399
|
"example"
|
389
400
|
end
|
@@ -395,7 +406,7 @@ describe Grape::API do
|
|
395
406
|
end
|
396
407
|
|
397
408
|
describe 'filters' do
|
398
|
-
it '
|
409
|
+
it 'adds a before filter' do
|
399
410
|
subject.before { @foo = 'first' }
|
400
411
|
subject.before { @bar = 'second' }
|
401
412
|
subject.get '/' do
|
@@ -406,7 +417,7 @@ describe Grape::API do
|
|
406
417
|
last_response.body.should eql 'first second'
|
407
418
|
end
|
408
419
|
|
409
|
-
it '
|
420
|
+
it 'adds a after_validation filter' do
|
410
421
|
subject.after_validation { @foo = "first #{params[:id]}:#{params[:id].class}" }
|
411
422
|
subject.after_validation { @bar = 'second' }
|
412
423
|
subject.params do
|
@@ -420,7 +431,7 @@ describe Grape::API do
|
|
420
431
|
last_response.body.should eql 'first 32:Fixnum second'
|
421
432
|
end
|
422
433
|
|
423
|
-
it '
|
434
|
+
it 'adds a after filter' do
|
424
435
|
m = double('after mock')
|
425
436
|
subject.after { m.do_something! }
|
426
437
|
subject.after { m.do_something! }
|
@@ -439,30 +450,30 @@ describe Grape::API do
|
|
439
450
|
subject.get("/foo") { "bar" }
|
440
451
|
end
|
441
452
|
|
442
|
-
it '
|
453
|
+
it 'sets content type for txt format' do
|
443
454
|
get '/foo'
|
444
455
|
last_response.headers['Content-Type'].should eql 'text/plain'
|
445
456
|
end
|
446
457
|
|
447
|
-
it '
|
458
|
+
it 'sets content type for json' do
|
448
459
|
get '/foo.json'
|
449
460
|
last_response.headers['Content-Type'].should eql 'application/json'
|
450
461
|
end
|
451
462
|
|
452
|
-
it '
|
463
|
+
it 'sets content type for error' do
|
453
464
|
subject.get('/error') { error!('error in plain text', 500) }
|
454
465
|
get '/error'
|
455
466
|
last_response.headers['Content-Type'].should eql 'text/plain'
|
456
467
|
end
|
457
468
|
|
458
|
-
it '
|
469
|
+
it 'sets content type for error' do
|
459
470
|
subject.format :json
|
460
471
|
subject.get('/error') { error!('error in json', 500) }
|
461
472
|
get '/error.json'
|
462
473
|
last_response.headers['Content-Type'].should eql 'application/json'
|
463
474
|
end
|
464
475
|
|
465
|
-
it '
|
476
|
+
it 'sets content type for xml' do
|
466
477
|
subject.format :xml
|
467
478
|
subject.get('/error') { error!('error in xml', 500) }
|
468
479
|
get '/error.xml'
|
@@ -489,14 +500,14 @@ describe Grape::API do
|
|
489
500
|
end
|
490
501
|
|
491
502
|
describe '.middleware' do
|
492
|
-
it '
|
503
|
+
it 'includes middleware arguments from settings' do
|
493
504
|
settings = Grape::Util::HashStack.new
|
494
505
|
settings.stub!(:stack).and_return([{:middleware => [[ApiSpec::PhonyMiddleware, 'abc', 123]]}])
|
495
506
|
subject.stub!(:settings).and_return(settings)
|
496
507
|
subject.middleware.should eql [[ApiSpec::PhonyMiddleware, 'abc', 123]]
|
497
508
|
end
|
498
509
|
|
499
|
-
it '
|
510
|
+
it 'includes all middleware from stacked settings' do
|
500
511
|
settings = Grape::Util::HashStack.new
|
501
512
|
settings.stub!(:stack).and_return [
|
502
513
|
{:middleware => [[ApiSpec::PhonyMiddleware, 123],[ApiSpec::PhonyMiddleware, 'abc']]},
|
@@ -513,12 +524,12 @@ describe Grape::API do
|
|
513
524
|
end
|
514
525
|
|
515
526
|
describe '.use' do
|
516
|
-
it '
|
527
|
+
it 'adds middleware' do
|
517
528
|
subject.use ApiSpec::PhonyMiddleware, 123
|
518
529
|
subject.middleware.should eql [[ApiSpec::PhonyMiddleware, 123]]
|
519
530
|
end
|
520
531
|
|
521
|
-
it '
|
532
|
+
it 'does not show up outside the namespace' do
|
522
533
|
subject.use ApiSpec::PhonyMiddleware, 123
|
523
534
|
subject.namespace :awesome do
|
524
535
|
use ApiSpec::PhonyMiddleware, 'abc'
|
@@ -528,7 +539,7 @@ describe Grape::API do
|
|
528
539
|
subject.middleware.should eql [[ApiSpec::PhonyMiddleware, 123]]
|
529
540
|
end
|
530
541
|
|
531
|
-
it '
|
542
|
+
it 'calls the middleware' do
|
532
543
|
subject.use ApiSpec::PhonyMiddleware, 'hello'
|
533
544
|
subject.get '/' do
|
534
545
|
env['phony.args'].first.first
|
@@ -538,13 +549,13 @@ describe Grape::API do
|
|
538
549
|
last_response.body.should eql 'hello'
|
539
550
|
end
|
540
551
|
|
541
|
-
it '
|
552
|
+
it 'adds a block if one is given' do
|
542
553
|
block = lambda{ }
|
543
554
|
subject.use ApiSpec::PhonyMiddleware, &block
|
544
555
|
subject.middleware.should eql [[ApiSpec::PhonyMiddleware, block]]
|
545
556
|
end
|
546
557
|
|
547
|
-
it '
|
558
|
+
it 'uses a block if one is given' do
|
548
559
|
block = lambda{ }
|
549
560
|
subject.use ApiSpec::PhonyMiddleware, &block
|
550
561
|
subject.get '/' do
|
@@ -555,7 +566,7 @@ describe Grape::API do
|
|
555
566
|
last_response.body.should == 'true'
|
556
567
|
end
|
557
568
|
|
558
|
-
it '
|
569
|
+
it 'does not destroy the middleware settings on multiple runs' do
|
559
570
|
block = lambda{ }
|
560
571
|
subject.use ApiSpec::PhonyMiddleware, &block
|
561
572
|
subject.get '/' do
|
@@ -570,7 +581,7 @@ describe Grape::API do
|
|
570
581
|
end
|
571
582
|
end
|
572
583
|
describe '.basic' do
|
573
|
-
it '
|
584
|
+
it 'protects any resources on the same scope' do
|
574
585
|
subject.http_basic do |u,p|
|
575
586
|
u == 'allow'
|
576
587
|
end
|
@@ -581,7 +592,7 @@ describe Grape::API do
|
|
581
592
|
last_response.status.should eql 200
|
582
593
|
end
|
583
594
|
|
584
|
-
it '
|
595
|
+
it 'is scopable' do
|
585
596
|
subject.get(:hello){ "Hello, world."}
|
586
597
|
subject.namespace :admin do
|
587
598
|
http_basic do |u,p|
|
@@ -597,7 +608,7 @@ describe Grape::API do
|
|
597
608
|
last_response.status.should eql 401
|
598
609
|
end
|
599
610
|
|
600
|
-
it '
|
611
|
+
it 'is callable via .auth as well' do
|
601
612
|
subject.auth :http_basic do |u,p|
|
602
613
|
u == 'allow'
|
603
614
|
end
|
@@ -611,11 +622,11 @@ describe Grape::API do
|
|
611
622
|
end
|
612
623
|
|
613
624
|
describe '.logger' do
|
614
|
-
it '
|
625
|
+
it 'returns an instance of Logger class by default' do
|
615
626
|
subject.logger.class.should eql Logger
|
616
627
|
end
|
617
628
|
|
618
|
-
it '
|
629
|
+
it 'allows setting a custom logger' do
|
619
630
|
mylogger = Class.new
|
620
631
|
subject.logger mylogger
|
621
632
|
mylogger.should_receive(:info).exactly(1).times
|
@@ -624,7 +635,7 @@ describe Grape::API do
|
|
624
635
|
end
|
625
636
|
|
626
637
|
describe '.helpers' do
|
627
|
-
it '
|
638
|
+
it 'is accessible from the endpoint' do
|
628
639
|
subject.helpers do
|
629
640
|
def hello
|
630
641
|
"Hello, world."
|
@@ -639,7 +650,7 @@ describe Grape::API do
|
|
639
650
|
last_response.body.should eql 'Hello, world.'
|
640
651
|
end
|
641
652
|
|
642
|
-
it '
|
653
|
+
it 'is scopable' do
|
643
654
|
subject.helpers do
|
644
655
|
def generic
|
645
656
|
'always there'
|
@@ -668,7 +679,7 @@ describe Grape::API do
|
|
668
679
|
last_response.body.should eql 'always there:only in admin'
|
669
680
|
end
|
670
681
|
|
671
|
-
it '
|
682
|
+
it 'is reopenable' do
|
672
683
|
subject.helpers do
|
673
684
|
def one
|
674
685
|
1
|
@@ -688,7 +699,7 @@ describe Grape::API do
|
|
688
699
|
lambda{get '/howdy'}.should_not raise_error
|
689
700
|
end
|
690
701
|
|
691
|
-
it '
|
702
|
+
it 'allows for modules' do
|
692
703
|
mod = Module.new do
|
693
704
|
def hello
|
694
705
|
"Hello, world."
|
@@ -704,7 +715,7 @@ describe Grape::API do
|
|
704
715
|
last_response.body.should eql 'Hello, world.'
|
705
716
|
end
|
706
717
|
|
707
|
-
it '
|
718
|
+
it 'allows multiple calls with modules and blocks' do
|
708
719
|
subject.helpers Module.new do
|
709
720
|
def one
|
710
721
|
1
|
@@ -730,7 +741,7 @@ describe Grape::API do
|
|
730
741
|
describe '.scope' do
|
731
742
|
# TODO: refactor this to not be tied to versioning. How about a generic
|
732
743
|
# .setting macro?
|
733
|
-
it '
|
744
|
+
it 'scopes the various settings' do
|
734
745
|
subject.prefix 'new'
|
735
746
|
|
736
747
|
subject.scope :legacy do
|
@@ -755,15 +766,15 @@ describe Grape::API do
|
|
755
766
|
end
|
756
767
|
end
|
757
768
|
|
758
|
-
describe
|
759
|
-
it '
|
769
|
+
describe '.rescue_from' do
|
770
|
+
it 'does not rescue errors when rescue_from is not set' do
|
760
771
|
subject.get '/exception' do
|
761
772
|
raise "rain!"
|
762
773
|
end
|
763
774
|
lambda { get '/exception' }.should raise_error
|
764
775
|
end
|
765
776
|
|
766
|
-
it '
|
777
|
+
it 'rescues all errors if rescue_from :all is called' do
|
767
778
|
subject.rescue_from :all
|
768
779
|
subject.get '/exception' do
|
769
780
|
raise "rain!"
|
@@ -772,7 +783,7 @@ describe Grape::API do
|
|
772
783
|
last_response.status.should eql 403
|
773
784
|
end
|
774
785
|
|
775
|
-
it '
|
786
|
+
it 'rescues only certain errors if rescue_from is called with specific errors' do
|
776
787
|
subject.rescue_from ArgumentError
|
777
788
|
subject.get('/rescued'){ raise ArgumentError }
|
778
789
|
subject.get('/unrescued'){ raise "beefcake" }
|
@@ -783,14 +794,14 @@ describe Grape::API do
|
|
783
794
|
lambda{ get '/unrescued' }.should raise_error
|
784
795
|
end
|
785
796
|
|
786
|
-
it '
|
797
|
+
it 'does not re-raise exceptions of type Grape::Exception::Base' do
|
787
798
|
class CustomError < Grape::Exceptions::Base; end
|
788
799
|
subject.get('/custom_exception'){ raise CustomError }
|
789
800
|
|
790
801
|
lambda{ get '/custom_exception' }.should_not raise_error
|
791
802
|
end
|
792
803
|
|
793
|
-
it '
|
804
|
+
it 'rescues custom grape exceptions' do
|
794
805
|
class CustomError < Grape::Exceptions::Base; end
|
795
806
|
subject.rescue_from CustomError do |e|
|
796
807
|
rack_response('New Error', e.status)
|
@@ -805,8 +816,8 @@ describe Grape::API do
|
|
805
816
|
end
|
806
817
|
end
|
807
818
|
|
808
|
-
describe
|
809
|
-
it '
|
819
|
+
describe '.rescue_from klass, block' do
|
820
|
+
it 'rescues Exception' do
|
810
821
|
subject.rescue_from RuntimeError do |e|
|
811
822
|
rack_response("rescued from #{e.message}", 202)
|
812
823
|
end
|
@@ -817,7 +828,7 @@ describe Grape::API do
|
|
817
828
|
last_response.status.should eql 202
|
818
829
|
last_response.body.should == 'rescued from rain!'
|
819
830
|
end
|
820
|
-
it '
|
831
|
+
it 'rescues an error via rescue_from :all' do
|
821
832
|
class ConnectionError < RuntimeError; end
|
822
833
|
subject.rescue_from :all do |e|
|
823
834
|
rack_response("rescued from #{e.class.name}", 500)
|
@@ -829,7 +840,7 @@ describe Grape::API do
|
|
829
840
|
last_response.status.should eql 500
|
830
841
|
last_response.body.should == 'rescued from ConnectionError'
|
831
842
|
end
|
832
|
-
it '
|
843
|
+
it 'rescues a specific error' do
|
833
844
|
class ConnectionError < RuntimeError; end
|
834
845
|
subject.rescue_from ConnectionError do |e|
|
835
846
|
rack_response("rescued from #{e.class.name}", 500)
|
@@ -841,7 +852,7 @@ describe Grape::API do
|
|
841
852
|
last_response.status.should eql 500
|
842
853
|
last_response.body.should == 'rescued from ConnectionError'
|
843
854
|
end
|
844
|
-
it '
|
855
|
+
it 'rescues multiple specific errors' do
|
845
856
|
class ConnectionError < RuntimeError; end
|
846
857
|
class DatabaseError < RuntimeError; end
|
847
858
|
subject.rescue_from ConnectionError do |e|
|
@@ -863,7 +874,7 @@ describe Grape::API do
|
|
863
874
|
last_response.status.should eql 500
|
864
875
|
last_response.body.should == 'rescued from DatabaseError'
|
865
876
|
end
|
866
|
-
it '
|
877
|
+
it 'does not rescue a different error' do
|
867
878
|
class CommunicationError < RuntimeError; end
|
868
879
|
subject.rescue_from RuntimeError do |e|
|
869
880
|
rack_response("rescued from #{e.class.name}", 500)
|
@@ -875,8 +886,8 @@ describe Grape::API do
|
|
875
886
|
end
|
876
887
|
end
|
877
888
|
|
878
|
-
describe
|
879
|
-
it '
|
889
|
+
describe '.error_format' do
|
890
|
+
it 'rescues all errors and return :txt' do
|
880
891
|
subject.rescue_from :all
|
881
892
|
subject.format :txt
|
882
893
|
subject.get '/exception' do
|
@@ -886,7 +897,7 @@ describe Grape::API do
|
|
886
897
|
last_response.body.should eql "rain!"
|
887
898
|
end
|
888
899
|
|
889
|
-
it '
|
900
|
+
it 'rescues all errors and return :txt with backtrace' do
|
890
901
|
subject.rescue_from :all, :backtrace => true
|
891
902
|
subject.format :txt
|
892
903
|
subject.get '/exception' do
|
@@ -896,7 +907,32 @@ describe Grape::API do
|
|
896
907
|
last_response.body.start_with?("rain!\r\n").should be_true
|
897
908
|
end
|
898
909
|
|
899
|
-
|
910
|
+
it 'rescues all errors with a default formatter' do
|
911
|
+
subject.default_format :foo
|
912
|
+
subject.content_type :foo, "text/foo"
|
913
|
+
subject.rescue_from :all
|
914
|
+
subject.get '/exception' do
|
915
|
+
raise "rain!"
|
916
|
+
end
|
917
|
+
get '/exception.foo'
|
918
|
+
last_response.body.should start_with "rain!"
|
919
|
+
end
|
920
|
+
|
921
|
+
it 'defaults the error formatter to format' do
|
922
|
+
subject.format :json
|
923
|
+
subject.rescue_from :all
|
924
|
+
subject.content_type :json, "application/json"
|
925
|
+
subject.content_type :foo, "text/foo"
|
926
|
+
subject.get '/exception' do
|
927
|
+
raise "rain!"
|
928
|
+
end
|
929
|
+
get '/exception.json'
|
930
|
+
last_response.body.should == '{"error":"rain!"}'
|
931
|
+
get '/exception.foo'
|
932
|
+
last_response.body.should == '{"error":"rain!"}'
|
933
|
+
end
|
934
|
+
|
935
|
+
context 'class' do
|
900
936
|
before :each do
|
901
937
|
class CustomErrorFormatter
|
902
938
|
def self.call(message, backtrace, options, env)
|
@@ -904,7 +940,7 @@ describe Grape::API do
|
|
904
940
|
end
|
905
941
|
end
|
906
942
|
end
|
907
|
-
it '
|
943
|
+
it 'returns a custom error format' do
|
908
944
|
subject.rescue_from :all, :backtrace => true
|
909
945
|
subject.error_formatter :txt, CustomErrorFormatter
|
910
946
|
subject.get '/exception' do
|
@@ -915,7 +951,7 @@ describe Grape::API do
|
|
915
951
|
end
|
916
952
|
end
|
917
953
|
|
918
|
-
it '
|
954
|
+
it 'rescues all errors and return :json' do
|
919
955
|
subject.rescue_from :all
|
920
956
|
subject.format :json
|
921
957
|
subject.get '/exception' do
|
@@ -924,7 +960,7 @@ describe Grape::API do
|
|
924
960
|
get '/exception'
|
925
961
|
last_response.body.should eql '{"error":"rain!"}'
|
926
962
|
end
|
927
|
-
it '
|
963
|
+
it 'rescues all errors and return :json with backtrace' do
|
928
964
|
subject.rescue_from :all, :backtrace => true
|
929
965
|
subject.format :json
|
930
966
|
subject.get '/exception' do
|
@@ -935,7 +971,7 @@ describe Grape::API do
|
|
935
971
|
json["error"].should eql 'rain!'
|
936
972
|
json["backtrace"].length.should > 0
|
937
973
|
end
|
938
|
-
it '
|
974
|
+
it 'rescues error! and return txt' do
|
939
975
|
subject.format :txt
|
940
976
|
subject.get '/error' do
|
941
977
|
error!("Access Denied", 401)
|
@@ -943,7 +979,7 @@ describe Grape::API do
|
|
943
979
|
get '/error'
|
944
980
|
last_response.body.should eql "Access Denied"
|
945
981
|
end
|
946
|
-
it '
|
982
|
+
it 'rescues error! and return json' do
|
947
983
|
subject.format :json
|
948
984
|
subject.get '/error' do
|
949
985
|
error!("Access Denied", 401)
|
@@ -953,8 +989,8 @@ describe Grape::API do
|
|
953
989
|
end
|
954
990
|
end
|
955
991
|
|
956
|
-
describe
|
957
|
-
it
|
992
|
+
describe '.content_type' do
|
993
|
+
it 'sets additional content-type' do
|
958
994
|
subject.content_type :xls, "application/vnd.ms-excel"
|
959
995
|
subject.get :excel do
|
960
996
|
"some binary content"
|
@@ -962,7 +998,7 @@ describe Grape::API do
|
|
962
998
|
get '/excel.xls'
|
963
999
|
last_response.content_type.should == "application/vnd.ms-excel"
|
964
1000
|
end
|
965
|
-
it
|
1001
|
+
it 'allows to override content-type' do
|
966
1002
|
subject.get :content do
|
967
1003
|
content_type "text/javascript"
|
968
1004
|
"var x = 1;"
|
@@ -970,7 +1006,7 @@ describe Grape::API do
|
|
970
1006
|
get '/content'
|
971
1007
|
last_response.content_type.should == "text/javascript"
|
972
1008
|
end
|
973
|
-
it
|
1009
|
+
it 'removes existing content types' do
|
974
1010
|
subject.content_type :xls, "application/vnd.ms-excel"
|
975
1011
|
subject.get :excel do
|
976
1012
|
"some binary content"
|
@@ -981,8 +1017,8 @@ describe Grape::API do
|
|
981
1017
|
end
|
982
1018
|
end
|
983
1019
|
|
984
|
-
describe
|
985
|
-
context
|
1020
|
+
describe '.formatter' do
|
1021
|
+
context 'multiple formatters' do
|
986
1022
|
before :each do
|
987
1023
|
subject.formatter :json, lambda { |object, env| "{\"custom_formatter\":\"#{object[:some]}\"}" }
|
988
1024
|
subject.formatter :txt, lambda { |object, env| "custom_formatter: #{object[:some]}" }
|
@@ -999,7 +1035,7 @@ describe Grape::API do
|
|
999
1035
|
last_response.body.should eql 'custom_formatter: hash'
|
1000
1036
|
end
|
1001
1037
|
end
|
1002
|
-
context
|
1038
|
+
context 'custom formatter' do
|
1003
1039
|
before :each do
|
1004
1040
|
subject.content_type :json, 'application/json'
|
1005
1041
|
subject.content_type :custom, 'application/custom'
|
@@ -1017,7 +1053,7 @@ describe Grape::API do
|
|
1017
1053
|
last_response.body.should eql '{"custom_formatter":"hash"}'
|
1018
1054
|
end
|
1019
1055
|
end
|
1020
|
-
context
|
1056
|
+
context 'custom formatter class' do
|
1021
1057
|
module CustomFormatter
|
1022
1058
|
def self.call(object, env)
|
1023
1059
|
"{\"custom_formatter\":\"#{object[:some]}\"}"
|
@@ -1042,8 +1078,8 @@ describe Grape::API do
|
|
1042
1078
|
end
|
1043
1079
|
end
|
1044
1080
|
|
1045
|
-
describe
|
1046
|
-
it '
|
1081
|
+
describe '.default_error_status' do
|
1082
|
+
it 'allows setting default_error_status' do
|
1047
1083
|
subject.rescue_from :all
|
1048
1084
|
subject.default_error_status 200
|
1049
1085
|
subject.get '/exception' do
|
@@ -1052,7 +1088,7 @@ describe Grape::API do
|
|
1052
1088
|
get '/exception'
|
1053
1089
|
last_response.status.should eql 200
|
1054
1090
|
end
|
1055
|
-
it '
|
1091
|
+
it 'has a default error status' do
|
1056
1092
|
subject.rescue_from :all
|
1057
1093
|
subject.get '/exception' do
|
1058
1094
|
raise "rain!"
|
@@ -1062,19 +1098,19 @@ describe Grape::API do
|
|
1062
1098
|
end
|
1063
1099
|
end
|
1064
1100
|
|
1065
|
-
context
|
1066
|
-
describe
|
1067
|
-
it
|
1101
|
+
context 'routes' do
|
1102
|
+
describe 'empty api structure' do
|
1103
|
+
it 'returns an empty array of routes' do
|
1068
1104
|
subject.routes.should == []
|
1069
1105
|
end
|
1070
1106
|
end
|
1071
|
-
describe
|
1107
|
+
describe 'single method api structure' do
|
1072
1108
|
before(:each) do
|
1073
1109
|
subject.get :ping do
|
1074
1110
|
'pong'
|
1075
1111
|
end
|
1076
1112
|
end
|
1077
|
-
it
|
1113
|
+
it 'returns one route' do
|
1078
1114
|
subject.routes.size.should == 1
|
1079
1115
|
route = subject.routes[0]
|
1080
1116
|
route.route_version.should be_nil
|
@@ -1082,60 +1118,60 @@ describe Grape::API do
|
|
1082
1118
|
route.route_method.should == "GET"
|
1083
1119
|
end
|
1084
1120
|
end
|
1085
|
-
describe
|
1121
|
+
describe 'api structure with two versions and a namespace' do
|
1086
1122
|
before :each do
|
1087
1123
|
subject.version 'v1', :using => :path
|
1088
|
-
subject.get
|
1124
|
+
subject.get 'version' do
|
1089
1125
|
api.version
|
1090
1126
|
end
|
1091
1127
|
# version v2
|
1092
1128
|
subject.version 'v2', :using => :path
|
1093
1129
|
subject.prefix 'p'
|
1094
|
-
subject.namespace
|
1095
|
-
namespace
|
1096
|
-
get
|
1130
|
+
subject.namespace 'n1' do
|
1131
|
+
namespace 'n2' do
|
1132
|
+
get 'version' do
|
1097
1133
|
api.version
|
1098
1134
|
end
|
1099
1135
|
end
|
1100
1136
|
end
|
1101
1137
|
end
|
1102
|
-
it
|
1138
|
+
it 'returns the latest version set' do
|
1103
1139
|
subject.version.should == 'v2'
|
1104
1140
|
end
|
1105
|
-
it
|
1141
|
+
it 'returns versions' do
|
1106
1142
|
subject.versions.should == [ 'v1', 'v2' ]
|
1107
1143
|
end
|
1108
|
-
it
|
1144
|
+
it 'sets route paths' do
|
1109
1145
|
subject.routes.size.should >= 2
|
1110
1146
|
subject.routes[0].route_path.should == "/:version/version(.:format)"
|
1111
1147
|
subject.routes[1].route_path.should == "/p/:version/n1/n2/version(.:format)"
|
1112
1148
|
end
|
1113
|
-
it
|
1149
|
+
it 'sets route versions' do
|
1114
1150
|
subject.routes[0].route_version.should == 'v1'
|
1115
1151
|
subject.routes[1].route_version.should == 'v2'
|
1116
1152
|
end
|
1117
|
-
it
|
1153
|
+
it 'sets a nested namespace' do
|
1118
1154
|
subject.routes[1].route_namespace.should == "/n1/n2"
|
1119
1155
|
end
|
1120
|
-
it
|
1156
|
+
it 'sets prefix' do
|
1121
1157
|
subject.routes[1].route_prefix.should == 'p'
|
1122
1158
|
end
|
1123
1159
|
end
|
1124
|
-
describe
|
1160
|
+
describe 'api structure with additional parameters' do
|
1125
1161
|
before(:each) do
|
1126
1162
|
subject.get 'split/:string', { :params => { "token" => "a token" }, :optional_params => { "limit" => "the limit" } } do
|
1127
1163
|
params[:string].split(params[:token], (params[:limit] || 0).to_i)
|
1128
1164
|
end
|
1129
1165
|
end
|
1130
|
-
it
|
1166
|
+
it 'splits a string' do
|
1131
1167
|
get "/split/a,b,c.json", :token => ','
|
1132
1168
|
last_response.body.should == '["a","b","c"]'
|
1133
1169
|
end
|
1134
|
-
it
|
1170
|
+
it 'splits a string with limit' do
|
1135
1171
|
get "/split/a,b,c.json", :token => ',', :limit => '2'
|
1136
1172
|
last_response.body.should == '["a","b,c"]'
|
1137
1173
|
end
|
1138
|
-
it
|
1174
|
+
it 'sets route_params' do
|
1139
1175
|
subject.routes.map { |route|
|
1140
1176
|
{ :params => route.route_params, :optional_params => route.route_optional_params }
|
1141
1177
|
}.should eq [
|
@@ -1145,15 +1181,15 @@ describe Grape::API do
|
|
1145
1181
|
end
|
1146
1182
|
end
|
1147
1183
|
|
1148
|
-
context
|
1149
|
-
it
|
1184
|
+
context 'desc' do
|
1185
|
+
it 'empty array of routes' do
|
1150
1186
|
subject.routes.should == []
|
1151
1187
|
end
|
1152
|
-
it
|
1188
|
+
it 'empty array of routes' do
|
1153
1189
|
subject.desc "grape api"
|
1154
1190
|
subject.routes.should == []
|
1155
1191
|
end
|
1156
|
-
it
|
1192
|
+
it 'describes a method' do
|
1157
1193
|
subject.desc "first method"
|
1158
1194
|
subject.get :first do ; end
|
1159
1195
|
subject.routes.length.should == 1
|
@@ -1162,7 +1198,7 @@ describe Grape::API do
|
|
1162
1198
|
route.route_foo.should be_nil
|
1163
1199
|
route.route_params.should == { }
|
1164
1200
|
end
|
1165
|
-
it
|
1201
|
+
it 'describes methods separately' do
|
1166
1202
|
subject.desc "first method"
|
1167
1203
|
subject.get :first do ; end
|
1168
1204
|
subject.desc "second method"
|
@@ -1175,7 +1211,7 @@ describe Grape::API do
|
|
1175
1211
|
{ :description => "second method", :params => {} }
|
1176
1212
|
]
|
1177
1213
|
end
|
1178
|
-
it
|
1214
|
+
it 'resets desc' do
|
1179
1215
|
subject.desc "first method"
|
1180
1216
|
subject.get :first do ; end
|
1181
1217
|
subject.get :second do ; end
|
@@ -1186,10 +1222,10 @@ describe Grape::API do
|
|
1186
1222
|
{ :description => nil, :params => {} }
|
1187
1223
|
]
|
1188
1224
|
end
|
1189
|
-
it
|
1190
|
-
subject.namespace
|
1225
|
+
it 'namespaces and describe arbitrary parameters' do
|
1226
|
+
subject.namespace 'ns' do
|
1191
1227
|
desc "ns second", :foo => "bar"
|
1192
|
-
get
|
1228
|
+
get 'second' do ; end
|
1193
1229
|
end
|
1194
1230
|
subject.routes.map { |route|
|
1195
1231
|
{ :description => route.route_description, :foo => route.route_foo, :params => route.route_params }
|
@@ -1197,20 +1233,20 @@ describe Grape::API do
|
|
1197
1233
|
{ :description => "ns second", :foo => "bar", :params => {} },
|
1198
1234
|
]
|
1199
1235
|
end
|
1200
|
-
it
|
1236
|
+
it 'includes details' do
|
1201
1237
|
subject.desc "method", :details => "method details"
|
1202
|
-
subject.get
|
1238
|
+
subject.get 'method' do ; end
|
1203
1239
|
subject.routes.map { |route|
|
1204
1240
|
{ :description => route.route_description, :details => route.route_details, :params => route.route_params }
|
1205
1241
|
}.should eq [
|
1206
1242
|
{ :description => "method", :details => "method details", :params => {} },
|
1207
1243
|
]
|
1208
1244
|
end
|
1209
|
-
it
|
1245
|
+
it 'describes a method with parameters' do
|
1210
1246
|
subject.desc "Reverses a string.", { :params =>
|
1211
1247
|
{ "s" => { :desc => "string to reverse", :type => "string" }}
|
1212
1248
|
}
|
1213
|
-
subject.get
|
1249
|
+
subject.get 'reverse' do
|
1214
1250
|
params[:s].reverse
|
1215
1251
|
end
|
1216
1252
|
subject.routes.map { |route|
|
@@ -1219,51 +1255,86 @@ describe Grape::API do
|
|
1219
1255
|
{ :description => "Reverses a string.", :params => { "s" => { :desc => "string to reverse", :type => "string" } } }
|
1220
1256
|
]
|
1221
1257
|
end
|
1222
|
-
it
|
1258
|
+
it 'merges the parameters of the namespace with the parameters of the method' do
|
1223
1259
|
subject.desc "namespace"
|
1224
1260
|
subject.params do
|
1225
1261
|
requires :ns_param, :desc => "namespace parameter"
|
1226
1262
|
end
|
1227
|
-
subject.namespace
|
1263
|
+
subject.namespace 'ns' do
|
1228
1264
|
desc "method"
|
1229
1265
|
params do
|
1230
1266
|
optional :method_param, :desc => "method parameter"
|
1231
1267
|
end
|
1232
|
-
get
|
1268
|
+
get 'method' do ; end
|
1233
1269
|
end
|
1234
1270
|
subject.routes.map { |route|
|
1235
1271
|
{ :description => route.route_description, :params => route.route_params }
|
1236
1272
|
}.should eq [
|
1237
|
-
{ :description => "method",
|
1273
|
+
{ :description => "method",
|
1274
|
+
:params => {
|
1275
|
+
"ns_param" => { :required => true, :desc => "namespace parameter" },
|
1276
|
+
"method_param" => { :required => false, :desc => "method parameter" }
|
1277
|
+
}
|
1278
|
+
}
|
1238
1279
|
]
|
1239
1280
|
end
|
1240
|
-
it
|
1281
|
+
it 'merges the parameters of nested namespaces' do
|
1241
1282
|
subject.desc "ns1"
|
1242
1283
|
subject.params do
|
1243
1284
|
optional :ns_param, :desc => "ns param 1"
|
1244
1285
|
requires :ns1_param, :desc => "ns1 param"
|
1245
1286
|
end
|
1246
|
-
subject.namespace
|
1287
|
+
subject.namespace 'ns1' do
|
1247
1288
|
desc "ns2"
|
1248
1289
|
params do
|
1249
1290
|
requires :ns_param, :desc => "ns param 2"
|
1250
1291
|
requires :ns2_param, :desc => "ns2 param"
|
1251
1292
|
end
|
1252
|
-
namespace
|
1293
|
+
namespace 'ns2' do
|
1253
1294
|
desc "method"
|
1254
1295
|
params do
|
1255
1296
|
optional :method_param, :desc => "method param"
|
1256
1297
|
end
|
1257
|
-
get
|
1298
|
+
get 'method' do ; end
|
1258
1299
|
end
|
1259
1300
|
end
|
1260
1301
|
subject.routes.map { |route|
|
1261
1302
|
{ :description => route.route_description, :params => route.route_params }
|
1262
1303
|
}.should eq [
|
1263
|
-
{ :description => "method",
|
1304
|
+
{ :description => "method",
|
1305
|
+
:params => {
|
1306
|
+
"ns_param" => { :required => true, :desc => "ns param 2" },
|
1307
|
+
"ns1_param" => { :required => true, :desc => "ns1 param" },
|
1308
|
+
"ns2_param" => { :required => true, :desc => "ns2 param" },
|
1309
|
+
"method_param" => { :required => false, :desc => "method param" }
|
1310
|
+
}
|
1311
|
+
}
|
1264
1312
|
]
|
1265
1313
|
end
|
1266
|
-
it "
|
1314
|
+
it "groups nested params and prevents overwriting of params with same name in different groups" do
|
1315
|
+
subject.desc "method"
|
1316
|
+
subject.params do
|
1317
|
+
group :group1 do
|
1318
|
+
optional :param1, :desc => "group1 param1 desc"
|
1319
|
+
requires :param2, :desc => "group1 param2 desc"
|
1320
|
+
end
|
1321
|
+
group :group2 do
|
1322
|
+
optional :param1, :desc => "group2 param1 desc"
|
1323
|
+
requires :param2, :desc => "group2 param2 desc"
|
1324
|
+
end
|
1325
|
+
end
|
1326
|
+
subject.get "method" do ; end
|
1327
|
+
|
1328
|
+
subject.routes.map { |route|
|
1329
|
+
route.route_params
|
1330
|
+
}.should eq [{
|
1331
|
+
"group1[param1]" => { :required => false, :desc => "group1 param1 desc" },
|
1332
|
+
"group1[param2]" => { :required => true, :desc => "group1 param2 desc" },
|
1333
|
+
"group2[param1]" => { :required => false, :desc => "group2 param1 desc" },
|
1334
|
+
"group2[param2]" => { :required => true, :desc => "group2 param2 desc" }
|
1335
|
+
}]
|
1336
|
+
end
|
1337
|
+
it 'uses full name of parameters in nested groups' do
|
1267
1338
|
subject.desc "nesting"
|
1268
1339
|
subject.params do
|
1269
1340
|
requires :root_param, :desc => "root param"
|
@@ -1271,29 +1342,34 @@ describe Grape::API do
|
|
1271
1342
|
requires :nested_param, :desc => "nested param"
|
1272
1343
|
end
|
1273
1344
|
end
|
1274
|
-
subject.get
|
1345
|
+
subject.get 'method' do ; end
|
1275
1346
|
subject.routes.map { |route|
|
1276
1347
|
{ :description => route.route_description, :params => route.route_params }
|
1277
1348
|
}.should eq [
|
1278
|
-
{ :description => "nesting",
|
1349
|
+
{ :description => "nesting",
|
1350
|
+
:params => {
|
1351
|
+
"root_param" => { :required => true, :desc => "root param" },
|
1352
|
+
"nested[nested_param]" => { :required => true, :desc => "nested param" }
|
1353
|
+
}
|
1354
|
+
}
|
1279
1355
|
]
|
1280
1356
|
end
|
1281
|
-
it
|
1357
|
+
it 'parses parameters when no description is given' do
|
1282
1358
|
subject.params do
|
1283
1359
|
requires :one_param, :desc => "one param"
|
1284
1360
|
end
|
1285
|
-
subject.get
|
1361
|
+
subject.get 'method' do ; end
|
1286
1362
|
subject.routes.map { |route|
|
1287
1363
|
{ :description => route.route_description, :params => route.route_params }
|
1288
1364
|
}.should eq [
|
1289
|
-
{ :description => nil, :params => { "one_param" => { :required => true, :desc => "one param"
|
1365
|
+
{ :description => nil, :params => { "one_param" => { :required => true, :desc => "one param" } } }
|
1290
1366
|
]
|
1291
1367
|
end
|
1292
|
-
it
|
1368
|
+
it 'does not symbolize params' do
|
1293
1369
|
subject.desc "Reverses a string.", { :params =>
|
1294
1370
|
{ "s" => { :desc => "string to reverse", :type => "string" }}
|
1295
1371
|
}
|
1296
|
-
subject.get
|
1372
|
+
subject.get 'reverse/:s' do
|
1297
1373
|
params[:s].reverse
|
1298
1374
|
end
|
1299
1375
|
subject.routes.map { |route|
|
@@ -1312,17 +1388,17 @@ describe Grape::API do
|
|
1312
1388
|
subject.mount mounted_app => '/mounty'
|
1313
1389
|
end
|
1314
1390
|
|
1315
|
-
it '
|
1391
|
+
it 'makes a bare Rack app available at the endpoint' do
|
1316
1392
|
get '/mounty'
|
1317
1393
|
last_response.body.should == 'MOUNTED'
|
1318
1394
|
end
|
1319
1395
|
|
1320
|
-
it '
|
1396
|
+
it 'anchors the routes, passing all subroutes to it' do
|
1321
1397
|
get '/mounty/awesome'
|
1322
1398
|
last_response.body.should == 'MOUNTED'
|
1323
1399
|
end
|
1324
1400
|
|
1325
|
-
it '
|
1401
|
+
it 'is able to cascade' do
|
1326
1402
|
subject.mount lambda{ |env|
|
1327
1403
|
headers = {}
|
1328
1404
|
headers['X-Cascade'] == 'pass' unless env['PATH_INFO'].include?('boo')
|
@@ -1337,7 +1413,7 @@ describe Grape::API do
|
|
1337
1413
|
end
|
1338
1414
|
|
1339
1415
|
context 'without a hash' do
|
1340
|
-
it '
|
1416
|
+
it 'calls through setting the route to "/"' do
|
1341
1417
|
subject.mount mounted_app
|
1342
1418
|
get '/'
|
1343
1419
|
last_response.body.should == 'MOUNTED'
|
@@ -1345,7 +1421,7 @@ describe Grape::API do
|
|
1345
1421
|
end
|
1346
1422
|
|
1347
1423
|
context 'mounting an API' do
|
1348
|
-
it '
|
1424
|
+
it 'applies the settings of the mounting api' do
|
1349
1425
|
subject.version 'v1', :using => :path
|
1350
1426
|
|
1351
1427
|
subject.namespace :cool do
|
@@ -1360,7 +1436,7 @@ describe Grape::API do
|
|
1360
1436
|
last_response.body.should == 'yo'
|
1361
1437
|
end
|
1362
1438
|
|
1363
|
-
it '
|
1439
|
+
it 'inherits rescues even when some defined by mounted' do
|
1364
1440
|
subject.rescue_from :all do |e|
|
1365
1441
|
rack_response("rescued from #{e.message}", 202)
|
1366
1442
|
end
|
@@ -1374,11 +1450,23 @@ describe Grape::API do
|
|
1374
1450
|
last_response.status.should eql 202
|
1375
1451
|
last_response.body.should == 'rescued from doh!'
|
1376
1452
|
end
|
1453
|
+
|
1454
|
+
it 'collects the routes of the mounted api' do
|
1455
|
+
subject.namespace :cool do
|
1456
|
+
app = Class.new(Grape::API)
|
1457
|
+
app.get('/awesome') {}
|
1458
|
+
app.post('/sauce') {}
|
1459
|
+
mount app
|
1460
|
+
end
|
1461
|
+
subject.routes.size.should == 2
|
1462
|
+
subject.routes.first.route_path.should =~ /\/cool\/awesome/
|
1463
|
+
subject.routes.last.route_path.should =~ /\/cool\/sauce/
|
1464
|
+
end
|
1377
1465
|
end
|
1378
1466
|
end
|
1379
1467
|
|
1380
1468
|
describe '.endpoints' do
|
1381
|
-
it '
|
1469
|
+
it 'adds one for each route created' do
|
1382
1470
|
subject.get '/'
|
1383
1471
|
subject.post '/'
|
1384
1472
|
subject.endpoints.size.should == 2
|
@@ -1386,7 +1474,7 @@ describe Grape::API do
|
|
1386
1474
|
end
|
1387
1475
|
|
1388
1476
|
describe '.compile' do
|
1389
|
-
it '
|
1477
|
+
it 'sets the instance' do
|
1390
1478
|
subject.instance.should be_nil
|
1391
1479
|
subject.compile
|
1392
1480
|
subject.instance.should be_kind_of(subject)
|
@@ -1394,15 +1482,34 @@ describe Grape::API do
|
|
1394
1482
|
end
|
1395
1483
|
|
1396
1484
|
describe '.change!' do
|
1397
|
-
it '
|
1485
|
+
it 'invalidates any compiled instance' do
|
1398
1486
|
subject.compile
|
1399
1487
|
subject.change!
|
1400
1488
|
subject.instance.should be_nil
|
1401
1489
|
end
|
1402
1490
|
end
|
1403
1491
|
|
1404
|
-
describe ".
|
1405
|
-
|
1492
|
+
describe ".endpoint" do
|
1493
|
+
before(:each) do
|
1494
|
+
subject.format :json
|
1495
|
+
subject.get '/endpoint/options' do
|
1496
|
+
{
|
1497
|
+
:path => options[:path],
|
1498
|
+
:source_location => source.source_location
|
1499
|
+
}
|
1500
|
+
end
|
1501
|
+
end
|
1502
|
+
it 'path' do
|
1503
|
+
get '/endpoint/options'
|
1504
|
+
options = MultiJson.load(last_response.body)
|
1505
|
+
options["path"].should == ["/endpoint/options"]
|
1506
|
+
options["source_location"][0].should include "api_spec.rb"
|
1507
|
+
options["source_location"][1].to_i.should > 0
|
1508
|
+
end
|
1509
|
+
end
|
1510
|
+
|
1511
|
+
describe '.route' do
|
1512
|
+
context 'plain' do
|
1406
1513
|
before(:each) do
|
1407
1514
|
subject.get '/' do
|
1408
1515
|
route.route_path
|
@@ -1411,14 +1518,14 @@ describe Grape::API do
|
|
1411
1518
|
route.route_path
|
1412
1519
|
end
|
1413
1520
|
end
|
1414
|
-
it '
|
1521
|
+
it 'provides access to route info' do
|
1415
1522
|
get '/'
|
1416
1523
|
last_response.body.should == "/(.:format)"
|
1417
1524
|
get '/path'
|
1418
1525
|
last_response.body.should == "/path(.:format)"
|
1419
1526
|
end
|
1420
1527
|
end
|
1421
|
-
context
|
1528
|
+
context 'with desc' do
|
1422
1529
|
before(:each) do
|
1423
1530
|
subject.desc 'returns description'
|
1424
1531
|
subject.get '/description' do
|
@@ -1429,58 +1536,58 @@ describe Grape::API do
|
|
1429
1536
|
route.route_params[params[:id]]
|
1430
1537
|
end
|
1431
1538
|
end
|
1432
|
-
it '
|
1539
|
+
it 'returns route description' do
|
1433
1540
|
get '/description'
|
1434
1541
|
last_response.body.should == "returns description"
|
1435
1542
|
end
|
1436
|
-
it '
|
1543
|
+
it 'returns route parameters' do
|
1437
1544
|
get '/params/x'
|
1438
1545
|
last_response.body.should == "y"
|
1439
1546
|
end
|
1440
1547
|
end
|
1441
1548
|
end
|
1442
|
-
context
|
1443
|
-
context
|
1549
|
+
context 'format' do
|
1550
|
+
context ':txt' do
|
1444
1551
|
before(:each) do
|
1445
1552
|
subject.format :txt
|
1446
1553
|
subject.get '/meaning_of_life' do
|
1447
1554
|
{ :meaning_of_life => 42 }
|
1448
1555
|
end
|
1449
1556
|
end
|
1450
|
-
it
|
1557
|
+
it 'forces txt without an extension' do
|
1451
1558
|
get '/meaning_of_life'
|
1452
1559
|
last_response.body.should == { :meaning_of_life => 42 }.to_s
|
1453
1560
|
end
|
1454
|
-
it
|
1561
|
+
it 'does not force txt with an extension' do
|
1455
1562
|
get '/meaning_of_life.json'
|
1456
1563
|
last_response.body.should == { :meaning_of_life => 42 }.to_json
|
1457
1564
|
end
|
1458
|
-
it
|
1565
|
+
it 'forces txt from a non-accepting header' do
|
1459
1566
|
get '/meaning_of_life', {}, { 'HTTP_ACCEPT' => 'application/json' }
|
1460
1567
|
last_response.body.should == { :meaning_of_life => 42 }.to_s
|
1461
1568
|
end
|
1462
1569
|
end
|
1463
|
-
context
|
1570
|
+
context ':json' do
|
1464
1571
|
before(:each) do
|
1465
1572
|
subject.format :json
|
1466
1573
|
subject.get '/meaning_of_life' do
|
1467
1574
|
{ :meaning_of_life => 42 }
|
1468
1575
|
end
|
1469
1576
|
end
|
1470
|
-
it
|
1577
|
+
it 'forces json without an extension' do
|
1471
1578
|
get '/meaning_of_life'
|
1472
1579
|
last_response.body.should == { :meaning_of_life => 42 }.to_json
|
1473
1580
|
end
|
1474
|
-
it
|
1581
|
+
it 'does not force json with an extension' do
|
1475
1582
|
get '/meaning_of_life.txt'
|
1476
1583
|
last_response.body.should == { :meaning_of_life => 42 }.to_s
|
1477
1584
|
end
|
1478
|
-
it
|
1585
|
+
it 'forces json from a non-accepting header' do
|
1479
1586
|
get '/meaning_of_life', {}, { 'HTTP_ACCEPT' => 'text/html' }
|
1480
1587
|
last_response.body.should == { :meaning_of_life => 42 }.to_json
|
1481
1588
|
end
|
1482
1589
|
end
|
1483
|
-
context
|
1590
|
+
context ':serializable_hash' do
|
1484
1591
|
before(:each) do
|
1485
1592
|
class SimpleExample
|
1486
1593
|
def serializable_hash
|
@@ -1489,21 +1596,21 @@ describe Grape::API do
|
|
1489
1596
|
end
|
1490
1597
|
subject.format :serializable_hash
|
1491
1598
|
end
|
1492
|
-
it
|
1599
|
+
it 'instance' do
|
1493
1600
|
subject.get '/example' do
|
1494
1601
|
SimpleExample.new
|
1495
1602
|
end
|
1496
1603
|
get '/example'
|
1497
1604
|
last_response.body.should == '{"abc":"def"}'
|
1498
1605
|
end
|
1499
|
-
it
|
1606
|
+
it 'root' do
|
1500
1607
|
subject.get '/example' do
|
1501
1608
|
{ "root" => SimpleExample.new }
|
1502
1609
|
end
|
1503
1610
|
get '/example'
|
1504
1611
|
last_response.body.should == '{"root":{"abc":"def"}}'
|
1505
1612
|
end
|
1506
|
-
it
|
1613
|
+
it 'array' do
|
1507
1614
|
subject.get '/examples' do
|
1508
1615
|
[ SimpleExample.new, SimpleExample.new ]
|
1509
1616
|
end
|
@@ -1512,4 +1619,38 @@ describe Grape::API do
|
|
1512
1619
|
end
|
1513
1620
|
end
|
1514
1621
|
end
|
1622
|
+
|
1623
|
+
context "catch-all" do
|
1624
|
+
before do
|
1625
|
+
api1 = Class.new(Grape::API)
|
1626
|
+
api1.version 'v1', :using => :path
|
1627
|
+
api1.get "hello" do
|
1628
|
+
"v1"
|
1629
|
+
end
|
1630
|
+
api2 = Class.new(Grape::API)
|
1631
|
+
api2.version 'v2', :using => :path
|
1632
|
+
api2.get "hello" do
|
1633
|
+
"v2"
|
1634
|
+
end
|
1635
|
+
subject.mount api1
|
1636
|
+
subject.mount api2
|
1637
|
+
end
|
1638
|
+
[ true, false ].each do |anchor|
|
1639
|
+
it "anchor=#{anchor}" do
|
1640
|
+
subject.route :any, '*path', :anchor => anchor do
|
1641
|
+
error!("Unrecognized request path: #{params[:path]} - #{env['PATH_INFO']}#{env['SCRIPT_NAME']}", 404)
|
1642
|
+
end
|
1643
|
+
get "/v1/hello"
|
1644
|
+
last_response.status.should == 200
|
1645
|
+
last_response.body.should == "v1"
|
1646
|
+
get "/v2/hello"
|
1647
|
+
last_response.status.should == 200
|
1648
|
+
last_response.body.should == "v2"
|
1649
|
+
get "/foobar"
|
1650
|
+
last_response.status.should == 404
|
1651
|
+
last_response.body.should == "Unrecognized request path: foobar - /foobar"
|
1652
|
+
end
|
1653
|
+
end
|
1654
|
+
end
|
1655
|
+
|
1515
1656
|
end
|