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.
@@ -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. It scales from zero configuration (a default shield) to specific rescue clauses for specific errors.
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
- ## Examples
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
@@ -1,95 +1,210 @@
1
1
  module Rack
2
2
  class Robustness
3
3
 
4
- VERSION = "1.0.0".freeze
4
+ VERSION = "1.1.0".freeze
5
5
 
6
- NIL_HANDLER = lambda{|ex| nil }
7
-
8
- def initialize(app)
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
- def no_catch_all
26
- @catch_all = false
27
- end
15
+ NIL_HANDLER = lambda{|ex| nil }
28
16
 
29
- def on(ex_class, &bl)
30
- @handlers[ex_class] = bl || NIL_HANDLER
31
- end
17
+ def inherited(x)
18
+ x.reset
19
+ end
32
20
 
33
- def status(s=nil, &bl)
34
- @status = s || bl
35
- end
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
- def headers(h=nil, &bl)
38
- if h.nil?
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
- def content_type(ct=nil, &bl)
46
- headers('Content-Type' => ct || bl)
47
- end
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 body(b=nil, &bl)
50
- @body = b.nil? ? bl : (String===b ? [ b ] : b)
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
- @app.call(env)
95
+ dup.call!(env)
58
96
  rescue => ex
59
- handler = error_handler(ex.class)
60
- raise unless handler
61
- handle_response(handler, ex)
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
- def handle_response(response, ex)
67
- case response
68
- when NilClass then handle_response([@status, {}, @body], ex)
69
- when Fixnum then handle_response([response, {}, @body], ex)
70
- when String then handle_response([@status, {}, response], ex)
71
- when Hash then handle_response([@status, response, @body], ex)
72
- when Proc then handle_response(response.call(ex), ex)
73
- else
74
- status, headers, body = response.map{|x| handle_value(x, ex) }
75
- [ status,
76
- handle_value(@headers, ex).merge(headers),
77
- body ]
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 handle_value(value, ex)
82
- case value
83
- when Proc then value.call(ex)
84
- when Hash then value.each_with_object({}){|(k,v),h| h[k] = handle_value(v, ex)}
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
- value
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 error_handler(ex_class)
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
- @handlers.fetch(ex_class){ error_handler(ex_class.superclass) }
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
@@ -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
@@ -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
@@ -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
@@ -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.0.0
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-02-26 00:00:00.000000000 Z
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: 2934394792950840478
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.24
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