grape-security 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +45 -0
- data/.rspec +2 -0
- data/.rubocop.yml +70 -0
- data/.travis.yml +18 -0
- data/.yardopts +2 -0
- data/CHANGELOG.md +314 -0
- data/CONTRIBUTING.md +118 -0
- data/Gemfile +21 -0
- data/Guardfile +14 -0
- data/LICENSE +20 -0
- data/README.md +1777 -0
- data/RELEASING.md +105 -0
- data/Rakefile +69 -0
- data/UPGRADING.md +124 -0
- data/grape-security.gemspec +39 -0
- data/grape.png +0 -0
- data/lib/grape.rb +99 -0
- data/lib/grape/api.rb +646 -0
- data/lib/grape/cookies.rb +39 -0
- data/lib/grape/endpoint.rb +533 -0
- data/lib/grape/error_formatter/base.rb +31 -0
- data/lib/grape/error_formatter/json.rb +15 -0
- data/lib/grape/error_formatter/txt.rb +16 -0
- data/lib/grape/error_formatter/xml.rb +15 -0
- data/lib/grape/exceptions/base.rb +66 -0
- data/lib/grape/exceptions/incompatible_option_values.rb +10 -0
- data/lib/grape/exceptions/invalid_formatter.rb +10 -0
- data/lib/grape/exceptions/invalid_versioner_option.rb +10 -0
- data/lib/grape/exceptions/invalid_with_option_for_represent.rb +10 -0
- data/lib/grape/exceptions/missing_mime_type.rb +10 -0
- data/lib/grape/exceptions/missing_option.rb +10 -0
- data/lib/grape/exceptions/missing_vendor_option.rb +10 -0
- data/lib/grape/exceptions/unknown_options.rb +10 -0
- data/lib/grape/exceptions/unknown_validator.rb +10 -0
- data/lib/grape/exceptions/validation.rb +26 -0
- data/lib/grape/exceptions/validation_errors.rb +43 -0
- data/lib/grape/formatter/base.rb +31 -0
- data/lib/grape/formatter/json.rb +12 -0
- data/lib/grape/formatter/serializable_hash.rb +35 -0
- data/lib/grape/formatter/txt.rb +11 -0
- data/lib/grape/formatter/xml.rb +12 -0
- data/lib/grape/http/request.rb +26 -0
- data/lib/grape/locale/en.yml +32 -0
- data/lib/grape/middleware/auth/base.rb +30 -0
- data/lib/grape/middleware/auth/basic.rb +13 -0
- data/lib/grape/middleware/auth/digest.rb +13 -0
- data/lib/grape/middleware/auth/oauth2.rb +83 -0
- data/lib/grape/middleware/base.rb +62 -0
- data/lib/grape/middleware/error.rb +89 -0
- data/lib/grape/middleware/filter.rb +17 -0
- data/lib/grape/middleware/formatter.rb +150 -0
- data/lib/grape/middleware/globals.rb +13 -0
- data/lib/grape/middleware/versioner.rb +32 -0
- data/lib/grape/middleware/versioner/accept_version_header.rb +67 -0
- data/lib/grape/middleware/versioner/header.rb +132 -0
- data/lib/grape/middleware/versioner/param.rb +42 -0
- data/lib/grape/middleware/versioner/path.rb +52 -0
- data/lib/grape/namespace.rb +23 -0
- data/lib/grape/parser/base.rb +29 -0
- data/lib/grape/parser/json.rb +11 -0
- data/lib/grape/parser/xml.rb +11 -0
- data/lib/grape/path.rb +70 -0
- data/lib/grape/route.rb +27 -0
- data/lib/grape/util/content_types.rb +18 -0
- data/lib/grape/util/deep_merge.rb +23 -0
- data/lib/grape/util/hash_stack.rb +120 -0
- data/lib/grape/validations.rb +322 -0
- data/lib/grape/validations/coerce.rb +63 -0
- data/lib/grape/validations/default.rb +25 -0
- data/lib/grape/validations/exactly_one_of.rb +26 -0
- data/lib/grape/validations/mutual_exclusion.rb +25 -0
- data/lib/grape/validations/presence.rb +16 -0
- data/lib/grape/validations/regexp.rb +12 -0
- data/lib/grape/validations/values.rb +23 -0
- data/lib/grape/version.rb +3 -0
- data/spec/grape/api_spec.rb +2571 -0
- data/spec/grape/endpoint_spec.rb +784 -0
- data/spec/grape/entity_spec.rb +324 -0
- data/spec/grape/exceptions/invalid_formatter_spec.rb +18 -0
- data/spec/grape/exceptions/invalid_versioner_option_spec.rb +18 -0
- data/spec/grape/exceptions/missing_mime_type_spec.rb +18 -0
- data/spec/grape/exceptions/missing_option_spec.rb +18 -0
- data/spec/grape/exceptions/unknown_options_spec.rb +18 -0
- data/spec/grape/exceptions/unknown_validator_spec.rb +18 -0
- data/spec/grape/exceptions/validation_errors_spec.rb +19 -0
- data/spec/grape/middleware/auth/basic_spec.rb +31 -0
- data/spec/grape/middleware/auth/digest_spec.rb +47 -0
- data/spec/grape/middleware/auth/oauth2_spec.rb +135 -0
- data/spec/grape/middleware/base_spec.rb +58 -0
- data/spec/grape/middleware/error_spec.rb +45 -0
- data/spec/grape/middleware/exception_spec.rb +184 -0
- data/spec/grape/middleware/formatter_spec.rb +258 -0
- data/spec/grape/middleware/versioner/accept_version_header_spec.rb +121 -0
- data/spec/grape/middleware/versioner/header_spec.rb +302 -0
- data/spec/grape/middleware/versioner/param_spec.rb +58 -0
- data/spec/grape/middleware/versioner/path_spec.rb +44 -0
- data/spec/grape/middleware/versioner_spec.rb +22 -0
- data/spec/grape/path_spec.rb +229 -0
- data/spec/grape/util/hash_stack_spec.rb +132 -0
- data/spec/grape/validations/coerce_spec.rb +208 -0
- data/spec/grape/validations/default_spec.rb +123 -0
- data/spec/grape/validations/exactly_one_of_spec.rb +71 -0
- data/spec/grape/validations/mutual_exclusion_spec.rb +61 -0
- data/spec/grape/validations/presence_spec.rb +142 -0
- data/spec/grape/validations/regexp_spec.rb +40 -0
- data/spec/grape/validations/values_spec.rb +152 -0
- data/spec/grape/validations/zh-CN.yml +10 -0
- data/spec/grape/validations_spec.rb +994 -0
- data/spec/shared/versioning_examples.rb +121 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/basic_auth_encode_helpers.rb +3 -0
- data/spec/support/content_type_helpers.rb +11 -0
- data/spec/support/versioned_helpers.rb +50 -0
- metadata +421 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'base64'
|
4
|
+
|
5
|
+
describe Grape::Middleware::Auth::Basic do
|
6
|
+
def app
|
7
|
+
Rack::Builder.new do |b|
|
8
|
+
b.use Grape::Middleware::Error
|
9
|
+
b.use(Grape::Middleware::Auth::Basic) do |u, p|
|
10
|
+
u && p && u == p
|
11
|
+
end
|
12
|
+
b.run lambda { |env| [200, {}, ["Hello there."]] }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'throws a 401 if no auth is given' do
|
17
|
+
@proc = lambda { false }
|
18
|
+
get '/whatever'
|
19
|
+
expect(last_response.status).to eq(401)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'authenticates if given valid creds' do
|
23
|
+
get '/whatever', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('admin', 'admin')
|
24
|
+
expect(last_response.status).to eq(200)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'throws a 401 is wrong auth is given' do
|
28
|
+
get '/whatever', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('admin', 'wrong')
|
29
|
+
expect(last_response.status).to eq(401)
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec::Matchers.define :be_challenge do
|
4
|
+
match do |actual_response|
|
5
|
+
actual_response.status == 401 &&
|
6
|
+
actual_response['WWW-Authenticate'] =~ /^Digest / &&
|
7
|
+
actual_response.body.empty?
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class Test < Grape::API
|
12
|
+
http_digest(realm: 'Test Api', opaque: 'secret') do |username|
|
13
|
+
{ 'foo' => 'bar' }[username]
|
14
|
+
end
|
15
|
+
|
16
|
+
get '/test' do
|
17
|
+
[{ hey: 'you' }, { there: 'bar' }, { foo: 'baz' }]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe Grape::Middleware::Auth::Digest do
|
22
|
+
def app
|
23
|
+
Test
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'is a digest authentication challenge' do
|
27
|
+
get '/test'
|
28
|
+
expect(last_response).to be_challenge
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'throws a 401 if no auth is given' do
|
32
|
+
get '/test'
|
33
|
+
expect(last_response.status).to eq(401)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'authenticates if given valid creds' do
|
37
|
+
digest_authorize "foo", "bar"
|
38
|
+
get '/test'
|
39
|
+
expect(last_response.status).to eq(200)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'throws a 401 if given invalid creds' do
|
43
|
+
digest_authorize "bar", "foo"
|
44
|
+
get '/test'
|
45
|
+
expect(last_response.status).to eq(401)
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Grape::Middleware::Auth::OAuth2 do
|
4
|
+
class FakeToken
|
5
|
+
attr_accessor :token
|
6
|
+
|
7
|
+
def self.verify(token)
|
8
|
+
FakeToken.new(token) if !!token && %w(g e).include?(token[0..0])
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(token)
|
12
|
+
@token = token
|
13
|
+
end
|
14
|
+
|
15
|
+
def expired?
|
16
|
+
@token[0..0] == 'e'
|
17
|
+
end
|
18
|
+
|
19
|
+
def permission_for?(env)
|
20
|
+
env['PATH_INFO'] == '/forbidden' ? false : true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def app
|
25
|
+
Rack::Builder.app do
|
26
|
+
use Grape::Middleware::Auth::OAuth2, token_class: 'FakeToken'
|
27
|
+
run lambda { |env| [200, {}, [(env['api.token'].token if env['api.token'])]] }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'with the token in the query string' do
|
32
|
+
context 'and a valid token' do
|
33
|
+
before { get '/awesome?access_token=g123' }
|
34
|
+
|
35
|
+
it 'sets env["api.token"]' do
|
36
|
+
expect(last_response.body).to eq('g123')
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'and an invalid token' do
|
41
|
+
before do
|
42
|
+
@err = catch :error do
|
43
|
+
get '/awesome?access_token=b123'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'throws an error' do
|
48
|
+
expect(@err[:status]).to eq(401)
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'sets the WWW-Authenticate header in the response' do
|
52
|
+
expect(@err[:headers]['WWW-Authenticate']).to eq("OAuth realm='OAuth API', error='invalid_grant'")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'with an expired token' do
|
58
|
+
before do
|
59
|
+
@err = catch :error do
|
60
|
+
get '/awesome?access_token=e123'
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'throws an error' do
|
65
|
+
expect(@err[:status]).to eq(401)
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'sets the WWW-Authenticate header in the response to error' do
|
69
|
+
expect(@err[:headers]['WWW-Authenticate']).to eq("OAuth realm='OAuth API', error='invalid_grant'")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
%w(HTTP_AUTHORIZATION X_HTTP_AUTHORIZATION X-HTTP_AUTHORIZATION REDIRECT_X_HTTP_AUTHORIZATION).each do |head|
|
74
|
+
context "with the token in the #{head} header" do
|
75
|
+
before do
|
76
|
+
get '/awesome', {}, head => 'OAuth g123'
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'sets env["api.token"]' do
|
80
|
+
expect(last_response.body).to eq('g123')
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context 'with the token in the POST body' do
|
86
|
+
before do
|
87
|
+
post '/awesome', 'access_token' => 'g123'
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'sets env["api.token"]' do
|
91
|
+
expect(last_response.body).to eq('g123')
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'when accessing something outside its scope' do
|
96
|
+
before do
|
97
|
+
@err = catch :error do
|
98
|
+
get '/forbidden?access_token=g123'
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'throws an error' do
|
103
|
+
expect(@err[:status]).to eq(403)
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'sets the WWW-Authenticate header in the response to error' do
|
107
|
+
expect(@err[:headers]['WWW-Authenticate']).to eq("OAuth realm='OAuth API', error='insufficient_scope'")
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
context 'when authorization is not required' do
|
112
|
+
def app
|
113
|
+
Rack::Builder.app do
|
114
|
+
use Grape::Middleware::Auth::OAuth2, token_class: 'FakeToken', required: false
|
115
|
+
run lambda { |env| [200, {}, [(env['api.token'].token if env['api.token'])]] }
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
context 'with no token' do
|
120
|
+
before { post '/awesome' }
|
121
|
+
|
122
|
+
it 'succeeds anyway' do
|
123
|
+
expect(last_response.status).to eq(200)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
context 'with a valid token' do
|
128
|
+
before { get '/awesome?access_token=g123' }
|
129
|
+
|
130
|
+
it 'sets env["api.token"]' do
|
131
|
+
expect(last_response.body).to eq('g123')
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Grape::Middleware::Base do
|
4
|
+
subject { Grape::Middleware::Base.new(blank_app) }
|
5
|
+
let(:blank_app) { lambda { |_| [200, {}, 'Hi there.'] } }
|
6
|
+
|
7
|
+
before do
|
8
|
+
# Keep it one object for testing.
|
9
|
+
allow(subject).to receive(:dup).and_return(subject)
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'has the app as an accessor' do
|
13
|
+
expect(subject.app).to eq(blank_app)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'calls through to the app' do
|
17
|
+
expect(subject.call({})).to eq([200, {}, 'Hi there.'])
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'callbacks' do
|
21
|
+
it 'calls #before' do
|
22
|
+
expect(subject).to receive(:before)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'calls #after' do
|
26
|
+
expect(subject).to receive(:after)
|
27
|
+
end
|
28
|
+
|
29
|
+
after { subject.call!({}) }
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'is able to access the response' do
|
33
|
+
subject.call({})
|
34
|
+
expect(subject.response).to be_kind_of(Rack::Response)
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'options' do
|
38
|
+
it 'persists options passed at initialization' do
|
39
|
+
expect(Grape::Middleware::Base.new(blank_app, abc: true).options[:abc]).to be true
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'defaults' do
|
43
|
+
class ExampleWare < Grape::Middleware::Base
|
44
|
+
def default_options
|
45
|
+
{ monkey: true }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'persists the default options' do
|
50
|
+
expect(ExampleWare.new(blank_app).options[:monkey]).to be true
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'overrides default options when provided' do
|
54
|
+
expect(ExampleWare.new(blank_app, monkey: false).options[:monkey]).to be false
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Grape::Middleware::Error do
|
4
|
+
class ErrApp
|
5
|
+
class << self
|
6
|
+
attr_accessor :error
|
7
|
+
attr_accessor :format
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
throw :error, error
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def app
|
16
|
+
Rack::Builder.app do
|
17
|
+
use Grape::Middleware::Error, default_message: 'Aww, hamburgers.'
|
18
|
+
run ErrApp
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'sets the status code appropriately' do
|
23
|
+
ErrApp.error = { status: 410 }
|
24
|
+
get '/'
|
25
|
+
expect(last_response.status).to eq(410)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'sets the error message appropriately' do
|
29
|
+
ErrApp.error = { message: 'Awesome stuff.' }
|
30
|
+
get '/'
|
31
|
+
expect(last_response.body).to eq('Awesome stuff.')
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'defaults to a 500 status' do
|
35
|
+
ErrApp.error = {}
|
36
|
+
get '/'
|
37
|
+
expect(last_response.status).to eq(500)
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'has a default message' do
|
41
|
+
ErrApp.error = {}
|
42
|
+
get '/'
|
43
|
+
expect(last_response.body).to eq('Aww, hamburgers.')
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'active_support/core_ext/hash'
|
3
|
+
|
4
|
+
describe Grape::Middleware::Error do
|
5
|
+
|
6
|
+
# raises a text exception
|
7
|
+
class ExceptionApp
|
8
|
+
class << self
|
9
|
+
def call(env)
|
10
|
+
raise "rain!"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# raises a hash error
|
16
|
+
class ErrorHashApp
|
17
|
+
class << self
|
18
|
+
def error!(message, status)
|
19
|
+
throw :error, message: { error: message, detail: "missing widget" }, status: status
|
20
|
+
end
|
21
|
+
|
22
|
+
def call(env)
|
23
|
+
error!("rain!", 401)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# raises an error!
|
29
|
+
class AccessDeniedApp
|
30
|
+
class << self
|
31
|
+
def error!(message, status)
|
32
|
+
throw :error, message: message, status: status
|
33
|
+
end
|
34
|
+
|
35
|
+
def call(env)
|
36
|
+
error!("Access Denied", 401)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# raises a custom error
|
42
|
+
class CustomError < Grape::Exceptions::Base
|
43
|
+
end
|
44
|
+
|
45
|
+
class CustomErrorApp
|
46
|
+
class << self
|
47
|
+
def call(env)
|
48
|
+
raise CustomError, status: 400, message: 'failed validation'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
attr_reader :app
|
54
|
+
|
55
|
+
it 'does not trap errors by default' do
|
56
|
+
@app ||= Rack::Builder.app do
|
57
|
+
use Grape::Middleware::Error
|
58
|
+
run ExceptionApp
|
59
|
+
end
|
60
|
+
expect { get '/' }.to raise_error
|
61
|
+
end
|
62
|
+
|
63
|
+
context 'with rescue_all set to true' do
|
64
|
+
it 'sets the message appropriately' do
|
65
|
+
@app ||= Rack::Builder.app do
|
66
|
+
use Grape::Middleware::Error, rescue_all: true
|
67
|
+
run ExceptionApp
|
68
|
+
end
|
69
|
+
get '/'
|
70
|
+
expect(last_response.body).to eq("rain!")
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'defaults to a 500 status' do
|
74
|
+
@app ||= Rack::Builder.app do
|
75
|
+
use Grape::Middleware::Error, rescue_all: true
|
76
|
+
run ExceptionApp
|
77
|
+
end
|
78
|
+
get '/'
|
79
|
+
expect(last_response.status).to eq(500)
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'is possible to specify a different default status code' do
|
83
|
+
@app ||= Rack::Builder.app do
|
84
|
+
use Grape::Middleware::Error, rescue_all: true, default_status: 500
|
85
|
+
run ExceptionApp
|
86
|
+
end
|
87
|
+
get '/'
|
88
|
+
expect(last_response.status).to eq(500)
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'is possible to return errors in json format' do
|
92
|
+
@app ||= Rack::Builder.app do
|
93
|
+
use Grape::Middleware::Error, rescue_all: true, format: :json
|
94
|
+
run ExceptionApp
|
95
|
+
end
|
96
|
+
get '/'
|
97
|
+
expect(last_response.body).to eq('{"error":"rain!"}')
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'is possible to return hash errors in json format' do
|
101
|
+
@app ||= Rack::Builder.app do
|
102
|
+
use Grape::Middleware::Error, rescue_all: true, format: :json
|
103
|
+
run ErrorHashApp
|
104
|
+
end
|
105
|
+
get '/'
|
106
|
+
expect(['{"error":"rain!","detail":"missing widget"}',
|
107
|
+
'{"detail":"missing widget","error":"rain!"}']).to include(last_response.body)
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'is possible to return errors in jsonapi format' do
|
111
|
+
@app ||= Rack::Builder.app do
|
112
|
+
use Grape::Middleware::Error, rescue_all: true, format: :jsonapi
|
113
|
+
run ExceptionApp
|
114
|
+
end
|
115
|
+
get '/'
|
116
|
+
expect(last_response.body).to eq('{"error":"rain!"}')
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'is possible to return hash errors in jsonapi format' do
|
120
|
+
@app ||= Rack::Builder.app do
|
121
|
+
use Grape::Middleware::Error, rescue_all: true, format: :jsonapi
|
122
|
+
run ErrorHashApp
|
123
|
+
end
|
124
|
+
get '/'
|
125
|
+
expect(['{"error":"rain!","detail":"missing widget"}',
|
126
|
+
'{"detail":"missing widget","error":"rain!"}']).to include(last_response.body)
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'is possible to return errors in xml format' do
|
130
|
+
@app ||= Rack::Builder.app do
|
131
|
+
use Grape::Middleware::Error, rescue_all: true, format: :xml
|
132
|
+
run ExceptionApp
|
133
|
+
end
|
134
|
+
get '/'
|
135
|
+
expect(last_response.body).to eq("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<error>\n <message>rain!</message>\n</error>\n")
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'is possible to return hash errors in xml format' do
|
139
|
+
@app ||= Rack::Builder.app do
|
140
|
+
use Grape::Middleware::Error, rescue_all: true, format: :xml
|
141
|
+
run ErrorHashApp
|
142
|
+
end
|
143
|
+
get '/'
|
144
|
+
expect(["<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<error>\n <detail>missing widget</detail>\n <error>rain!</error>\n</error>\n",
|
145
|
+
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<error>\n <error>rain!</error>\n <detail>missing widget</detail>\n</error>\n"]).to include(last_response.body)
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'is possible to specify a custom formatter' do
|
149
|
+
@app ||= Rack::Builder.app do
|
150
|
+
use Grape::Middleware::Error, rescue_all: true,
|
151
|
+
format: :custom,
|
152
|
+
error_formatters: {
|
153
|
+
custom: lambda { |message, backtrace, options, env|
|
154
|
+
{ custom_formatter: message }.inspect
|
155
|
+
}
|
156
|
+
}
|
157
|
+
run ExceptionApp
|
158
|
+
end
|
159
|
+
get '/'
|
160
|
+
expect(last_response.body).to eq('{:custom_formatter=>"rain!"}')
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'does not trap regular error! codes' do
|
164
|
+
@app ||= Rack::Builder.app do
|
165
|
+
use Grape::Middleware::Error
|
166
|
+
run AccessDeniedApp
|
167
|
+
end
|
168
|
+
get '/'
|
169
|
+
expect(last_response.status).to eq(401)
|
170
|
+
end
|
171
|
+
|
172
|
+
it 'responds to custom Grape exceptions appropriately' do
|
173
|
+
@app ||= Rack::Builder.app do
|
174
|
+
use Grape::Middleware::Error, rescue_all: false
|
175
|
+
run CustomErrorApp
|
176
|
+
end
|
177
|
+
|
178
|
+
get '/'
|
179
|
+
expect(last_response.status).to eq(400)
|
180
|
+
expect(last_response.body).to eq('failed validation')
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|
184
|
+
end
|