rack-robustness 1.0.0 → 1.1.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.
- 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
|
[](http://travis-ci.org/blambeau/rack-robustness)
|
6
6
|
[](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
|