rack-robustness 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +48 -0
- data/README.md +80 -8
- data/lib/rack/robustness.rb +173 -58
- data/spec/spec_helper.rb +23 -0
- data/spec/test_context.rb +45 -0
- data/spec/test_ensure.rb +37 -0
- data/spec/test_last_resort.rb +98 -0
- data/spec/test_rescue.rb +32 -0
- data/spec/test_response.rb +40 -0
- data/spec/test_robustness.rb +7 -15
- data/spec/test_subclass.rb +20 -0
- metadata +16 -4
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,51 @@
|
|
1
|
+
# 1.1.0 / 2013-04-16
|
2
|
+
|
3
|
+
* Fixed catching of non standard errors (e.g. SecurityError)
|
4
|
+
|
5
|
+
* Global headers are now correctly overrided by specific per-exception headers
|
6
|
+
|
7
|
+
* Renamed `#on` as `#rescue` for better capturing semantics of `on` blocks (now an alias).
|
8
|
+
|
9
|
+
* Added last resort exception handling if an error occurs during exception handling itself.
|
10
|
+
In `no_catch_all` mode, the exception is simply reraised; otherwise a default 500 error
|
11
|
+
is returned with a safe message.
|
12
|
+
|
13
|
+
* Added a shortcut form for `#rescue` clauses allowing values directly, e.g.,
|
14
|
+
|
15
|
+
use Rack::Robustness do |g|
|
16
|
+
g.rescue(SecurityError, 403)
|
17
|
+
end
|
18
|
+
|
19
|
+
* Added suppport for ensure clause(s), called after `rescue` blocks on every error
|
20
|
+
|
21
|
+
* Rack's `env` is now available in all error handling blocks, e.g.,
|
22
|
+
|
23
|
+
use Rack::Robustness do |g|
|
24
|
+
g.status{|ex| ... env ... }
|
25
|
+
g.body {|ex| ... env ... }
|
26
|
+
g.rescue(SecurityError){|ex| ... env ... }
|
27
|
+
g.ensure{|ex| ... env ... }
|
28
|
+
end
|
29
|
+
|
30
|
+
* Similarly, Rack::Robustness now internally uses instances of Rack::Request and Rack::Response,
|
31
|
+
which are available under `request` and `response` in all blocks. The specific Response
|
32
|
+
object to use can be built using the `response` DSL method, e.g.,
|
33
|
+
|
34
|
+
use Rack::Robustness do |g|
|
35
|
+
g.response{|ex| MyOwnRackResponse.new }
|
36
|
+
end
|
37
|
+
|
38
|
+
* Rack::Robustness may now be subclassed as an alternative to inline use shown above, e.g.
|
39
|
+
|
40
|
+
class Shield < Rack::Robustness
|
41
|
+
self.body {|ex| ... }
|
42
|
+
self.rescue(SecurityError){|ex| ... }
|
43
|
+
...
|
44
|
+
end
|
45
|
+
|
46
|
+
# in Rack-based configuration
|
47
|
+
use Shield
|
48
|
+
|
1
49
|
# 1.0.0 / 2013-02-26
|
2
50
|
|
3
51
|
* Enhancements
|
data/README.md
CHANGED
@@ -1,15 +1,59 @@
|
|
1
1
|
# Rack::Robustness, the rescue clause of your Rack stack.
|
2
2
|
|
3
|
-
Rack::Robustness is the rescue clause of your Rack's call stack. In other words, a middleware that ensures the robustness of your web stack, because exceptions occur either intentionally or unintentionally.
|
3
|
+
Rack::Robustness is the rescue clause of your Rack's call stack. In other words, a middleware that ensures the robustness of your web stack, because exceptions occur either intentionally or unintentionally. Rack::Robustness is the rack middleware you would have written manually (see below) but provides a DSL for scaling from zero configuration (a default shield) to specific rescue clauses for specific errors.
|
4
4
|
|
5
5
|
[![Build Status](https://secure.travis-ci.org/blambeau/rack-robustness.png)](http://travis-ci.org/blambeau/rack-robustness)
|
6
6
|
[![Dependency Status](https://gemnasium.com/blambeau/rack-robustness.png)](https://gemnasium.com/blambeau/rack-robustness)
|
7
7
|
|
8
|
+
```ruby
|
9
|
+
##
|
10
|
+
#
|
11
|
+
# The middleware you would have written
|
12
|
+
#
|
13
|
+
class Robustness
|
14
|
+
|
15
|
+
def initialize(app)
|
16
|
+
@app = app
|
17
|
+
end
|
18
|
+
|
19
|
+
def call(env)
|
20
|
+
@app.call(env)
|
21
|
+
rescue ArgumentError => ex
|
22
|
+
[400, { 'Content-Type' => 'text/plain' }, [ ex.message ] ] # suppose the message can be safely used
|
23
|
+
rescue SecurityError => ex
|
24
|
+
[403, { 'Content-Type' => 'text/plain' }, [ ex.message ] ]
|
25
|
+
ensure
|
26
|
+
env['rack.errors'].write(ex.message) if ex
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
```
|
31
|
+
|
32
|
+
...becomes...
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
use Rack::Robustness do |g|
|
36
|
+
g.on(ArgumentError){|ex| 400 }
|
37
|
+
g.on(SecurityError){|ex| 403 }
|
38
|
+
|
39
|
+
g.content_type 'text/plain'
|
40
|
+
|
41
|
+
g.body{|ex|
|
42
|
+
ex.message
|
43
|
+
}
|
44
|
+
|
45
|
+
g.ensure(true){|ex|
|
46
|
+
env['rack.errors'].write(ex.message)
|
47
|
+
}
|
48
|
+
end
|
49
|
+
```
|
50
|
+
|
8
51
|
## Links
|
9
52
|
|
10
|
-
https://github.com/blambeau/rack-robustness
|
53
|
+
* https://github.com/blambeau/rack-robustness
|
54
|
+
* http://www.revision-zero.org/rack-robustness
|
11
55
|
|
12
|
-
## Why?
|
56
|
+
## Why?
|
13
57
|
|
14
58
|
In my opinion, Sinatra's error handling is sometimes a bit limited for real-case needs. So I came up with something a bit more Rack-ish, that allows handling exceptions actively, because exceptions occur and that you'll handle them... enventually. A more theoretic argumentation would be:
|
15
59
|
|
@@ -17,20 +61,22 @@ In my opinion, Sinatra's error handling is sometimes a bit limited for real-case
|
|
17
61
|
* The behavior to adopt when obstacles occur is not necessary defined where the exception is thrown, but often higher in the call stack.
|
18
62
|
* In ruby web apps, the Rack's call stack is a very important part of your stack. Middlewares, routes and controllers do rarely rescue all errors, so it's still your job to rescue errors higher in the call stack.
|
19
63
|
|
20
|
-
Rack::Robustness is therefore a try/catch mechanism as a middleware, to be used along the Rack call stack as you would use a standard one in a more conventional call stack:
|
64
|
+
Rack::Robustness is therefore a try/catch/finally mechanism as a middleware, to be used along the Rack call stack as you would use a standard one in a more conventional call stack:
|
21
65
|
|
22
66
|
```java
|
23
67
|
try {
|
24
68
|
// main shield, typically in a main
|
25
|
-
|
69
|
+
|
26
70
|
try {
|
27
71
|
// try to achieve a goal here
|
28
72
|
} catch (...) {
|
29
73
|
// fallback to an alternative
|
74
|
+
} finally {
|
75
|
+
// ensure something is executed in all cases
|
30
76
|
}
|
31
|
-
|
77
|
+
|
32
78
|
// continue your flow
|
33
|
-
|
79
|
+
|
34
80
|
} catch (...) {
|
35
81
|
// something goes really wrong, inform the user as you can
|
36
82
|
}
|
@@ -53,6 +99,8 @@ class Main < Sinatra::Base
|
|
53
99
|
use Rack::Robustness do
|
54
100
|
# fallback to an alternative
|
55
101
|
# 3xx, 4xx errors maybe
|
102
|
+
|
103
|
+
# ensure something is executed in all cases
|
56
104
|
end
|
57
105
|
|
58
106
|
# try to achieve your goal through standard routes
|
@@ -60,7 +108,7 @@ class Main < Sinatra::Base
|
|
60
108
|
end
|
61
109
|
```
|
62
110
|
|
63
|
-
##
|
111
|
+
## Additional examples
|
64
112
|
|
65
113
|
```ruby
|
66
114
|
class App < Sinatra::Base
|
@@ -102,6 +150,9 @@ class App < Sinatra::Base
|
|
102
150
|
# we use SecurityError for handling forbidden accesses.
|
103
151
|
# The default status is 403 here
|
104
152
|
g.on(SecurityError){|ex| 403 }
|
153
|
+
|
154
|
+
# ensure logging in all exceptional cases
|
155
|
+
g.ensure(true){|ex| env['rack.errors'].write(ex.message) }
|
105
156
|
end
|
106
157
|
|
107
158
|
get '/some/route/:id' do |id|
|
@@ -202,6 +253,27 @@ use Rack::Robustness do |g|
|
|
202
253
|
end
|
203
254
|
```
|
204
255
|
|
256
|
+
## Ensure common block in happy/exceptional/all cases
|
257
|
+
|
258
|
+
```ruby
|
259
|
+
##
|
260
|
+
# Ensure in all cases (no arg) or exceptional cases only (true)
|
261
|
+
#
|
262
|
+
use Rack::Robustness do |g|
|
263
|
+
|
264
|
+
# Ensure in all cases
|
265
|
+
g.ensure{|ex|
|
266
|
+
# ex might be nil here
|
267
|
+
}
|
268
|
+
|
269
|
+
# Ensure in exceptional cases only (for logging purposes for instance)
|
270
|
+
g.ensure(true){|ex|
|
271
|
+
# an exception occured, ex is never nil
|
272
|
+
env['rack.errors'].write("#{ex.message}\n")
|
273
|
+
}
|
274
|
+
end
|
275
|
+
```
|
276
|
+
|
205
277
|
## Don't catch all!
|
206
278
|
|
207
279
|
```ruby
|
data/lib/rack/robustness.rb
CHANGED
@@ -1,95 +1,210 @@
|
|
1
1
|
module Rack
|
2
2
|
class Robustness
|
3
3
|
|
4
|
-
VERSION = "1.
|
4
|
+
VERSION = "1.1.0".freeze
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
@app = app
|
10
|
-
@handlers = {}
|
11
|
-
@status = 500
|
12
|
-
@headers = {'Content-Type' => "text/plain"}
|
13
|
-
@body = ["Sorry, a fatal error occured."]
|
14
|
-
@catch_all = true
|
15
|
-
yield self if block_given?
|
16
|
-
on(Object){|ex| [@status, {}, @body]} if @catch_all
|
17
|
-
@headers.freeze
|
18
|
-
@body.freeze
|
19
|
-
@handlers.freeze
|
6
|
+
def self.new(app, &bl)
|
7
|
+
return super(app) if bl.nil? and not(Robustness==self)
|
8
|
+
Class.new(self).install(&bl).new(app)
|
20
9
|
end
|
21
10
|
|
22
11
|
##
|
23
12
|
# Configuration
|
13
|
+
module DSL
|
24
14
|
|
25
|
-
|
26
|
-
@catch_all = false
|
27
|
-
end
|
15
|
+
NIL_HANDLER = lambda{|ex| nil }
|
28
16
|
|
29
|
-
|
30
|
-
|
31
|
-
|
17
|
+
def inherited(x)
|
18
|
+
x.reset
|
19
|
+
end
|
32
20
|
|
33
|
-
|
34
|
-
|
35
|
-
|
21
|
+
def reset
|
22
|
+
@rescue_clauses = {}
|
23
|
+
@ensure_clauses = []
|
24
|
+
@status_clause = 500
|
25
|
+
@headers_clause = {'Content-Type' => "text/plain"}
|
26
|
+
@body_clause = ["Sorry, a fatal error occured."]
|
27
|
+
@response_builder = lambda{|ex| ::Rack::Response.new }
|
28
|
+
@catch_all = true
|
29
|
+
end
|
30
|
+
attr_reader :rescue_clauses, :ensure_clauses, :status_clause,
|
31
|
+
:headers_clause, :body_clause, :catch_all, :response_builder
|
32
|
+
|
33
|
+
def install
|
34
|
+
yield self if block_given?
|
35
|
+
on(Object){|ex|
|
36
|
+
[status_clause, {}, body_clause]
|
37
|
+
} if @catch_all
|
38
|
+
@headers_clause.freeze
|
39
|
+
@body_clause.freeze
|
40
|
+
@rescue_clauses.freeze
|
41
|
+
@ensure_clauses.freeze
|
42
|
+
self
|
43
|
+
end
|
36
44
|
|
37
|
-
|
38
|
-
|
39
|
-
@headers = bl
|
40
|
-
else
|
41
|
-
@headers.merge!(h)
|
45
|
+
def no_catch_all
|
46
|
+
@catch_all = false
|
42
47
|
end
|
43
|
-
end
|
44
48
|
|
45
|
-
|
46
|
-
|
47
|
-
|
49
|
+
def response(&bl)
|
50
|
+
@response_builder = bl
|
51
|
+
end
|
52
|
+
|
53
|
+
def rescue(ex_class, handler = nil, &bl)
|
54
|
+
@rescue_clauses[ex_class] = handler || bl || NIL_HANDLER
|
55
|
+
end
|
56
|
+
alias :on :rescue
|
57
|
+
|
58
|
+
def ensure(bypass_on_success = false, &bl)
|
59
|
+
@ensure_clauses << [bypass_on_success, bl]
|
60
|
+
end
|
61
|
+
|
62
|
+
def status(s=nil, &bl)
|
63
|
+
@status_clause = s || bl
|
64
|
+
end
|
65
|
+
|
66
|
+
def headers(h=nil, &bl)
|
67
|
+
if h.nil?
|
68
|
+
@headers_clause = bl
|
69
|
+
else
|
70
|
+
@headers_clause.merge!(h)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def content_type(ct=nil, &bl)
|
75
|
+
headers('Content-Type' => ct || bl)
|
76
|
+
end
|
77
|
+
|
78
|
+
def body(b=nil, &bl)
|
79
|
+
@body_clause = b.nil? ? bl : (String===b ? [ b ] : b)
|
80
|
+
end
|
81
|
+
|
82
|
+
end # module DSL
|
83
|
+
extend DSL
|
84
|
+
|
85
|
+
public
|
48
86
|
|
49
|
-
def
|
50
|
-
@
|
87
|
+
def initialize(app)
|
88
|
+
@app = app
|
51
89
|
end
|
52
90
|
|
53
91
|
##
|
54
92
|
# Rack's call
|
55
93
|
|
56
94
|
def call(env)
|
57
|
-
|
95
|
+
dup.call!(env)
|
58
96
|
rescue => ex
|
59
|
-
|
60
|
-
|
61
|
-
|
97
|
+
catch_all ? last_resort(ex) : raise(ex)
|
98
|
+
end
|
99
|
+
|
100
|
+
protected
|
101
|
+
|
102
|
+
def call!(env)
|
103
|
+
@env, @request = env, Rack::Request.new(env)
|
104
|
+
triple = @app.call(env)
|
105
|
+
handle_happy(triple)
|
106
|
+
rescue Exception => ex
|
107
|
+
handle_rescue(ex)
|
108
|
+
ensure
|
109
|
+
handle_ensure(ex)
|
62
110
|
end
|
63
111
|
|
64
112
|
private
|
65
113
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
114
|
+
attr_reader :env, :request, :response
|
115
|
+
|
116
|
+
[ :response_builder,
|
117
|
+
:rescue_clauses,
|
118
|
+
:ensure_clauses,
|
119
|
+
:status_clause,
|
120
|
+
:headers_clause,
|
121
|
+
:body_clause,
|
122
|
+
:catch_all ].each do |m|
|
123
|
+
define_method(m){|*args, &bl|
|
124
|
+
self.class.send(m, *args, &bl)
|
125
|
+
}
|
126
|
+
end
|
127
|
+
|
128
|
+
def handle_happy(triple)
|
129
|
+
s, h, b = triple
|
130
|
+
@response = Response.new(b, s, h)
|
131
|
+
@response.finish
|
132
|
+
end
|
133
|
+
|
134
|
+
def handle_rescue(ex)
|
135
|
+
begin
|
136
|
+
# build a response instance
|
137
|
+
@response = instance_exec(ex, &response_builder)
|
138
|
+
|
139
|
+
# populate it if a rescue clause can be found
|
140
|
+
if rescue_clause = find_rescue_clause(ex.class)
|
141
|
+
handle_error(ex, rescue_clause)
|
142
|
+
return @response.finish
|
143
|
+
end
|
144
|
+
|
145
|
+
# no_catch_all mode, let reraise it later
|
146
|
+
rescue Exception => ex2
|
147
|
+
return catch_all ? last_resort(ex2) : raise(ex2)
|
148
|
+
end
|
149
|
+
|
150
|
+
# we are in no_catch_all mode, reraise
|
151
|
+
raise(ex)
|
152
|
+
end
|
153
|
+
|
154
|
+
def handle_ensure(ex)
|
155
|
+
@response ||= begin
|
156
|
+
status, headers, body = last_resort(ex)
|
157
|
+
::Rack::Response.new(body, status, headers)
|
78
158
|
end
|
159
|
+
ensure_clauses.each{|(bypass,ensurer)|
|
160
|
+
instance_exec(ex, &ensurer) if ex or not(bypass)
|
161
|
+
}
|
79
162
|
end
|
80
163
|
|
81
|
-
def
|
82
|
-
case
|
83
|
-
when
|
84
|
-
when
|
164
|
+
def handle_error(ex, rescue_clause)
|
165
|
+
case rescue_clause
|
166
|
+
when NilClass then handle_error(ex, [status_clause, {}, body_clause])
|
167
|
+
when Fixnum then handle_error(ex, [rescue_clause, {}, body_clause])
|
168
|
+
when String then handle_error(ex, [status_clause, {}, rescue_clause])
|
169
|
+
when Hash then handle_error(ex, [status_clause, rescue_clause, body_clause])
|
170
|
+
when Proc then handle_error(ex, handle_value(ex, rescue_clause))
|
85
171
|
else
|
86
|
-
|
172
|
+
status, headers, body = rescue_clause
|
173
|
+
handle_status(ex, status)
|
174
|
+
handle_headers(ex, headers)
|
175
|
+
handle_headers(ex, headers_clause)
|
176
|
+
handle_body(ex, body)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def handle_status(ex, status)
|
181
|
+
@response.status = handle_value(ex, status)
|
182
|
+
end
|
183
|
+
|
184
|
+
def handle_headers(ex, headers)
|
185
|
+
handle_value(ex, headers).each_pair do |key,value|
|
186
|
+
@response[key] ||= handle_value(ex, value)
|
87
187
|
end
|
88
188
|
end
|
89
189
|
|
90
|
-
def
|
190
|
+
def handle_body(ex, body)
|
191
|
+
body = handle_value(ex, body)
|
192
|
+
@response.body = body.is_a?(String) ? [ body ] : body
|
193
|
+
end
|
194
|
+
|
195
|
+
def handle_value(ex, value)
|
196
|
+
value.is_a?(Proc) ? instance_exec(ex, &value) : value
|
197
|
+
end
|
198
|
+
|
199
|
+
def find_rescue_clause(ex_class)
|
91
200
|
return nil if ex_class.nil?
|
92
|
-
|
201
|
+
rescue_clauses.fetch(ex_class){ find_rescue_clause(ex_class.superclass) }
|
202
|
+
end
|
203
|
+
|
204
|
+
def last_resort(ex)
|
205
|
+
[ 500,
|
206
|
+
{'Content-Type' => 'text/plain'},
|
207
|
+
[ 'An internal error occured, sorry for the disagreement.' ] ]
|
93
208
|
end
|
94
209
|
|
95
210
|
end # class Robustness
|
data/spec/spec_helper.rb
CHANGED
@@ -4,6 +4,29 @@ require 'rack/robustness'
|
|
4
4
|
require 'rack/test'
|
5
5
|
|
6
6
|
module SpecHelpers
|
7
|
+
|
8
|
+
def mock_app(clazz = Rack::Robustness, &bl)
|
9
|
+
Rack::Builder.new do
|
10
|
+
use clazz, &bl
|
11
|
+
map '/happy' do
|
12
|
+
run lambda{|env| [200, {'Content-Type' => 'text/plain'}, ['happy']]}
|
13
|
+
end
|
14
|
+
map "/argument-error" do
|
15
|
+
run lambda{|env| raise ArgumentError, "an argument error" }
|
16
|
+
end
|
17
|
+
map "/type-error" do
|
18
|
+
run lambda{|env| raise TypeError, "a type error" }
|
19
|
+
end
|
20
|
+
map "/security-error" do
|
21
|
+
run lambda{|env| raise SecurityError, "a security error" }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def app
|
27
|
+
mock_app{}
|
28
|
+
end
|
29
|
+
|
7
30
|
end
|
8
31
|
|
9
32
|
RSpec.configure do |c|
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
describe Rack::Robustness, 'the context in which blocks execute' do
|
3
|
+
include Rack::Test::Methods
|
4
|
+
|
5
|
+
let(:app){
|
6
|
+
mock_app do |g|
|
7
|
+
g.response{|ex|
|
8
|
+
raise "Invalid context" unless env && request
|
9
|
+
Rack::Response.new
|
10
|
+
}
|
11
|
+
g.body{|ex|
|
12
|
+
raise "Invalid context" unless env && request && response
|
13
|
+
if response.status == 400
|
14
|
+
"argument-error"
|
15
|
+
else
|
16
|
+
"security-error"
|
17
|
+
end
|
18
|
+
}
|
19
|
+
g.rescue(ArgumentError){|ex|
|
20
|
+
raise "Invalid context" unless env && request && response
|
21
|
+
400
|
22
|
+
}
|
23
|
+
g.rescue(SecurityError){|ex|
|
24
|
+
raise "Invalid context" unless env && request && response
|
25
|
+
403
|
26
|
+
}
|
27
|
+
g.ensure{|ex|
|
28
|
+
raise "Invalid context" unless env && request && response
|
29
|
+
$seen_ex = ex
|
30
|
+
}
|
31
|
+
end
|
32
|
+
}
|
33
|
+
|
34
|
+
it 'should let `env`, `request` and `response` be available in all blocks' do
|
35
|
+
get '/argument-error'
|
36
|
+
last_response.status.should eq(400)
|
37
|
+
last_response.body.should eq('argument-error')
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'executes the ensure block as well' do
|
41
|
+
get '/argument-error'
|
42
|
+
$seen_ex.should be_a(ArgumentError)
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
data/spec/test_ensure.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
describe Rack::Robustness, 'ensure' do
|
3
|
+
include Rack::Test::Methods
|
4
|
+
|
5
|
+
let(:app){
|
6
|
+
mock_app do |g|
|
7
|
+
g.ensure(true) {|ex| $seen_true = [ex.class] }
|
8
|
+
g.ensure(false){|ex| $seen_false = [ex.class] }
|
9
|
+
g.ensure {|ex| $seen_none = [ex.class] }
|
10
|
+
g.status 400
|
11
|
+
g.on(ArgumentError){|ex| "error" }
|
12
|
+
end
|
13
|
+
}
|
14
|
+
|
15
|
+
before do
|
16
|
+
$seen_true = $seen_false = $seen_none = nil
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should be called in all cases when an error occurs' do
|
20
|
+
get '/argument-error'
|
21
|
+
last_response.status.should eq(400)
|
22
|
+
last_response.body.should eq("error")
|
23
|
+
$seen_true.should eq([ArgumentError])
|
24
|
+
$seen_false.should eq([ArgumentError])
|
25
|
+
$seen_none.should eq([ArgumentError])
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should not be called when explicit bypass on happy paths' do
|
29
|
+
get '/happy'
|
30
|
+
last_response.status.should eq(200)
|
31
|
+
last_response.body.should eq("happy")
|
32
|
+
$seen_true.should be_nil
|
33
|
+
$seen_false.should eq([NilClass])
|
34
|
+
$seen_none.should eq([NilClass])
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
describe Rack::Robustness, 'last resort' do
|
3
|
+
include Rack::Test::Methods
|
4
|
+
|
5
|
+
before do
|
6
|
+
$seen_ex = nil
|
7
|
+
end
|
8
|
+
|
9
|
+
context 'when the response cannot be built and no catch all' do
|
10
|
+
let(:app){
|
11
|
+
mock_app do |g|
|
12
|
+
g.no_catch_all
|
13
|
+
g.response{|ex| NoSuchResponseClass.new }
|
14
|
+
g.ensure(true){|ex| $seen_ex = ex }
|
15
|
+
end
|
16
|
+
}
|
17
|
+
|
18
|
+
it 'reraises the internal error' do
|
19
|
+
lambda{
|
20
|
+
get '/argument-error'
|
21
|
+
}.should raise_error(NameError, /NoSuchResponseClass/)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'passes into the ensure block with the original error' do
|
25
|
+
lambda{
|
26
|
+
get '/argument-error'
|
27
|
+
}.should raise_error(NameError, /NoSuchResponseClass/)
|
28
|
+
$seen_ex.should be_a(ArgumentError)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'when the response cannot be built and catch all' do
|
33
|
+
let(:app){
|
34
|
+
mock_app do |g|
|
35
|
+
g.response{|ex| NoSuchResponseClass.new }
|
36
|
+
end
|
37
|
+
}
|
38
|
+
|
39
|
+
it 'falls back to last resort response' do
|
40
|
+
get '/argument-error'
|
41
|
+
last_response.status.should eq(500)
|
42
|
+
last_response.content_type.should eq("text/plain")
|
43
|
+
last_response.body.should eq("An internal error occured, sorry for the disagreement.")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'when an ensure block raises an error and no catch all' do
|
48
|
+
let(:app){
|
49
|
+
mock_app do |g|
|
50
|
+
g.no_catch_all
|
51
|
+
g.ensure{|ex| NoSuchResponseClass.new }
|
52
|
+
end
|
53
|
+
}
|
54
|
+
|
55
|
+
it 'reraises the internal error' do
|
56
|
+
lambda{
|
57
|
+
get '/argument-error'
|
58
|
+
}.should raise_error(NameError, /NoSuchResponseClass/)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context 'when an ensure block raises an error and catch all' do
|
63
|
+
let(:app){
|
64
|
+
mock_app do |g|
|
65
|
+
g.ensure{|ex| NoSuchResponseClass.new }
|
66
|
+
end
|
67
|
+
}
|
68
|
+
|
69
|
+
it 'reraises the internal error' do
|
70
|
+
get '/argument-error'
|
71
|
+
last_response.status.should eq(500)
|
72
|
+
last_response.content_type.should eq("text/plain")
|
73
|
+
last_response.body.should eq("An internal error occured, sorry for the disagreement.")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context 'when the response block fails and the ensure block uses the response object' do
|
78
|
+
let(:app){
|
79
|
+
mock_app do |g|
|
80
|
+
g.response{|ex| NoSuchResponseClass.new }
|
81
|
+
g.ensure{|ex| $seen_response = response }
|
82
|
+
end
|
83
|
+
}
|
84
|
+
|
85
|
+
before do
|
86
|
+
$seen_response = nil
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'sets a default response object for the ensure clause' do
|
90
|
+
get '/argument-error'
|
91
|
+
last_response.status.should eq(500)
|
92
|
+
last_response.content_type.should eq("text/plain")
|
93
|
+
last_response.body.should eq("An internal error occured, sorry for the disagreement.")
|
94
|
+
$seen_response.should_not be_nil
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
data/spec/test_rescue.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
describe Rack::Robustness, 'rescue' do
|
3
|
+
include Rack::Test::Methods
|
4
|
+
|
5
|
+
let(:app){
|
6
|
+
mock_app do |g|
|
7
|
+
g.status 400
|
8
|
+
g.rescue(ArgumentError){|ex| 'argument-error' }
|
9
|
+
g.rescue(SecurityError, 'security-error')
|
10
|
+
g.on(TypeError) {|ex| 'type-error' }
|
11
|
+
end
|
12
|
+
}
|
13
|
+
|
14
|
+
it 'correctly rescues specified errors' do
|
15
|
+
get '/argument-error'
|
16
|
+
last_response.status.should eq(400)
|
17
|
+
last_response.body.should eq("argument-error")
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'correctly support a non-block shortcut' do
|
21
|
+
get '/security-error'
|
22
|
+
last_response.status.should eq(400)
|
23
|
+
last_response.body.should eq("security-error")
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'is has a `on` alias' do
|
27
|
+
get '/type-error'
|
28
|
+
last_response.status.should eq(400)
|
29
|
+
last_response.body.should eq("type-error")
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
describe Rack::Robustness, 'response' do
|
3
|
+
include Rack::Test::Methods
|
4
|
+
|
5
|
+
class MyFooResponse < Rack::Response
|
6
|
+
|
7
|
+
def initialize(*args)
|
8
|
+
super
|
9
|
+
self['Content-Type'] = "application/json"
|
10
|
+
end
|
11
|
+
|
12
|
+
def each
|
13
|
+
yield("response text")
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
let(:app){
|
19
|
+
mock_app do |g|
|
20
|
+
g.status 400
|
21
|
+
g.response{|ex| MyFooResponse.new }
|
22
|
+
end
|
23
|
+
}
|
24
|
+
|
25
|
+
it 'correctly sets the status' do
|
26
|
+
get '/argument-error'
|
27
|
+
last_response.status.should eq(400)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'correctly sets the body' do
|
31
|
+
get '/argument-error'
|
32
|
+
last_response.body.should eq("response text")
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'correctly sets the content type' do
|
36
|
+
get '/argument-error'
|
37
|
+
last_response.content_type.should eq("application/json")
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
data/spec/test_robustness.rb
CHANGED
@@ -2,21 +2,6 @@ require 'spec_helper'
|
|
2
2
|
describe Rack::Robustness do
|
3
3
|
include Rack::Test::Methods
|
4
4
|
|
5
|
-
def mock_app(&bl)
|
6
|
-
Rack::Builder.new do
|
7
|
-
use Rack::Robustness, &bl
|
8
|
-
map '/happy' do
|
9
|
-
run lambda{|env| [200, {'Content-Type' => 'text/plain'}, ['happy']]}
|
10
|
-
end
|
11
|
-
map "/argument-error" do
|
12
|
-
run lambda{|env| raise ArgumentError, "an argument error" }
|
13
|
-
end
|
14
|
-
map "/type-error" do
|
15
|
-
run lambda{|env| raise TypeError, "a type error" }
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
5
|
shared_examples_for 'A transparent middleware for happy paths' do
|
21
6
|
|
22
7
|
it 'let happy responses unchanged' do
|
@@ -40,6 +25,13 @@ describe Rack::Robustness do
|
|
40
25
|
last_response.content_type.should eq("text/plain")
|
41
26
|
last_response.body.should eq("Sorry, a fatal error occured.")
|
42
27
|
end
|
28
|
+
|
29
|
+
it 'catches all exceptions by default' do
|
30
|
+
get '/security-error'
|
31
|
+
last_response.status.should eq(500)
|
32
|
+
last_response.content_type.should eq("text/plain")
|
33
|
+
last_response.body.should eq("Sorry, a fatal error occured.")
|
34
|
+
end
|
43
35
|
end
|
44
36
|
|
45
37
|
context 'with a status, content_type and body constants' do
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
describe "Rack::Robustness subclasses" do
|
3
|
+
include Rack::Test::Methods
|
4
|
+
|
5
|
+
class Shield < Rack::Robustness
|
6
|
+
self.body{|ex| ex.message }
|
7
|
+
self.rescue(ArgumentError){|ex| 400 }
|
8
|
+
end
|
9
|
+
|
10
|
+
let(:app){
|
11
|
+
mock_app(Shield)
|
12
|
+
}
|
13
|
+
|
14
|
+
it 'works as expected' do
|
15
|
+
get '/argument-error'
|
16
|
+
last_response.status.should eq(400)
|
17
|
+
last_response.body.should eq("an argument error")
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-robustness
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-04-17 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
@@ -96,7 +96,13 @@ files:
|
|
96
96
|
- Rakefile
|
97
97
|
- README.md
|
98
98
|
- spec/spec_helper.rb
|
99
|
+
- spec/test_context.rb
|
100
|
+
- spec/test_ensure.rb
|
101
|
+
- spec/test_last_resort.rb
|
102
|
+
- spec/test_rescue.rb
|
103
|
+
- spec/test_response.rb
|
99
104
|
- spec/test_robustness.rb
|
105
|
+
- spec/test_subclass.rb
|
100
106
|
- tasks/gem.rake
|
101
107
|
- tasks/spec_test.rake
|
102
108
|
homepage: https://github.com/blambeau/rack-robustness
|
@@ -113,7 +119,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
113
119
|
version: '0'
|
114
120
|
segments:
|
115
121
|
- 0
|
116
|
-
hash:
|
122
|
+
hash: -3037208557824391971
|
117
123
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
118
124
|
none: false
|
119
125
|
requirements:
|
@@ -122,10 +128,16 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
122
128
|
version: '0'
|
123
129
|
requirements: []
|
124
130
|
rubyforge_project:
|
125
|
-
rubygems_version: 1.8.
|
131
|
+
rubygems_version: 1.8.25
|
126
132
|
signing_key:
|
127
133
|
specification_version: 3
|
128
134
|
summary: Rack::Robustness, the rescue clause of your Rack stack.
|
129
135
|
test_files:
|
130
136
|
- spec/spec_helper.rb
|
137
|
+
- spec/test_context.rb
|
138
|
+
- spec/test_ensure.rb
|
139
|
+
- spec/test_last_resort.rb
|
140
|
+
- spec/test_rescue.rb
|
141
|
+
- spec/test_response.rb
|
131
142
|
- spec/test_robustness.rb
|
143
|
+
- spec/test_subclass.rb
|