rack-robustness 1.0.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![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/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
|