rack-robustness 1.0.0 → 1.2.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/CHANGELOG.md +55 -1
- data/Gemfile +3 -3
- data/Gemfile.lock +22 -14
- data/README.md +80 -8
- data/Rakefile +1 -3
- 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 +42 -50
- data/spec/test_subclass.rb +20 -0
- data/tasks/gem.rake +25 -59
- data/tasks/test.rake +11 -0
- metadata +32 -34
- data/tasks/spec_test.rake +0 -71
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 46cc2e7cca1b36e536f5e2e26a5e48c93e793b4c144944001464120fd6b01afa
|
4
|
+
data.tar.gz: 625f614030368429b7a2cd9290adfdab5ca1ec668784df94214b7a16528c7663
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6306779c8ba88fd038e9184604a995bd4694ce3f4d98428fbf2013a71f4cb8ede0d386df8b967c442baf7735f87a84773aab910b5e2b29b607182f90459042f6
|
7
|
+
data.tar.gz: 8dde80698d33fdc1bde9c605f69dfba631bbbe0649084d6c5b4a810f66bf9c66c818f81e56d7aab87846f0ef9a44122aad19e1702728d8cbec7d96d6a28b841f
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,58 @@
|
|
1
|
-
|
1
|
+
## 1.2.0 / 2023-06-09
|
2
|
+
|
3
|
+
* Modernize with test matrix on ruby 2.7, 3.1, and 3.2
|
4
|
+
|
5
|
+
* Fix usage of Fixnum to be compatible with Ruby 3.x
|
6
|
+
|
7
|
+
## 1.1.0 / 2013-04-16
|
8
|
+
|
9
|
+
* Fixed catching of non standard errors (e.g. SecurityError)
|
10
|
+
|
11
|
+
* Global headers are now correctly overrided by specific per-exception headers
|
12
|
+
|
13
|
+
* Renamed `#on` as `#rescue` for better capturing semantics of `on` blocks (now an alias).
|
14
|
+
|
15
|
+
* Added last resort exception handling if an error occurs during exception handling itself.
|
16
|
+
In `no_catch_all` mode, the exception is simply reraised; otherwise a default 500 error
|
17
|
+
is returned with a safe message.
|
18
|
+
|
19
|
+
* Added a shortcut form for `#rescue` clauses allowing values directly, e.g.,
|
20
|
+
|
21
|
+
use Rack::Robustness do |g|
|
22
|
+
g.rescue(SecurityError, 403)
|
23
|
+
end
|
24
|
+
|
25
|
+
* Added suppport for ensure clause(s), always called after `rescue` blocks
|
26
|
+
|
27
|
+
* Rack's `env` is now available in all error handling blocks, e.g.,
|
28
|
+
|
29
|
+
use Rack::Robustness do |g|
|
30
|
+
g.status{|ex| ... env ... }
|
31
|
+
g.body {|ex| ... env ... }
|
32
|
+
g.rescue(SecurityError){|ex| ... env ... }
|
33
|
+
g.ensure{|ex| ... env ... }
|
34
|
+
end
|
35
|
+
|
36
|
+
* Similarly, Rack::Robustness now internally uses instances of Rack::Request and Rack::Response;
|
37
|
+
`request` and `response` are available in all blocks. The specific Response
|
38
|
+
object to use can be built using the `response` DSL method, e.g.,
|
39
|
+
|
40
|
+
use Rack::Robustness do |g|
|
41
|
+
g.response{|ex| MyOwnRackResponse.new }
|
42
|
+
end
|
43
|
+
|
44
|
+
* Rack::Robustness may now be subclassed as an alternative to inline `use`, e.g.
|
45
|
+
|
46
|
+
class Shield < Rack::Robustness
|
47
|
+
self.body {|ex| ... }
|
48
|
+
self.rescue(SecurityError){|ex| ... }
|
49
|
+
...
|
50
|
+
end
|
51
|
+
|
52
|
+
# in Rack-based configuration
|
53
|
+
use Shield
|
54
|
+
|
55
|
+
## 1.0.0 / 2013-02-26
|
2
56
|
|
3
57
|
* Enhancements
|
4
58
|
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,25 +1,33 @@
|
|
1
1
|
GEM
|
2
2
|
remote: http://rubygems.org/
|
3
3
|
specs:
|
4
|
-
diff-lcs (1.
|
5
|
-
rack (
|
4
|
+
diff-lcs (1.5.0)
|
5
|
+
rack (2.2.7)
|
6
6
|
rack-test (0.6.2)
|
7
7
|
rack (>= 1.0)
|
8
|
-
rake (
|
9
|
-
rspec (
|
10
|
-
rspec-core (~>
|
11
|
-
rspec-expectations (~>
|
12
|
-
rspec-mocks (~>
|
13
|
-
rspec-core (
|
14
|
-
|
15
|
-
|
16
|
-
|
8
|
+
rake (13.0.6)
|
9
|
+
rspec (3.12.0)
|
10
|
+
rspec-core (~> 3.12.0)
|
11
|
+
rspec-expectations (~> 3.12.0)
|
12
|
+
rspec-mocks (~> 3.12.0)
|
13
|
+
rspec-core (3.12.2)
|
14
|
+
rspec-support (~> 3.12.0)
|
15
|
+
rspec-expectations (3.12.3)
|
16
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
17
|
+
rspec-support (~> 3.12.0)
|
18
|
+
rspec-mocks (3.12.5)
|
19
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
20
|
+
rspec-support (~> 3.12.0)
|
21
|
+
rspec-support (3.12.0)
|
17
22
|
|
18
23
|
PLATFORMS
|
19
24
|
ruby
|
20
25
|
|
21
26
|
DEPENDENCIES
|
22
|
-
rack (~>
|
27
|
+
rack (~> 2)
|
23
28
|
rack-test (~> 0.6)
|
24
|
-
rake (~>
|
25
|
-
rspec (~>
|
29
|
+
rake (~> 13)
|
30
|
+
rspec (~> 3)
|
31
|
+
|
32
|
+
BUNDLED WITH
|
33
|
+
2.4.6
|
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/Rakefile
CHANGED
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.2.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 Integer 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
|
+
expect(last_response.status).to eq(400)
|
37
|
+
expect(last_response.body).to eq('argument-error')
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'executes the ensure block as well' do
|
41
|
+
get '/argument-error'
|
42
|
+
expect($seen_ex).to 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
|
+
expect(last_response.status).to eq(400)
|
22
|
+
expect(last_response.body).to eq("error")
|
23
|
+
expect($seen_true).to eq([ArgumentError])
|
24
|
+
expect($seen_false).to eq([ArgumentError])
|
25
|
+
expect($seen_none).to eq([ArgumentError])
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should not be called when explicit bypass on happy paths' do
|
29
|
+
get '/happy'
|
30
|
+
expect(last_response.status).to eq(200)
|
31
|
+
expect(last_response.body).to eq("happy")
|
32
|
+
expect($seen_true).to be_nil
|
33
|
+
expect($seen_false).to eq([NilClass])
|
34
|
+
expect($seen_none).to eq([NilClass])
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|