grape 0.6.1 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of grape might be problematic. Click here for more details.
- checksums.yaml +9 -9
- data/.gitignore +7 -0
- data/.rubocop.yml +5 -0
- data/.travis.yml +3 -3
- data/CHANGELOG.md +42 -3
- data/CONTRIBUTING.md +118 -0
- data/Gemfile +4 -4
- data/README.md +312 -52
- data/Rakefile +6 -1
- data/UPGRADING.md +124 -0
- data/lib/grape.rb +2 -0
- data/lib/grape/api.rb +95 -44
- data/lib/grape/cookies.rb +0 -2
- data/lib/grape/endpoint.rb +63 -39
- data/lib/grape/error_formatter/base.rb +0 -3
- data/lib/grape/error_formatter/json.rb +0 -2
- data/lib/grape/error_formatter/txt.rb +0 -2
- data/lib/grape/error_formatter/xml.rb +0 -2
- data/lib/grape/exceptions/base.rb +0 -2
- data/lib/grape/exceptions/incompatible_option_values.rb +0 -3
- data/lib/grape/exceptions/invalid_formatter.rb +0 -3
- data/lib/grape/exceptions/invalid_versioner_option.rb +0 -4
- data/lib/grape/exceptions/invalid_with_option_for_represent.rb +0 -2
- data/lib/grape/exceptions/missing_mime_type.rb +0 -4
- data/lib/grape/exceptions/missing_option.rb +0 -3
- data/lib/grape/exceptions/missing_vendor_option.rb +0 -3
- data/lib/grape/exceptions/unknown_options.rb +0 -4
- data/lib/grape/exceptions/unknown_validator.rb +0 -2
- data/lib/grape/exceptions/validation_errors.rb +6 -5
- data/lib/grape/formatter/base.rb +0 -3
- data/lib/grape/formatter/json.rb +0 -2
- data/lib/grape/formatter/serializable_hash.rb +15 -16
- data/lib/grape/formatter/txt.rb +0 -2
- data/lib/grape/formatter/xml.rb +0 -2
- data/lib/grape/http/request.rb +2 -4
- data/lib/grape/locale/en.yml +1 -1
- data/lib/grape/middleware/auth/oauth2.rb +15 -6
- data/lib/grape/middleware/base.rb +7 -7
- data/lib/grape/middleware/error.rb +11 -6
- data/lib/grape/middleware/formatter.rb +80 -78
- data/lib/grape/middleware/globals.rb +13 -0
- data/lib/grape/middleware/versioner/accept_version_header.rb +0 -2
- data/lib/grape/middleware/versioner/header.rb +5 -3
- data/lib/grape/middleware/versioner/param.rb +2 -4
- data/lib/grape/middleware/versioner/path.rb +3 -4
- data/lib/grape/namespace.rb +0 -1
- data/lib/grape/parser/base.rb +0 -3
- data/lib/grape/parser/json.rb +0 -2
- data/lib/grape/parser/xml.rb +0 -2
- data/lib/grape/path.rb +1 -3
- data/lib/grape/route.rb +0 -3
- data/lib/grape/util/hash_stack.rb +1 -1
- data/lib/grape/validations.rb +72 -22
- data/lib/grape/validations/coerce.rb +5 -4
- data/lib/grape/validations/default.rb +5 -3
- data/lib/grape/validations/presence.rb +1 -1
- data/lib/grape/validations/regexp.rb +0 -2
- data/lib/grape/validations/values.rb +2 -1
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api_spec.rb +385 -96
- data/spec/grape/endpoint_spec.rb +162 -15
- data/spec/grape/entity_spec.rb +25 -0
- data/spec/grape/exceptions/validation_errors_spec.rb +19 -0
- data/spec/grape/middleware/auth/oauth2_spec.rb +60 -15
- data/spec/grape/middleware/base_spec.rb +3 -8
- data/spec/grape/middleware/error_spec.rb +2 -2
- data/spec/grape/middleware/exception_spec.rb +4 -4
- data/spec/grape/middleware/formatter_spec.rb +7 -4
- data/spec/grape/middleware/versioner/param_spec.rb +8 -7
- data/spec/grape/path_spec.rb +24 -14
- data/spec/grape/util/hash_stack_spec.rb +8 -8
- data/spec/grape/validations/coerce_spec.rb +75 -33
- data/spec/grape/validations/default_spec.rb +57 -0
- data/spec/grape/validations/presence_spec.rb +13 -11
- data/spec/grape/validations/values_spec.rb +76 -2
- data/spec/grape/validations_spec.rb +443 -20
- data/spec/spec_helper.rb +2 -2
- data/spec/support/content_type_helpers.rb +11 -0
- metadata +9 -38
@@ -1,13 +1,12 @@
|
|
1
1
|
module Grape
|
2
|
-
|
3
2
|
class API
|
4
3
|
Boolean = Virtus::Attribute::Boolean # rubocop:disable ConstantName
|
5
4
|
end
|
6
5
|
|
7
6
|
module Validations
|
8
|
-
|
9
7
|
class CoerceValidator < SingleOptionValidator
|
10
8
|
def validate_param!(attr_name, params)
|
9
|
+
raise Grape::Exceptions::Validation, param: @scope.full_name(attr_name), message_key: :coerce unless params.is_a? Hash
|
11
10
|
new_value = coerce_value(@option, params[attr_name])
|
12
11
|
if valid_type?(new_value)
|
13
12
|
params[attr_name] = new_value
|
@@ -47,6 +46,10 @@ module Grape
|
|
47
46
|
end
|
48
47
|
|
49
48
|
def coerce_value(type, val)
|
49
|
+
# Don't coerce things other than nil to Arrays or Hashes
|
50
|
+
return val || [] if type == Array
|
51
|
+
return val || {} if type == Hash
|
52
|
+
|
50
53
|
converter = Virtus::Attribute.build(type)
|
51
54
|
converter.coerce(val)
|
52
55
|
|
@@ -55,8 +58,6 @@ module Grape
|
|
55
58
|
rescue
|
56
59
|
InvalidValue.new
|
57
60
|
end
|
58
|
-
|
59
61
|
end
|
60
|
-
|
61
62
|
end
|
62
63
|
end
|
@@ -7,14 +7,16 @@ module Grape
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def validate_param!(attr_name, params)
|
10
|
-
params[attr_name] = @default unless params.has_key?(attr_name)
|
10
|
+
params[attr_name] = @default.is_a?(Proc) ? @default.call : @default unless params.has_key?(attr_name)
|
11
11
|
end
|
12
12
|
|
13
13
|
def validate!(params)
|
14
|
-
|
15
|
-
|
14
|
+
attrs = AttributesIterator.new(self, @scope, params)
|
15
|
+
parent_element = @scope.element
|
16
|
+
attrs.each do |resource_params, attr_name|
|
16
17
|
if resource_params[attr_name].nil?
|
17
18
|
validate_param!(attr_name, resource_params)
|
19
|
+
params[parent_element] = resource_params if parent_element && params[parent_element].nil?
|
18
20
|
end
|
19
21
|
end
|
20
22
|
end
|
@@ -7,7 +7,7 @@ module Grape
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def validate_param!(attr_name, params)
|
10
|
-
unless params.has_key?(attr_name)
|
10
|
+
unless params.respond_to?(:has_key?) && params.has_key?(attr_name)
|
11
11
|
raise Grape::Exceptions::Validation, param: @scope.full_name(attr_name), message_key: :presence
|
12
12
|
end
|
13
13
|
end
|
@@ -3,11 +3,12 @@ module Grape
|
|
3
3
|
class ValuesValidator < Validator
|
4
4
|
def initialize(attrs, options, required, scope)
|
5
5
|
@values = options
|
6
|
+
@required = required
|
6
7
|
super
|
7
8
|
end
|
8
9
|
|
9
10
|
def validate_param!(attr_name, params)
|
10
|
-
if params[attr_name] &&
|
11
|
+
if (params[attr_name] || @required) && !(@values.is_a?(Proc) ? @values.call : @values).include?(params[attr_name])
|
11
12
|
raise Grape::Exceptions::Validation, param: @scope.full_name(attr_name), message_key: :values
|
12
13
|
end
|
13
14
|
end
|
data/lib/grape/version.rb
CHANGED
data/spec/grape/api_spec.rb
CHANGED
@@ -383,7 +383,7 @@ describe Grape::API do
|
|
383
383
|
subject.send(verb) do
|
384
384
|
env['api.request.body']
|
385
385
|
end
|
386
|
-
send verb, '/', MultiJson.dump(object),
|
386
|
+
send verb, '/', MultiJson.dump(object), 'CONTENT_TYPE' => 'application/json'
|
387
387
|
last_response.status.should == (verb == :post ? 201 : 200)
|
388
388
|
last_response.body.should eql MultiJson.dump(object)
|
389
389
|
last_request.params.should eql Hash.new
|
@@ -393,7 +393,7 @@ describe Grape::API do
|
|
393
393
|
subject.send(verb) do
|
394
394
|
env['api.request.input']
|
395
395
|
end
|
396
|
-
send verb, '/', MultiJson.dump(object),
|
396
|
+
send verb, '/', MultiJson.dump(object), 'CONTENT_TYPE' => 'application/json'
|
397
397
|
last_response.status.should == (verb == :post ? 201 : 200)
|
398
398
|
last_response.body.should eql MultiJson.dump(object).to_json
|
399
399
|
end
|
@@ -403,7 +403,7 @@ describe Grape::API do
|
|
403
403
|
subject.send(verb) do
|
404
404
|
env['api.request.input']
|
405
405
|
end
|
406
|
-
send verb, '/', MultiJson.dump(object),
|
406
|
+
send verb, '/', MultiJson.dump(object), 'CONTENT_TYPE' => 'application/json', 'HTTP_TRANSFER_ENCODING' => 'chunked', 'CONTENT_LENGTH' => nil
|
407
407
|
last_response.status.should == (verb == :post ? 201 : 200)
|
408
408
|
last_response.body.should eql MultiJson.dump(object).to_json
|
409
409
|
end
|
@@ -473,13 +473,15 @@ describe Grape::API do
|
|
473
473
|
last_response.body.should eql 'Created'
|
474
474
|
end
|
475
475
|
|
476
|
-
it 'returns a 405 for an unsupported method' do
|
476
|
+
it 'returns a 405 for an unsupported method with an X-Custom-Header' do
|
477
|
+
subject.before { header 'X-Custom-Header', 'foo' }
|
477
478
|
subject.get 'example' do
|
478
479
|
"example"
|
479
480
|
end
|
480
481
|
put '/example'
|
481
482
|
last_response.status.should eql 405
|
482
483
|
last_response.body.should eql ''
|
484
|
+
last_response.headers['X-Custom-Header'].should eql 'foo'
|
483
485
|
end
|
484
486
|
|
485
487
|
specify '405 responses includes an Allow header specifying supported methods' do
|
@@ -504,7 +506,8 @@ describe Grape::API do
|
|
504
506
|
last_response.headers['Content-Type'].should eql 'text/plain'
|
505
507
|
end
|
506
508
|
|
507
|
-
it 'adds an OPTIONS route that returns a 204
|
509
|
+
it 'adds an OPTIONS route that returns a 204, an Allow header and a X-Custom-Header' do
|
510
|
+
subject.before { header 'X-Custom-Header', 'foo' }
|
508
511
|
subject.get 'example' do
|
509
512
|
"example"
|
510
513
|
end
|
@@ -512,6 +515,7 @@ describe Grape::API do
|
|
512
515
|
last_response.status.should eql 204
|
513
516
|
last_response.body.should eql ''
|
514
517
|
last_response.headers['Allow'].should eql 'OPTIONS, GET, HEAD'
|
518
|
+
last_response.headers['X-Custom-Header'].should eql 'foo'
|
515
519
|
end
|
516
520
|
|
517
521
|
it 'allows HEAD on a GET request' do
|
@@ -579,6 +583,31 @@ describe Grape::API do
|
|
579
583
|
last_response.body.should eql 'first second'
|
580
584
|
end
|
581
585
|
|
586
|
+
it 'adds a before filter to current and child namespaces only' do
|
587
|
+
subject.get '/' do
|
588
|
+
"root - #{@foo}"
|
589
|
+
end
|
590
|
+
subject.namespace :blah do
|
591
|
+
before { @foo = 'foo' }
|
592
|
+
get '/' do
|
593
|
+
"blah - #{@foo}"
|
594
|
+
end
|
595
|
+
|
596
|
+
namespace :bar do
|
597
|
+
get '/' do
|
598
|
+
"blah - bar - #{@foo}"
|
599
|
+
end
|
600
|
+
end
|
601
|
+
end
|
602
|
+
|
603
|
+
get '/'
|
604
|
+
last_response.body.should eql 'root - '
|
605
|
+
get '/blah'
|
606
|
+
last_response.body.should eql 'blah - foo'
|
607
|
+
get '/blah/bar'
|
608
|
+
last_response.body.should eql 'blah - bar - foo'
|
609
|
+
end
|
610
|
+
|
582
611
|
it 'adds a after_validation filter' do
|
583
612
|
subject.after_validation { @foo = "first #{params[:id] }:#{params[:id].class}" }
|
584
613
|
subject.after_validation { @bar = 'second' }
|
@@ -605,6 +634,94 @@ describe Grape::API do
|
|
605
634
|
get '/'
|
606
635
|
last_response.body.should eql 'default'
|
607
636
|
end
|
637
|
+
|
638
|
+
it 'calls all filters when validation passes' do
|
639
|
+
a = double('before mock')
|
640
|
+
b = double('before_validation mock')
|
641
|
+
c = double('after_validation mock')
|
642
|
+
d = double('after mock')
|
643
|
+
|
644
|
+
subject.params do
|
645
|
+
requires :id, type: Integer
|
646
|
+
end
|
647
|
+
subject.resource ':id' do
|
648
|
+
before { a.do_something! }
|
649
|
+
before_validation { b.do_something! }
|
650
|
+
after_validation { c.do_something! }
|
651
|
+
after { d.do_something! }
|
652
|
+
get do
|
653
|
+
'got it'
|
654
|
+
end
|
655
|
+
end
|
656
|
+
|
657
|
+
a.should_receive(:do_something!).exactly(1).times
|
658
|
+
b.should_receive(:do_something!).exactly(1).times
|
659
|
+
c.should_receive(:do_something!).exactly(1).times
|
660
|
+
d.should_receive(:do_something!).exactly(1).times
|
661
|
+
|
662
|
+
get '/123'
|
663
|
+
last_response.status.should eql 200
|
664
|
+
last_response.body.should eql 'got it'
|
665
|
+
end
|
666
|
+
|
667
|
+
it 'calls only before filters when validation fails' do
|
668
|
+
a = double('before mock')
|
669
|
+
b = double('before_validation mock')
|
670
|
+
c = double('after_validation mock')
|
671
|
+
d = double('after mock')
|
672
|
+
|
673
|
+
subject.params do
|
674
|
+
requires :id, type: Integer
|
675
|
+
end
|
676
|
+
subject.resource ':id' do
|
677
|
+
before { a.do_something! }
|
678
|
+
before_validation { b.do_something! }
|
679
|
+
after_validation { c.do_something! }
|
680
|
+
after { d.do_something! }
|
681
|
+
get do
|
682
|
+
'got it'
|
683
|
+
end
|
684
|
+
end
|
685
|
+
|
686
|
+
a.should_receive(:do_something!).exactly(1).times
|
687
|
+
b.should_receive(:do_something!).exactly(1).times
|
688
|
+
c.should_receive(:do_something!).exactly(0).times
|
689
|
+
d.should_receive(:do_something!).exactly(0).times
|
690
|
+
|
691
|
+
get '/abc'
|
692
|
+
last_response.status.should eql 400
|
693
|
+
last_response.body.should eql 'id is invalid'
|
694
|
+
end
|
695
|
+
|
696
|
+
it 'calls filters in the correct order' do
|
697
|
+
i = 0
|
698
|
+
a = double('before mock')
|
699
|
+
b = double('before_validation mock')
|
700
|
+
c = double('after_validation mock')
|
701
|
+
d = double('after mock')
|
702
|
+
|
703
|
+
subject.params do
|
704
|
+
requires :id, type: Integer
|
705
|
+
end
|
706
|
+
subject.resource ':id' do
|
707
|
+
before { a.here(i += 1) }
|
708
|
+
before_validation { b.here(i += 1) }
|
709
|
+
after_validation { c.here(i += 1) }
|
710
|
+
after { d.here(i += 1) }
|
711
|
+
get do
|
712
|
+
'got it'
|
713
|
+
end
|
714
|
+
end
|
715
|
+
|
716
|
+
a.should_receive(:here).with(1).exactly(1).times
|
717
|
+
b.should_receive(:here).with(2).exactly(1).times
|
718
|
+
c.should_receive(:here).with(3).exactly(1).times
|
719
|
+
d.should_receive(:here).with(4).exactly(1).times
|
720
|
+
|
721
|
+
get '/123'
|
722
|
+
last_response.status.should eql 200
|
723
|
+
last_response.body.should eql 'got it'
|
724
|
+
end
|
608
725
|
end
|
609
726
|
|
610
727
|
context 'format' do
|
@@ -641,6 +758,26 @@ describe Grape::API do
|
|
641
758
|
get '/error.xml'
|
642
759
|
last_response.headers['Content-Type'].should eql 'application/xml'
|
643
760
|
end
|
761
|
+
|
762
|
+
context 'with a custom content_type' do
|
763
|
+
before do
|
764
|
+
subject.content_type :custom, 'application/custom'
|
765
|
+
subject.formatter :custom, lambda { |object, env| "custom" }
|
766
|
+
|
767
|
+
subject.get('/custom') { 'bar' }
|
768
|
+
subject.get('/error') { error!('error in custom', 500) }
|
769
|
+
end
|
770
|
+
|
771
|
+
it 'sets content type' do
|
772
|
+
get '/custom.custom'
|
773
|
+
last_response.headers['Content-Type'].should eql 'application/custom'
|
774
|
+
end
|
775
|
+
|
776
|
+
it 'sets content type for error' do
|
777
|
+
get '/error.custom'
|
778
|
+
last_response.headers['Content-Type'].should eql 'application/custom'
|
779
|
+
end
|
780
|
+
end
|
644
781
|
end
|
645
782
|
|
646
783
|
context 'custom middleware' do
|
@@ -713,13 +850,13 @@ describe Grape::API do
|
|
713
850
|
end
|
714
851
|
|
715
852
|
it 'adds a block if one is given' do
|
716
|
-
block = lambda {
|
853
|
+
block = lambda {}
|
717
854
|
subject.use ApiSpec::PhonyMiddleware, &block
|
718
855
|
subject.middleware.should eql [[ApiSpec::PhonyMiddleware, block]]
|
719
856
|
end
|
720
857
|
|
721
858
|
it 'uses a block if one is given' do
|
722
|
-
block = lambda {
|
859
|
+
block = lambda {}
|
723
860
|
subject.use ApiSpec::PhonyMiddleware, &block
|
724
861
|
subject.get '/' do
|
725
862
|
env['phony.block'].inspect
|
@@ -730,7 +867,7 @@ describe Grape::API do
|
|
730
867
|
end
|
731
868
|
|
732
869
|
it 'does not destroy the middleware settings on multiple runs' do
|
733
|
-
block = lambda {
|
870
|
+
block = lambda {}
|
734
871
|
subject.use ApiSpec::PhonyMiddleware, &block
|
735
872
|
subject.get '/' do
|
736
873
|
env['phony.block'].inspect
|
@@ -757,7 +894,7 @@ describe Grape::API do
|
|
757
894
|
end
|
758
895
|
end
|
759
896
|
end
|
760
|
-
describe '.
|
897
|
+
describe '.http_basic' do
|
761
898
|
it 'protects any resources on the same scope' do
|
762
899
|
subject.http_basic do |u, p|
|
763
900
|
u == 'allow'
|
@@ -796,6 +933,51 @@ describe Grape::API do
|
|
796
933
|
get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')
|
797
934
|
last_response.status.should eql 200
|
798
935
|
end
|
936
|
+
|
937
|
+
it 'has access to the current endpoint' do
|
938
|
+
basic_auth_context = nil
|
939
|
+
|
940
|
+
subject.http_basic do |u, p|
|
941
|
+
basic_auth_context = self
|
942
|
+
|
943
|
+
u == 'allow'
|
944
|
+
end
|
945
|
+
|
946
|
+
subject.get(:hello) { "Hello, world." }
|
947
|
+
get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')
|
948
|
+
basic_auth_context.should be_an_instance_of(Grape::Endpoint)
|
949
|
+
end
|
950
|
+
|
951
|
+
it 'has access to helper methods' do
|
952
|
+
subject.helpers do
|
953
|
+
def authorize(u, p)
|
954
|
+
u == 'allow' && p == 'whatever'
|
955
|
+
end
|
956
|
+
end
|
957
|
+
|
958
|
+
subject.http_basic do |u, p|
|
959
|
+
authorize(u, p)
|
960
|
+
end
|
961
|
+
|
962
|
+
subject.get(:hello) { "Hello, world." }
|
963
|
+
get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')
|
964
|
+
last_response.status.should eql 200
|
965
|
+
get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('disallow', 'whatever')
|
966
|
+
last_response.status.should eql 401
|
967
|
+
end
|
968
|
+
|
969
|
+
it 'can set instance variables accessible to routes' do
|
970
|
+
subject.http_basic do |u, p|
|
971
|
+
@hello = "Hello, world."
|
972
|
+
|
973
|
+
u == 'allow'
|
974
|
+
end
|
975
|
+
|
976
|
+
subject.get(:hello) { @hello }
|
977
|
+
get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')
|
978
|
+
last_response.status.should eql 200
|
979
|
+
last_response.body.should eql "Hello, world."
|
980
|
+
end
|
799
981
|
end
|
800
982
|
|
801
983
|
describe '.logger' do
|
@@ -964,7 +1146,7 @@ describe Grape::API do
|
|
964
1146
|
raise "rain!"
|
965
1147
|
end
|
966
1148
|
get '/exception'
|
967
|
-
last_response.status.should eql
|
1149
|
+
last_response.status.should eql 500
|
968
1150
|
end
|
969
1151
|
|
970
1152
|
it 'rescues only certain errors if rescue_from is called with specific errors' do
|
@@ -973,30 +1155,34 @@ describe Grape::API do
|
|
973
1155
|
subject.get('/unrescued') { raise "beefcake" }
|
974
1156
|
|
975
1157
|
get '/rescued'
|
976
|
-
last_response.status.should eql
|
1158
|
+
last_response.status.should eql 500
|
977
1159
|
|
978
1160
|
lambda { get '/unrescued' }.should raise_error
|
979
1161
|
end
|
980
1162
|
|
981
|
-
|
982
|
-
|
983
|
-
|
1163
|
+
context 'CustomError subclass of Grape::Exceptions::Base' do
|
1164
|
+
before do
|
1165
|
+
class CustomError < Grape::Exceptions::Base; end
|
1166
|
+
end
|
984
1167
|
|
985
|
-
|
986
|
-
|
1168
|
+
it 'does not re-raise exceptions of type Grape::Exceptions::Base' do
|
1169
|
+
subject.get('/custom_exception') { raise CustomError }
|
987
1170
|
|
988
|
-
|
989
|
-
class CustomError < Grape::Exceptions::Base; end
|
990
|
-
subject.rescue_from CustomError do |e|
|
991
|
-
rack_response('New Error', e.status)
|
992
|
-
end
|
993
|
-
subject.get '/custom_error' do
|
994
|
-
raise CustomError, status: 400, message: 'Custom Error'
|
1171
|
+
lambda { get '/custom_exception' }.should_not raise_error
|
995
1172
|
end
|
996
1173
|
|
997
|
-
|
998
|
-
|
999
|
-
|
1174
|
+
it 'rescues custom grape exceptions' do
|
1175
|
+
subject.rescue_from CustomError do |e|
|
1176
|
+
rack_response('New Error', e.status)
|
1177
|
+
end
|
1178
|
+
subject.get '/custom_error' do
|
1179
|
+
raise CustomError, status: 400, message: 'Custom Error'
|
1180
|
+
end
|
1181
|
+
|
1182
|
+
get '/custom_error'
|
1183
|
+
last_response.status.should == 400
|
1184
|
+
last_response.body.should == 'New Error'
|
1185
|
+
end
|
1000
1186
|
end
|
1001
1187
|
|
1002
1188
|
it 'can rescue exceptions raised in the formatter' do
|
@@ -1027,61 +1213,65 @@ describe Grape::API do
|
|
1027
1213
|
last_response.status.should eql 202
|
1028
1214
|
last_response.body.should == 'rescued from rain!'
|
1029
1215
|
end
|
1030
|
-
|
1031
|
-
|
1032
|
-
|
1033
|
-
|
1034
|
-
|
1035
|
-
|
1036
|
-
raise ConnectionError
|
1037
|
-
end
|
1038
|
-
get '/exception'
|
1039
|
-
last_response.status.should eql 500
|
1040
|
-
last_response.body.should == 'rescued from ConnectionError'
|
1041
|
-
end
|
1042
|
-
it 'rescues a specific error' do
|
1043
|
-
class ConnectionError < RuntimeError; end
|
1044
|
-
subject.rescue_from ConnectionError do |e|
|
1045
|
-
rack_response("rescued from #{e.class.name}", 500)
|
1046
|
-
end
|
1047
|
-
subject.get '/exception' do
|
1048
|
-
raise ConnectionError
|
1049
|
-
end
|
1050
|
-
get '/exception'
|
1051
|
-
last_response.status.should eql 500
|
1052
|
-
last_response.body.should == 'rescued from ConnectionError'
|
1053
|
-
end
|
1054
|
-
it 'rescues multiple specific errors' do
|
1055
|
-
class ConnectionError < RuntimeError; end
|
1056
|
-
class DatabaseError < RuntimeError; end
|
1057
|
-
subject.rescue_from ConnectionError do |e|
|
1058
|
-
rack_response("rescued from #{e.class.name}", 500)
|
1059
|
-
end
|
1060
|
-
subject.rescue_from DatabaseError do |e|
|
1061
|
-
rack_response("rescued from #{e.class.name}", 500)
|
1216
|
+
|
1217
|
+
context 'custom errors' do
|
1218
|
+
before do
|
1219
|
+
class ConnectionError < RuntimeError; end
|
1220
|
+
class DatabaseError < RuntimeError; end
|
1221
|
+
class CommunicationError < StandardError; end
|
1062
1222
|
end
|
1063
|
-
|
1064
|
-
|
1223
|
+
|
1224
|
+
it 'rescues an error via rescue_from :all' do
|
1225
|
+
subject.rescue_from :all do |e|
|
1226
|
+
rack_response("rescued from #{e.class.name}", 500)
|
1227
|
+
end
|
1228
|
+
subject.get '/exception' do
|
1229
|
+
raise ConnectionError
|
1230
|
+
end
|
1231
|
+
get '/exception'
|
1232
|
+
last_response.status.should eql 500
|
1233
|
+
last_response.body.should == 'rescued from ConnectionError'
|
1065
1234
|
end
|
1066
|
-
|
1067
|
-
|
1235
|
+
it 'rescues a specific error' do
|
1236
|
+
subject.rescue_from ConnectionError do |e|
|
1237
|
+
rack_response("rescued from #{e.class.name}", 500)
|
1238
|
+
end
|
1239
|
+
subject.get '/exception' do
|
1240
|
+
raise ConnectionError
|
1241
|
+
end
|
1242
|
+
get '/exception'
|
1243
|
+
last_response.status.should eql 500
|
1244
|
+
last_response.body.should == 'rescued from ConnectionError'
|
1068
1245
|
end
|
1069
|
-
|
1070
|
-
|
1071
|
-
|
1072
|
-
|
1073
|
-
|
1074
|
-
|
1075
|
-
|
1076
|
-
|
1077
|
-
|
1078
|
-
|
1079
|
-
|
1246
|
+
it 'rescues multiple specific errors' do
|
1247
|
+
subject.rescue_from ConnectionError do |e|
|
1248
|
+
rack_response("rescued from #{e.class.name}", 500)
|
1249
|
+
end
|
1250
|
+
subject.rescue_from DatabaseError do |e|
|
1251
|
+
rack_response("rescued from #{e.class.name}", 500)
|
1252
|
+
end
|
1253
|
+
subject.get '/connection' do
|
1254
|
+
raise ConnectionError
|
1255
|
+
end
|
1256
|
+
subject.get '/database' do
|
1257
|
+
raise DatabaseError
|
1258
|
+
end
|
1259
|
+
get '/connection'
|
1260
|
+
last_response.status.should eql 500
|
1261
|
+
last_response.body.should == 'rescued from ConnectionError'
|
1262
|
+
get '/database'
|
1263
|
+
last_response.status.should eql 500
|
1264
|
+
last_response.body.should == 'rescued from DatabaseError'
|
1080
1265
|
end
|
1081
|
-
|
1082
|
-
|
1266
|
+
it 'does not rescue a different error' do
|
1267
|
+
subject.rescue_from RuntimeError do |e|
|
1268
|
+
rack_response("rescued from #{e.class.name}", 500)
|
1269
|
+
end
|
1270
|
+
subject.get '/uncaught' do
|
1271
|
+
raise CommunicationError
|
1272
|
+
end
|
1273
|
+
lambda { get '/uncaught' }.should raise_error(CommunicationError)
|
1083
1274
|
end
|
1084
|
-
lambda { get '/uncaught' }.should raise_error(CommunicationError)
|
1085
1275
|
end
|
1086
1276
|
end
|
1087
1277
|
|
@@ -1124,6 +1314,46 @@ describe Grape::API do
|
|
1124
1314
|
end
|
1125
1315
|
end
|
1126
1316
|
|
1317
|
+
describe '.rescue_from klass, rescue_subclasses: boolean' do
|
1318
|
+
before do
|
1319
|
+
module APIErrors
|
1320
|
+
class ParentError < StandardError; end
|
1321
|
+
class ChildError < ParentError; end
|
1322
|
+
end
|
1323
|
+
end
|
1324
|
+
|
1325
|
+
it 'rescues error as well as subclass errors with rescue_subclasses option set' do
|
1326
|
+
subject.rescue_from APIErrors::ParentError, rescue_subclasses: true do |e|
|
1327
|
+
rack_response("rescued from #{e.class.name}", 500)
|
1328
|
+
end
|
1329
|
+
subject.get '/caught_child' do
|
1330
|
+
raise APIErrors::ChildError
|
1331
|
+
end
|
1332
|
+
subject.get '/caught_parent' do
|
1333
|
+
raise APIErrors::ParentError
|
1334
|
+
end
|
1335
|
+
subject.get '/uncaught_parent' do
|
1336
|
+
raise StandardError
|
1337
|
+
end
|
1338
|
+
|
1339
|
+
get '/caught_child'
|
1340
|
+
last_response.status.should eql 500
|
1341
|
+
get '/caught_parent'
|
1342
|
+
last_response.status.should eql 500
|
1343
|
+
lambda { get '/uncaught_parent' }.should raise_error(StandardError)
|
1344
|
+
end
|
1345
|
+
|
1346
|
+
it 'does not rescue child errors if rescue_subclasses is false' do
|
1347
|
+
subject.rescue_from APIErrors::ParentError, rescue_subclasses: false do |e|
|
1348
|
+
rack_response("rescued from #{e.class.name}", 500)
|
1349
|
+
end
|
1350
|
+
subject.get '/uncaught' do
|
1351
|
+
raise APIErrors::ChildError
|
1352
|
+
end
|
1353
|
+
lambda { get '/uncaught' }.should raise_error(APIErrors::ChildError)
|
1354
|
+
end
|
1355
|
+
end
|
1356
|
+
|
1127
1357
|
describe '.error_format' do
|
1128
1358
|
it 'rescues all errors and return :txt' do
|
1129
1359
|
subject.rescue_from :all
|
@@ -1142,7 +1372,7 @@ describe Grape::API do
|
|
1142
1372
|
raise "rain!"
|
1143
1373
|
end
|
1144
1374
|
get '/exception'
|
1145
|
-
last_response.body.start_with?("rain!\r\n").should
|
1375
|
+
last_response.body.start_with?("rain!\r\n").should be true
|
1146
1376
|
end
|
1147
1377
|
|
1148
1378
|
it 'rescues all errors with a default formatter' do
|
@@ -1308,7 +1538,7 @@ describe Grape::API do
|
|
1308
1538
|
last_response.body.should eql '{"some":"hash"}'
|
1309
1539
|
end
|
1310
1540
|
it 'uses custom formatter' do
|
1311
|
-
get '/simple.custom',
|
1541
|
+
get '/simple.custom', 'HTTP_ACCEPT' => 'application/custom'
|
1312
1542
|
last_response.body.should eql '{"custom_formatter":"hash"}'
|
1313
1543
|
end
|
1314
1544
|
end
|
@@ -1331,7 +1561,7 @@ describe Grape::API do
|
|
1331
1561
|
last_response.body.should eql '{"some":"hash"}'
|
1332
1562
|
end
|
1333
1563
|
it 'uses custom formatter' do
|
1334
|
-
get '/simple.custom',
|
1564
|
+
get '/simple.custom', 'HTTP_ACCEPT' => 'application/custom'
|
1335
1565
|
last_response.body.should eql '{"custom_formatter":"hash"}'
|
1336
1566
|
end
|
1337
1567
|
end
|
@@ -1343,7 +1573,7 @@ describe Grape::API do
|
|
1343
1573
|
subject.post '/data' do
|
1344
1574
|
{ x: params[:x] }
|
1345
1575
|
end
|
1346
|
-
post "/data", '{"x":42}',
|
1576
|
+
post "/data", '{"x":42}', 'CONTENT_TYPE' => 'application/json'
|
1347
1577
|
last_response.status.should == 201
|
1348
1578
|
last_response.body.should == '{"x":42}'
|
1349
1579
|
end
|
@@ -1448,7 +1678,16 @@ describe Grape::API do
|
|
1448
1678
|
raise "rain!"
|
1449
1679
|
end
|
1450
1680
|
get '/exception'
|
1451
|
-
last_response.status.should eql
|
1681
|
+
last_response.status.should eql 500
|
1682
|
+
end
|
1683
|
+
it 'uses the default error status in error!' do
|
1684
|
+
subject.rescue_from :all
|
1685
|
+
subject.default_error_status 400
|
1686
|
+
subject.get '/exception' do
|
1687
|
+
error! "rain!"
|
1688
|
+
end
|
1689
|
+
get '/exception'
|
1690
|
+
last_response.status.should eql 400
|
1452
1691
|
end
|
1453
1692
|
end
|
1454
1693
|
|
@@ -1513,7 +1752,7 @@ describe Grape::API do
|
|
1513
1752
|
end
|
1514
1753
|
describe 'api structure with additional parameters' do
|
1515
1754
|
before(:each) do
|
1516
|
-
subject.get 'split/:string',
|
1755
|
+
subject.get 'split/:string', params: { "token" => "a token" }, optional_params: { "limit" => "the limit" } do
|
1517
1756
|
params[:string].split(params[:token], (params[:limit] || 0).to_i)
|
1518
1757
|
end
|
1519
1758
|
end
|
@@ -1550,7 +1789,7 @@ describe Grape::API do
|
|
1550
1789
|
route = subject.routes.first
|
1551
1790
|
route.route_description.should == "first method"
|
1552
1791
|
route.route_foo.should be_nil
|
1553
|
-
route.route_params.should == {
|
1792
|
+
route.route_params.should == {}
|
1554
1793
|
end
|
1555
1794
|
it 'describes methods separately' do
|
1556
1795
|
subject.desc "first method"
|
@@ -1597,9 +1836,7 @@ describe Grape::API do
|
|
1597
1836
|
]
|
1598
1837
|
end
|
1599
1838
|
it 'describes a method with parameters' do
|
1600
|
-
subject.desc "Reverses a string.", {
|
1601
|
-
{ "s" => { desc: "string to reverse", type: "string" } }
|
1602
|
-
}
|
1839
|
+
subject.desc "Reverses a string.", params: { "s" => { desc: "string to reverse", type: "string" } }
|
1603
1840
|
subject.get 'reverse' do
|
1604
1841
|
params[:s].reverse
|
1605
1842
|
end
|
@@ -1682,8 +1919,10 @@ describe Grape::API do
|
|
1682
1919
|
subject.routes.map { |route|
|
1683
1920
|
route.route_params
|
1684
1921
|
}.should eq [{
|
1922
|
+
"group1" => { required: true, type: "Array" },
|
1685
1923
|
"group1[param1]" => { required: false, desc: "group1 param1 desc" },
|
1686
1924
|
"group1[param2]" => { required: true, desc: "group1 param2 desc" },
|
1925
|
+
"group2" => { required: true, type: "Array" },
|
1687
1926
|
"group2[param1]" => { required: false, desc: "group2 param1 desc" },
|
1688
1927
|
"group2[param2]" => { required: true, desc: "group2 param2 desc" }
|
1689
1928
|
}]
|
@@ -1703,11 +1942,19 @@ describe Grape::API do
|
|
1703
1942
|
{ description: "nesting",
|
1704
1943
|
params: {
|
1705
1944
|
"root_param" => { required: true, desc: "root param" },
|
1945
|
+
"nested" => { required: true, type: "Array" },
|
1706
1946
|
"nested[nested_param]" => { required: true, desc: "nested param" }
|
1707
1947
|
}
|
1708
1948
|
}
|
1709
1949
|
]
|
1710
1950
|
end
|
1951
|
+
it 'allows to set the type attribute on :group element' do
|
1952
|
+
subject.params do
|
1953
|
+
group :foo, type: Array do
|
1954
|
+
optional :bar
|
1955
|
+
end
|
1956
|
+
end
|
1957
|
+
end
|
1711
1958
|
it 'parses parameters when no description is given' do
|
1712
1959
|
subject.params do
|
1713
1960
|
requires :one_param, desc: "one param"
|
@@ -1720,9 +1967,7 @@ describe Grape::API do
|
|
1720
1967
|
]
|
1721
1968
|
end
|
1722
1969
|
it 'does not symbolize params' do
|
1723
|
-
subject.desc "Reverses a string.", {
|
1724
|
-
{ "s" => { desc: "string to reverse", type: "string" } }
|
1725
|
-
}
|
1970
|
+
subject.desc "Reverses a string.", params: { "s" => { desc: "string to reverse", type: "string" } }
|
1726
1971
|
subject.get 'reverse/:s' do
|
1727
1972
|
params[:s].reverse
|
1728
1973
|
end
|
@@ -1735,7 +1980,7 @@ describe Grape::API do
|
|
1735
1980
|
end
|
1736
1981
|
|
1737
1982
|
describe '.mount' do
|
1738
|
-
let(:mounted_app) { lambda { |env| [200, {
|
1983
|
+
let(:mounted_app) { lambda { |env| [200, {}, ["MOUNTED"]] } }
|
1739
1984
|
|
1740
1985
|
context 'with a bare rack app' do
|
1741
1986
|
before do
|
@@ -1827,8 +2072,8 @@ describe Grape::API do
|
|
1827
2072
|
it 'collects the routes of the mounted api' do
|
1828
2073
|
subject.namespace :cool do
|
1829
2074
|
app = Class.new(Grape::API)
|
1830
|
-
app.get('/awesome') {
|
1831
|
-
app.post('/sauce') {
|
2075
|
+
app.get('/awesome') {}
|
2076
|
+
app.post('/sauce') {}
|
1832
2077
|
mount app
|
1833
2078
|
end
|
1834
2079
|
subject.routes.size.should == 2
|
@@ -1861,6 +2106,50 @@ describe Grape::API do
|
|
1861
2106
|
get "/app1/app2/nice"
|
1862
2107
|
last_response.status.should == 200
|
1863
2108
|
last_response.body.should == "play"
|
2109
|
+
options "/app1/app2/nice"
|
2110
|
+
last_response.status.should == 204
|
2111
|
+
end
|
2112
|
+
|
2113
|
+
it 'responds to options' do
|
2114
|
+
app = Class.new(Grape::API)
|
2115
|
+
app.get '/colour' do
|
2116
|
+
'red'
|
2117
|
+
end
|
2118
|
+
app.namespace :pears do
|
2119
|
+
get '/colour' do
|
2120
|
+
'green'
|
2121
|
+
end
|
2122
|
+
end
|
2123
|
+
subject.namespace :apples do
|
2124
|
+
mount app
|
2125
|
+
end
|
2126
|
+
get '/apples/colour'
|
2127
|
+
last_response.status.should eql 200
|
2128
|
+
last_response.body.should == 'red'
|
2129
|
+
options '/apples/colour'
|
2130
|
+
last_response.status.should eql 204
|
2131
|
+
get '/apples/pears/colour'
|
2132
|
+
last_response.status.should eql 200
|
2133
|
+
last_response.body.should == 'green'
|
2134
|
+
options '/apples/pears/colour'
|
2135
|
+
last_response.status.should eql 204
|
2136
|
+
end
|
2137
|
+
|
2138
|
+
it 'responds to options with path versioning' do
|
2139
|
+
subject.version 'v1', using: :path
|
2140
|
+
subject.namespace :apples do
|
2141
|
+
app = Class.new(Grape::API)
|
2142
|
+
app.get('/colour') do
|
2143
|
+
"red"
|
2144
|
+
end
|
2145
|
+
mount app
|
2146
|
+
end
|
2147
|
+
|
2148
|
+
get '/v1/apples/colour'
|
2149
|
+
last_response.status.should eql 200
|
2150
|
+
last_response.body.should == 'red'
|
2151
|
+
options '/v1/apples/colour'
|
2152
|
+
last_response.status.should eql 204
|
1864
2153
|
end
|
1865
2154
|
|
1866
2155
|
end
|
@@ -1932,7 +2221,7 @@ describe Grape::API do
|
|
1932
2221
|
subject.get '/description' do
|
1933
2222
|
route.route_description
|
1934
2223
|
end
|
1935
|
-
subject.desc 'returns parameters',
|
2224
|
+
subject.desc 'returns parameters', params: { "x" => "y" }
|
1936
2225
|
subject.get '/params/:id' do
|
1937
2226
|
route.route_params[params[:id]]
|
1938
2227
|
end
|
@@ -1965,7 +2254,7 @@ describe Grape::API do
|
|
1965
2254
|
last_response.body.should == { meaning_of_life: 42 }.to_json
|
1966
2255
|
end
|
1967
2256
|
it 'forces txt from a non-accepting header' do
|
1968
|
-
get '/meaning_of_life', {},
|
2257
|
+
get '/meaning_of_life', {}, 'HTTP_ACCEPT' => 'application/json'
|
1969
2258
|
last_response.body.should == { meaning_of_life: 42 }.to_s
|
1970
2259
|
end
|
1971
2260
|
end
|
@@ -1985,7 +2274,7 @@ describe Grape::API do
|
|
1985
2274
|
last_response.body.should == { meaning_of_life: 42 }.to_s
|
1986
2275
|
end
|
1987
2276
|
it 'forces txt from a non-accepting header' do
|
1988
|
-
get '/meaning_of_life', {},
|
2277
|
+
get '/meaning_of_life', {}, 'HTTP_ACCEPT' => 'application/json'
|
1989
2278
|
last_response.body.should == { meaning_of_life: 42 }.to_s
|
1990
2279
|
end
|
1991
2280
|
end
|
@@ -2006,7 +2295,7 @@ describe Grape::API do
|
|
2006
2295
|
last_response.body.should == { meaning_of_life: 42 }.to_s
|
2007
2296
|
end
|
2008
2297
|
it 'forces json from a non-accepting header' do
|
2009
|
-
get '/meaning_of_life', {},
|
2298
|
+
get '/meaning_of_life', {}, 'HTTP_ACCEPT' => 'text/html'
|
2010
2299
|
last_response.body.should == { meaning_of_life: 42 }.to_json
|
2011
2300
|
end
|
2012
2301
|
it 'can be overwritten with an explicit content type' do
|