grape 0.2.1.1 → 0.2.2
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/.gitignore +1 -0
- data/CHANGELOG.markdown +23 -2
- data/Gemfile +2 -0
- data/README.markdown +402 -227
- data/grape.gemspec +5 -2
- data/lib/grape.rb +6 -0
- data/lib/grape/api.rb +59 -2
- data/lib/grape/endpoint.rb +49 -9
- data/lib/grape/entity.rb +75 -8
- data/lib/grape/exceptions/base.rb +17 -0
- data/lib/grape/exceptions/validation_error.rb +10 -0
- data/lib/grape/middleware/base.rb +28 -19
- data/lib/grape/middleware/error.rb +11 -3
- data/lib/grape/middleware/formatter.rb +11 -18
- data/lib/grape/middleware/versioner/header.rb +76 -17
- data/lib/grape/util/deep_merge.rb +23 -0
- data/lib/grape/util/hash_stack.rb +12 -3
- data/lib/grape/validations.rb +202 -0
- data/lib/grape/validations/coerce.rb +61 -0
- data/lib/grape/validations/presence.rb +11 -0
- data/lib/grape/validations/regexp.rb +13 -0
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api_spec.rb +281 -123
- data/spec/grape/endpoint_spec.rb +69 -4
- data/spec/grape/entity_spec.rb +204 -16
- data/spec/grape/middleware/exception_spec.rb +21 -0
- data/spec/grape/middleware/formatter_spec.rb +19 -0
- data/spec/grape/middleware/versioner/header_spec.rb +159 -88
- data/spec/grape/validations/coerce_spec.rb +129 -0
- data/spec/grape/validations/presence_spec.rb +138 -0
- data/spec/grape/validations/regexp_spec.rb +33 -0
- data/spec/grape/validations_spec.rb +185 -0
- metadata +65 -74
- data/spec/grape_spec.rb +0 -1
@@ -0,0 +1,61 @@
|
|
1
|
+
module Grape
|
2
|
+
|
3
|
+
class API
|
4
|
+
Boolean = Virtus::Attribute::Boolean
|
5
|
+
end
|
6
|
+
|
7
|
+
module Validations
|
8
|
+
|
9
|
+
class CoerceValidator < SingleOptionValidator
|
10
|
+
def validate_param!(attr_name, params)
|
11
|
+
new_value = coerce_value(@option, params[attr_name])
|
12
|
+
if valid_type?(new_value)
|
13
|
+
params[attr_name] = new_value
|
14
|
+
else
|
15
|
+
raise ValidationError, :status => 400, :param => attr_name, :message => "invalid parameter: #{attr_name}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class InvalidValue; end
|
20
|
+
private
|
21
|
+
|
22
|
+
def _valid_array_type?(type, values)
|
23
|
+
values.all? do |val|
|
24
|
+
_valid_single_type?(type, val)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def _valid_single_type?(klass, val)
|
29
|
+
# allow nil, to ignore when a parameter is absent
|
30
|
+
return true if val.nil?
|
31
|
+
if klass == Virtus::Attribute::Boolean
|
32
|
+
val.is_a?(TrueClass) || val.is_a?(FalseClass)
|
33
|
+
elsif klass == Rack::Multipart::UploadedFile
|
34
|
+
val.is_a?(Hashie::Mash) && val.key?(:tempfile)
|
35
|
+
else
|
36
|
+
val.is_a?(klass)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def valid_type?(val)
|
41
|
+
if @option.is_a?(Array)
|
42
|
+
_valid_array_type?(@option[0], val)
|
43
|
+
else
|
44
|
+
_valid_single_type?(@option, val)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def coerce_value(type, val)
|
49
|
+
converter = Virtus::Attribute.build(:a, type)
|
50
|
+
converter.coerce(val)
|
51
|
+
|
52
|
+
# not the prettiest but some invalid coercion can currently trigger
|
53
|
+
# errors in Virtus (see coerce_spec.rb)
|
54
|
+
rescue
|
55
|
+
InvalidValue.new
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Grape
|
2
|
+
module Validations
|
3
|
+
class PresenceValidator < Validator
|
4
|
+
def validate_param!(attr_name, params)
|
5
|
+
unless params.has_key?(attr_name)
|
6
|
+
raise ValidationError, :status => 400, :param => attr_name, :message => "missing parameter: #{attr_name}"
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Grape
|
2
|
+
module Validations
|
3
|
+
|
4
|
+
class RegexpValidator < SingleOptionValidator
|
5
|
+
def validate_param!(attr_name, params)
|
6
|
+
if params[attr_name] && !( params[attr_name].to_s =~ @option )
|
7
|
+
raise ValidationError, :status => 400, :param => attr_name, :message => "invalid parameter: #{attr_name}"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
data/lib/grape/version.rb
CHANGED
data/spec/grape/api_spec.rb
CHANGED
@@ -21,6 +21,21 @@ describe Grape::API do
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
+
describe '.version' do
|
25
|
+
context 'when defined' do
|
26
|
+
it 'should return version value' do
|
27
|
+
subject.version 'v1'
|
28
|
+
subject.version.should == 'v1'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'when not defined' do
|
33
|
+
it 'should return nil' do
|
34
|
+
subject.version.should be_nil
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
24
39
|
describe '.version using path' do
|
25
40
|
it_should_behave_like 'versioning' do
|
26
41
|
let(:macro_options) do
|
@@ -68,7 +83,7 @@ describe Grape::API do
|
|
68
83
|
end
|
69
84
|
|
70
85
|
it 'should route if any media type is allowed' do
|
71
|
-
|
86
|
+
|
72
87
|
end
|
73
88
|
end
|
74
89
|
|
@@ -133,7 +148,7 @@ describe Grape::API do
|
|
133
148
|
get '/members/23'
|
134
149
|
last_response.body.should == "23"
|
135
150
|
end
|
136
|
-
|
151
|
+
|
137
152
|
it 'should be callable with nil just to push onto the stack' do
|
138
153
|
subject.namespace do
|
139
154
|
version 'v2', :using => :path
|
@@ -146,7 +161,7 @@ describe Grape::API do
|
|
146
161
|
get '/hello'
|
147
162
|
last_response.body.should == "outer"
|
148
163
|
end
|
149
|
-
|
164
|
+
|
150
165
|
%w(group resource resources segment).each do |als|
|
151
166
|
it "`.#{als}` should be an alias" do
|
152
167
|
subject.send(als, :awesome) do
|
@@ -191,7 +206,7 @@ describe Grape::API do
|
|
191
206
|
RSpec::Mocks::Mock.new(:to_json => 'abc', :to_txt => 'def')
|
192
207
|
end
|
193
208
|
end
|
194
|
-
|
209
|
+
|
195
210
|
it "should allow .json" do
|
196
211
|
get '/abc.json'
|
197
212
|
last_response.status.should == 200
|
@@ -245,7 +260,7 @@ describe Grape::API do
|
|
245
260
|
subject.route([:get, :post], '/:id/first') do
|
246
261
|
"first"
|
247
262
|
end
|
248
|
-
|
263
|
+
|
249
264
|
subject.route([:get, :post], '/:id') do
|
250
265
|
"ola"
|
251
266
|
end
|
@@ -286,8 +301,8 @@ describe Grape::API do
|
|
286
301
|
send(verb, '/example')
|
287
302
|
last_response.body.should eql verb == 'head' ? '' : verb
|
288
303
|
# Call it with a method other than the properly constrained one.
|
289
|
-
send(verbs[(verbs.index(verb) + 1) % verbs.size], '/example')
|
290
|
-
last_response.status.should eql
|
304
|
+
send(used_verb = verbs[(verbs.index(verb) + 1) % verbs.size], '/example')
|
305
|
+
last_response.status.should eql used_verb == 'options' ? 204 :405
|
291
306
|
end
|
292
307
|
end
|
293
308
|
|
@@ -300,6 +315,36 @@ describe Grape::API do
|
|
300
315
|
last_response.status.should eql 201
|
301
316
|
last_response.body.should eql 'Created'
|
302
317
|
end
|
318
|
+
|
319
|
+
it 'should return a 405 for an unsupported method' do
|
320
|
+
subject.get 'example' do
|
321
|
+
"example"
|
322
|
+
end
|
323
|
+
put '/example'
|
324
|
+
last_response.status.should eql 405
|
325
|
+
last_response.body.should eql ''
|
326
|
+
end
|
327
|
+
|
328
|
+
specify '405 responses should include an Allow header specifying supported methods' do
|
329
|
+
subject.get 'example' do
|
330
|
+
"example"
|
331
|
+
end
|
332
|
+
subject.post 'example' do
|
333
|
+
"example"
|
334
|
+
end
|
335
|
+
put '/example'
|
336
|
+
last_response.headers['Allow'].should eql 'OPTIONS, GET, POST'
|
337
|
+
end
|
338
|
+
|
339
|
+
it 'should add an OPTIONS route that returns a 204 and an Allow header' do
|
340
|
+
subject.get 'example' do
|
341
|
+
"example"
|
342
|
+
end
|
343
|
+
options '/example'
|
344
|
+
last_response.status.should eql 204
|
345
|
+
last_response.body.should eql ''
|
346
|
+
last_response.headers['Allow'].should eql 'OPTIONS, GET'
|
347
|
+
end
|
303
348
|
end
|
304
349
|
|
305
350
|
describe 'filters' do
|
@@ -314,6 +359,20 @@ describe Grape::API do
|
|
314
359
|
last_response.body.should eql 'first second'
|
315
360
|
end
|
316
361
|
|
362
|
+
it 'should add a after_validation filter' do
|
363
|
+
subject.after_validation { @foo = "first #{params[:id]}:#{params[:id].class}" }
|
364
|
+
subject.after_validation { @bar = 'second' }
|
365
|
+
subject.params do
|
366
|
+
requires :id, :type => Integer
|
367
|
+
end
|
368
|
+
subject.get '/' do
|
369
|
+
"#{@foo} #{@bar}"
|
370
|
+
end
|
371
|
+
|
372
|
+
get '/', :id => "32"
|
373
|
+
last_response.body.should eql 'first 32:Fixnum second'
|
374
|
+
end
|
375
|
+
|
317
376
|
it 'should add a after filter' do
|
318
377
|
m = double('after mock')
|
319
378
|
subject.after { m.do_something! }
|
@@ -356,65 +415,67 @@ describe Grape::API do
|
|
356
415
|
last_response.headers['Content-Type'].should eql 'application/json'
|
357
416
|
end
|
358
417
|
end
|
359
|
-
|
418
|
+
|
360
419
|
context 'custom middleware' do
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
420
|
+
module ApiSpec
|
421
|
+
class PhonyMiddleware
|
422
|
+
def initialize(app, *args)
|
423
|
+
@args = args
|
424
|
+
@app = app
|
425
|
+
@block = true if block_given?
|
426
|
+
end
|
367
427
|
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
428
|
+
def call(env)
|
429
|
+
env['phony.args'] ||= []
|
430
|
+
env['phony.args'] << @args
|
431
|
+
env['phony.block'] = true if @block
|
432
|
+
@app.call(env)
|
433
|
+
end
|
373
434
|
end
|
374
435
|
end
|
375
436
|
|
376
437
|
describe '.middleware' do
|
377
438
|
it 'should include middleware arguments from settings' do
|
378
439
|
settings = Grape::Util::HashStack.new
|
379
|
-
settings.stub!(:stack).and_return([{:middleware => [[PhonyMiddleware, 'abc', 123]]}])
|
440
|
+
settings.stub!(:stack).and_return([{:middleware => [[ApiSpec::PhonyMiddleware, 'abc', 123]]}])
|
380
441
|
subject.stub!(:settings).and_return(settings)
|
381
|
-
subject.middleware.should eql [[PhonyMiddleware, 'abc', 123]]
|
442
|
+
subject.middleware.should eql [[ApiSpec::PhonyMiddleware, 'abc', 123]]
|
382
443
|
end
|
383
444
|
|
384
445
|
it 'should include all middleware from stacked settings' do
|
385
446
|
settings = Grape::Util::HashStack.new
|
386
447
|
settings.stub!(:stack).and_return [
|
387
|
-
{:middleware => [[PhonyMiddleware, 123],[PhonyMiddleware, 'abc']]},
|
388
|
-
{:middleware => [[PhonyMiddleware, 'foo']]}
|
448
|
+
{:middleware => [[ApiSpec::PhonyMiddleware, 123],[ApiSpec::PhonyMiddleware, 'abc']]},
|
449
|
+
{:middleware => [[ApiSpec::PhonyMiddleware, 'foo']]}
|
389
450
|
]
|
390
451
|
subject.stub!(:settings).and_return(settings)
|
391
|
-
|
452
|
+
|
392
453
|
subject.middleware.should eql [
|
393
|
-
[PhonyMiddleware, 123],
|
394
|
-
[PhonyMiddleware, 'abc'],
|
395
|
-
[PhonyMiddleware, 'foo']
|
454
|
+
[ApiSpec::PhonyMiddleware, 123],
|
455
|
+
[ApiSpec::PhonyMiddleware, 'abc'],
|
456
|
+
[ApiSpec::PhonyMiddleware, 'foo']
|
396
457
|
]
|
397
458
|
end
|
398
459
|
end
|
399
460
|
|
400
461
|
describe '.use' do
|
401
462
|
it 'should add middleware' do
|
402
|
-
subject.use PhonyMiddleware, 123
|
403
|
-
subject.middleware.should eql [[PhonyMiddleware, 123]]
|
463
|
+
subject.use ApiSpec::PhonyMiddleware, 123
|
464
|
+
subject.middleware.should eql [[ApiSpec::PhonyMiddleware, 123]]
|
404
465
|
end
|
405
466
|
|
406
467
|
it 'should not show up outside the namespace' do
|
407
|
-
subject.use PhonyMiddleware, 123
|
468
|
+
subject.use ApiSpec::PhonyMiddleware, 123
|
408
469
|
subject.namespace :awesome do
|
409
|
-
use PhonyMiddleware, 'abc'
|
410
|
-
middleware.should == [[PhonyMiddleware, 123],[PhonyMiddleware, 'abc']]
|
470
|
+
use ApiSpec::PhonyMiddleware, 'abc'
|
471
|
+
middleware.should == [[ApiSpec::PhonyMiddleware, 123],[ApiSpec::PhonyMiddleware, 'abc']]
|
411
472
|
end
|
412
473
|
|
413
|
-
subject.middleware.should eql [[PhonyMiddleware, 123]]
|
474
|
+
subject.middleware.should eql [[ApiSpec::PhonyMiddleware, 123]]
|
414
475
|
end
|
415
476
|
|
416
477
|
it 'should actually call the middleware' do
|
417
|
-
subject.use PhonyMiddleware, 'hello'
|
478
|
+
subject.use ApiSpec::PhonyMiddleware, 'hello'
|
418
479
|
subject.get '/' do
|
419
480
|
env['phony.args'].first.first
|
420
481
|
end
|
@@ -425,13 +486,13 @@ describe Grape::API do
|
|
425
486
|
|
426
487
|
it 'should add a block if one is given' do
|
427
488
|
block = lambda{ }
|
428
|
-
subject.use PhonyMiddleware, &block
|
429
|
-
subject.middleware.should eql [[PhonyMiddleware, block]]
|
489
|
+
subject.use ApiSpec::PhonyMiddleware, &block
|
490
|
+
subject.middleware.should eql [[ApiSpec::PhonyMiddleware, block]]
|
430
491
|
end
|
431
492
|
|
432
493
|
it 'should use a block if one is given' do
|
433
494
|
block = lambda{ }
|
434
|
-
subject.use PhonyMiddleware, &block
|
495
|
+
subject.use ApiSpec::PhonyMiddleware, &block
|
435
496
|
subject.get '/' do
|
436
497
|
env['phony.block'].inspect
|
437
498
|
end
|
@@ -442,7 +503,7 @@ describe Grape::API do
|
|
442
503
|
|
443
504
|
it 'should not destroy the middleware settings on multiple runs' do
|
444
505
|
block = lambda{ }
|
445
|
-
subject.use PhonyMiddleware, &block
|
506
|
+
subject.use ApiSpec::PhonyMiddleware, &block
|
446
507
|
subject.get '/' do
|
447
508
|
env['phony.block'].inspect
|
448
509
|
end
|
@@ -628,7 +689,7 @@ describe Grape::API do
|
|
628
689
|
subject.get '/def' do
|
629
690
|
'def'
|
630
691
|
end
|
631
|
-
|
692
|
+
|
632
693
|
get '/new/abc'
|
633
694
|
last_response.status.should eql 404
|
634
695
|
get '/legacy/abc'
|
@@ -667,6 +728,97 @@ describe Grape::API do
|
|
667
728
|
|
668
729
|
lambda{ get '/unrescued' }.should raise_error
|
669
730
|
end
|
731
|
+
|
732
|
+
it 'should not re-raise exceptions of type Grape::Exception::Base' do
|
733
|
+
class CustomError < Grape::Exceptions::Base; end
|
734
|
+
subject.get('/custom_exception'){ raise CustomError }
|
735
|
+
|
736
|
+
lambda{ get '/custom_exception' }.should_not raise_error
|
737
|
+
end
|
738
|
+
|
739
|
+
it 'should rescue custom grape exceptions' do
|
740
|
+
class CustomError < Grape::Exceptions::Base; end
|
741
|
+
subject.rescue_from CustomError do |e|
|
742
|
+
rack_response('New Error', e.status)
|
743
|
+
end
|
744
|
+
subject.get '/custom_error' do
|
745
|
+
raise CustomError, :status => 400, :message => 'Custom Error'
|
746
|
+
end
|
747
|
+
|
748
|
+
get '/custom_error'
|
749
|
+
last_response.status.should == 400
|
750
|
+
last_response.body.should == 'New Error'
|
751
|
+
end
|
752
|
+
end
|
753
|
+
|
754
|
+
describe ".rescue_from klass, block" do
|
755
|
+
it 'should rescue Exception' do
|
756
|
+
subject.rescue_from RuntimeError do |e|
|
757
|
+
rack_response("rescued from #{e.message}", 202)
|
758
|
+
end
|
759
|
+
subject.get '/exception' do
|
760
|
+
raise "rain!"
|
761
|
+
end
|
762
|
+
get '/exception'
|
763
|
+
last_response.status.should eql 202
|
764
|
+
last_response.body.should == 'rescued from rain!'
|
765
|
+
end
|
766
|
+
it 'should rescue an error via rescue_from :all' do
|
767
|
+
class ConnectionError < RuntimeError; end
|
768
|
+
subject.rescue_from :all do |e|
|
769
|
+
rack_response("rescued from #{e.class.name}", 500)
|
770
|
+
end
|
771
|
+
subject.get '/exception' do
|
772
|
+
raise ConnectionError
|
773
|
+
end
|
774
|
+
get '/exception'
|
775
|
+
last_response.status.should eql 500
|
776
|
+
last_response.body.should == 'rescued from ConnectionError'
|
777
|
+
end
|
778
|
+
it 'should rescue a specific error' do
|
779
|
+
class ConnectionError < RuntimeError; end
|
780
|
+
subject.rescue_from ConnectionError do |e|
|
781
|
+
rack_response("rescued from #{e.class.name}", 500)
|
782
|
+
end
|
783
|
+
subject.get '/exception' do
|
784
|
+
raise ConnectionError
|
785
|
+
end
|
786
|
+
get '/exception'
|
787
|
+
last_response.status.should eql 500
|
788
|
+
last_response.body.should == 'rescued from ConnectionError'
|
789
|
+
end
|
790
|
+
it 'should rescue multiple specific errors' do
|
791
|
+
class ConnectionError < RuntimeError; end
|
792
|
+
class DatabaseError < RuntimeError; end
|
793
|
+
subject.rescue_from ConnectionError do |e|
|
794
|
+
rack_response("rescued from #{e.class.name}", 500)
|
795
|
+
end
|
796
|
+
subject.rescue_from DatabaseError do |e|
|
797
|
+
rack_response("rescued from #{e.class.name}", 500)
|
798
|
+
end
|
799
|
+
subject.get '/connection' do
|
800
|
+
raise ConnectionError
|
801
|
+
end
|
802
|
+
subject.get '/database' do
|
803
|
+
raise DatabaseError
|
804
|
+
end
|
805
|
+
get '/connection'
|
806
|
+
last_response.status.should eql 500
|
807
|
+
last_response.body.should == 'rescued from ConnectionError'
|
808
|
+
get '/database'
|
809
|
+
last_response.status.should eql 500
|
810
|
+
last_response.body.should == 'rescued from DatabaseError'
|
811
|
+
end
|
812
|
+
it 'should not rescue a different error' do
|
813
|
+
class CommunicationError < RuntimeError; end
|
814
|
+
subject.rescue_from RuntimeError do |e|
|
815
|
+
rack_response("rescued from #{e.class.name}", 500)
|
816
|
+
end
|
817
|
+
subject.get '/uncaught' do
|
818
|
+
raise CommunicationError
|
819
|
+
end
|
820
|
+
lambda { get '/uncaught' }.should raise_error(CommunicationError)
|
821
|
+
end
|
670
822
|
end
|
671
823
|
|
672
824
|
describe ".error_format" do
|
@@ -766,19 +918,7 @@ describe Grape::API do
|
|
766
918
|
last_response.status.should eql 403
|
767
919
|
end
|
768
920
|
end
|
769
|
-
|
770
|
-
context "muti_xml" do
|
771
|
-
it "doesn't parse yaml" do
|
772
|
-
subject.put :yaml do
|
773
|
-
params[:tag]
|
774
|
-
end
|
775
921
|
|
776
|
-
expect {
|
777
|
-
put '/yaml', '<tag type="symbol">a123</tag>', "CONTENT_TYPE" => "application/xml"
|
778
|
-
}.to raise_error(MultiXml::DisallowedTypeError)
|
779
|
-
end
|
780
|
-
end
|
781
|
-
|
782
922
|
context "routes" do
|
783
923
|
describe "empty api structure" do
|
784
924
|
it "returns an empty array of routes" do
|
@@ -816,6 +956,9 @@ describe Grape::API do
|
|
816
956
|
end
|
817
957
|
end
|
818
958
|
end
|
959
|
+
it "should return the latest version set" do
|
960
|
+
subject.version.should == 'v2'
|
961
|
+
end
|
819
962
|
it "should return versions" do
|
820
963
|
subject.versions.should == [ 'v1', 'v2' ]
|
821
964
|
end
|
@@ -933,99 +1076,99 @@ describe Grape::API do
|
|
933
1076
|
{ :description => "Reverses a string.", :params => { "s" => { :desc => "string to reverse", :type => "string" } } }
|
934
1077
|
]
|
935
1078
|
end
|
936
|
-
it "should
|
937
|
-
subject.desc "
|
938
|
-
|
939
|
-
|
940
|
-
|
941
|
-
|
1079
|
+
it "should merge the parameters of the namespace with the parameters of the method" do
|
1080
|
+
subject.desc "namespace"
|
1081
|
+
subject.params do
|
1082
|
+
requires :ns_param, :desc => "namespace parameter"
|
1083
|
+
end
|
1084
|
+
subject.namespace "ns" do
|
1085
|
+
desc "method"
|
1086
|
+
params do
|
1087
|
+
optional :method_param, :desc => "method parameter"
|
1088
|
+
end
|
1089
|
+
get "method" do ; end
|
942
1090
|
end
|
943
1091
|
subject.routes.map { |route|
|
944
1092
|
{ :description => route.route_description, :params => route.route_params }
|
945
1093
|
}.should eq [
|
946
|
-
{ :description => "
|
1094
|
+
{ :description => "method", :params => { "ns_param" => { :required => true, :desc => "namespace parameter", :full_name=>"ns_param" }, "method_param" => { :required => false, :desc => "method parameter", :full_name=>"method_param" } } }
|
947
1095
|
]
|
948
1096
|
end
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
|
959
|
-
|
960
|
-
|
961
|
-
|
962
|
-
|
963
|
-
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
|
968
|
-
subject.get '/exception' do
|
969
|
-
raise ConnectionError
|
1097
|
+
it "should merge the parameters of nested namespaces" do
|
1098
|
+
subject.desc "ns1"
|
1099
|
+
subject.params do
|
1100
|
+
optional :ns_param, :desc => "ns param 1"
|
1101
|
+
requires :ns1_param, :desc => "ns1 param"
|
1102
|
+
end
|
1103
|
+
subject.namespace "ns1" do
|
1104
|
+
desc "ns2"
|
1105
|
+
params do
|
1106
|
+
requires :ns_param, :desc => "ns param 2"
|
1107
|
+
requires :ns2_param, :desc => "ns2 param"
|
1108
|
+
end
|
1109
|
+
namespace "ns2" do
|
1110
|
+
desc "method"
|
1111
|
+
params do
|
1112
|
+
optional :method_param, :desc => "method param"
|
1113
|
+
end
|
1114
|
+
get "method" do ; end
|
1115
|
+
end
|
970
1116
|
end
|
971
|
-
|
972
|
-
|
973
|
-
|
1117
|
+
subject.routes.map { |route|
|
1118
|
+
{ :description => route.route_description, :params => route.route_params }
|
1119
|
+
}.should eq [
|
1120
|
+
{ :description => "method", :params => { "ns_param" => { :required => true, :desc => "ns param 2", :full_name=>"ns_param" }, "ns1_param" => { :required => true, :desc => "ns1 param", :full_name=>"ns1_param" }, "ns2_param" => { :required => true, :desc => "ns2 param", :full_name=>"ns2_param" }, "method_param" => { :required => false, :desc => "method param", :full_name=>"method_param" } } }
|
1121
|
+
]
|
974
1122
|
end
|
975
|
-
it
|
976
|
-
|
977
|
-
subject.
|
978
|
-
|
979
|
-
|
980
|
-
|
981
|
-
|
1123
|
+
it "should provide a full_name for parameters in nested groups" do
|
1124
|
+
subject.desc "nesting"
|
1125
|
+
subject.params do
|
1126
|
+
requires :root_param, :desc => "root param"
|
1127
|
+
group :nested do
|
1128
|
+
requires :nested_param, :desc => "nested param"
|
1129
|
+
end
|
982
1130
|
end
|
983
|
-
get
|
984
|
-
|
985
|
-
|
1131
|
+
subject.get "method" do ; end
|
1132
|
+
subject.routes.map { |route|
|
1133
|
+
{ :description => route.route_description, :params => route.route_params }
|
1134
|
+
}.should eq [
|
1135
|
+
{ :description => "nesting", :params => { "root_param" => { :required => true, :desc => "root param", :full_name=>"root_param" }, "nested_param" => { :required => true, :desc => "nested param", :full_name=>"nested[nested_param]" } } }
|
1136
|
+
]
|
986
1137
|
end
|
987
|
-
it
|
988
|
-
|
989
|
-
|
990
|
-
subject.rescue_from ConnectionError do |e|
|
991
|
-
rack_response("rescued from #{e.class.name}", 500)
|
992
|
-
end
|
993
|
-
subject.rescue_from DatabaseError do |e|
|
994
|
-
rack_response("rescued from #{e.class.name}", 500)
|
995
|
-
end
|
996
|
-
subject.get '/connection' do
|
997
|
-
raise ConnectionError
|
1138
|
+
it "should parse parameters when no description is given" do
|
1139
|
+
subject.params do
|
1140
|
+
requires :one_param, :desc => "one param"
|
998
1141
|
end
|
999
|
-
subject.get
|
1000
|
-
|
1001
|
-
|
1002
|
-
|
1003
|
-
|
1004
|
-
|
1005
|
-
get '/database'
|
1006
|
-
last_response.status.should eql 500
|
1007
|
-
last_response.body.should == 'rescued from DatabaseError'
|
1142
|
+
subject.get "method" do ; end
|
1143
|
+
subject.routes.map { |route|
|
1144
|
+
{ :description => route.route_description, :params => route.route_params }
|
1145
|
+
}.should eq [
|
1146
|
+
{ :description => nil, :params => { "one_param" => { :required => true, :desc => "one param", :full_name=>"one_param" } } }
|
1147
|
+
]
|
1008
1148
|
end
|
1009
|
-
it
|
1010
|
-
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
1015
|
-
raise CommunicationError
|
1149
|
+
it "should not symbolize params" do
|
1150
|
+
subject.desc "Reverses a string.", { :params =>
|
1151
|
+
{ "s" => { :desc => "string to reverse", :type => "string" }}
|
1152
|
+
}
|
1153
|
+
subject.get "reverse/:s" do
|
1154
|
+
params[:s].reverse
|
1016
1155
|
end
|
1017
|
-
|
1156
|
+
subject.routes.map { |route|
|
1157
|
+
{ :description => route.route_description, :params => route.route_params }
|
1158
|
+
}.should eq [
|
1159
|
+
{ :description => "Reverses a string.", :params => { "s" => { :desc => "string to reverse", :type => "string" } } }
|
1160
|
+
]
|
1018
1161
|
end
|
1019
1162
|
end
|
1020
1163
|
|
1021
1164
|
describe '.mount' do
|
1022
1165
|
let(:mounted_app){ lambda{|env| [200, {}, ["MOUNTED"]]} }
|
1023
|
-
|
1166
|
+
|
1024
1167
|
context 'with a bare rack app' do
|
1025
1168
|
before do
|
1026
1169
|
subject.mount mounted_app => '/mounty'
|
1027
1170
|
end
|
1028
|
-
|
1171
|
+
|
1029
1172
|
it 'should make a bare Rack app available at the endpoint' do
|
1030
1173
|
get '/mounty'
|
1031
1174
|
last_response.body.should == 'MOUNTED'
|
@@ -1073,6 +1216,21 @@ describe Grape::API do
|
|
1073
1216
|
get '/v1/cool/awesome'
|
1074
1217
|
last_response.body.should == 'yo'
|
1075
1218
|
end
|
1219
|
+
|
1220
|
+
it 'should inherit rescues even when some defined by mounted' do
|
1221
|
+
subject.rescue_from :all do |e|
|
1222
|
+
rack_response("rescued from #{e.message}", 202)
|
1223
|
+
end
|
1224
|
+
subject.namespace :mounted do
|
1225
|
+
app = Class.new(Grape::API)
|
1226
|
+
app.rescue_from ArgumentError
|
1227
|
+
app.get('/fail') { raise "doh!" }
|
1228
|
+
mount app
|
1229
|
+
end
|
1230
|
+
get '/mounted/fail'
|
1231
|
+
last_response.status.should eql 202
|
1232
|
+
last_response.body.should == 'rescued from doh!'
|
1233
|
+
end
|
1076
1234
|
end
|
1077
1235
|
end
|
1078
1236
|
|
@@ -1099,7 +1257,7 @@ describe Grape::API do
|
|
1099
1257
|
subject.instance.should be_nil
|
1100
1258
|
end
|
1101
1259
|
end
|
1102
|
-
|
1260
|
+
|
1103
1261
|
describe ".route" do
|
1104
1262
|
context "plain" do
|
1105
1263
|
before(:each) do
|