rack-robustness 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # 1.0.0 / 2013-02-26
2
+
3
+ * Enhancements
4
+
5
+ * Birthday!
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'http://rubygems.org'
2
+
3
+ group :development do
4
+ gem "rack", "~> 1.5"
5
+ gem "rake", "~> 10.0"
6
+ gem "rspec", "~> 2.12"
7
+ gem "rack-test", "~> 0.6"
8
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,25 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.1.3)
5
+ rack (1.5.2)
6
+ rack-test (0.6.2)
7
+ rack (>= 1.0)
8
+ rake (10.0.3)
9
+ rspec (2.12.0)
10
+ rspec-core (~> 2.12.0)
11
+ rspec-expectations (~> 2.12.0)
12
+ rspec-mocks (~> 2.12.0)
13
+ rspec-core (2.12.2)
14
+ rspec-expectations (2.12.1)
15
+ diff-lcs (~> 1.1.3)
16
+ rspec-mocks (2.12.2)
17
+
18
+ PLATFORMS
19
+ ruby
20
+
21
+ DEPENDENCIES
22
+ rack (~> 1.5)
23
+ rack-test (~> 0.6)
24
+ rake (~> 10.0)
25
+ rspec (~> 2.12)
data/LICENCE.md ADDED
@@ -0,0 +1,22 @@
1
+ # The MIT Licence
2
+
3
+ Copyright (c) 2013 - Bernard Lambeau
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Manifest.txt ADDED
@@ -0,0 +1,15 @@
1
+ rack-robustness.gemspec
2
+ rack-robustness.noespec
3
+ .gemtest
4
+ CHANGELOG.md
5
+ Gemfile
6
+ Gemfile.lock
7
+ bin/**/*
8
+ lib/**/*
9
+ LICENCE.md
10
+ Manifest.txt
11
+ Rakefile
12
+ README.md
13
+ spec/**/*
14
+ tasks/**/*
15
+ test/**/*
data/README.md ADDED
@@ -0,0 +1,219 @@
1
+ # Rack::Robustness, the rescue clause of your Rack stack.
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.
4
+
5
+ [![Build Status](https://secure.travis-ci.org/blambeau/rack-robustness.png)](http://travis-ci.org/blambeau/rack-robustness)
6
+ [![Dependency Status](https://gemnasium.com/blambeau/rack-robustness.png)](https://gemnasium.com/blambeau/rack-robustness)
7
+
8
+ ## Links
9
+
10
+ https://github.com/blambeau/rack-robustness
11
+
12
+ ## Why?
13
+
14
+ 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
+
16
+ * Exceptions occur, because you can't always test/control boundary conditions. E.g. your code can pro-actively test that a file exists before reading it, but it cannot pro-actively test that the user removes the network cable in the middle of a download.
17
+ * The behavior to adopt when obstacles occur is not necessary defined where the exception is thrown, but often higher in the call stack.
18
+ * 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
+
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:
21
+
22
+ ```java
23
+ try {
24
+ // main shield, typically in a main
25
+
26
+ try {
27
+ // try to achieve a goal here
28
+ } catch (...) {
29
+ // fallback to an alternative
30
+ }
31
+
32
+ // continue your flow
33
+
34
+ } catch (...) {
35
+ // something goes really wrong, inform the user as you can
36
+ }
37
+ ```
38
+
39
+ becomes:
40
+
41
+ ```ruby
42
+ class Main < Sinatra::Base
43
+
44
+ # main shield, main = rack top level
45
+ use Rack::Robustness do
46
+ # something goes really wrong, inform the user as you can
47
+ # probably a 5xx http status here
48
+ end
49
+
50
+ # continue your flow
51
+ use Other::Useful::Middlewares
52
+
53
+ use Rack::Robustness do
54
+ # fallback to an alternative
55
+ # 3xx, 4xx errors maybe
56
+ end
57
+
58
+ # try to achieve your goal through standard routes
59
+
60
+ end
61
+ ```
62
+
63
+ ## Examples
64
+
65
+ ```ruby
66
+ class App < Sinatra::Base
67
+
68
+ ##
69
+ # Catch everything but hide root causes, for security reasons, for instance.
70
+ #
71
+ # This handler should never be fired unless the application has a bug...
72
+ #
73
+ use Rack::Robustness do |g|
74
+ g.status 500
75
+ g.content_type 'text/plain'
76
+ g.body 'A fatal error occured.'
77
+ end
78
+
79
+ ##
80
+ # Some middleware here for logging, content length of whatever.
81
+ #
82
+ # Those middleware might fail, even if unlikely.
83
+ #
84
+ use ...
85
+ use ...
86
+
87
+ ##
88
+ # Catch some exceptions that denote client errors by convention in our app.
89
+ #
90
+ # Those exceptions are considered safe, so the message is sent to the user.
91
+ #
92
+ use Rack::Robustness do |g|
93
+ g.no_catch_all # do not catch all errors
94
+
95
+ g.status 400 # default status to 400, client error
96
+ g.content_type 'text/plain' # a default content-type, maybe
97
+ g.body{|ex| ex.message } # by default, send the message
98
+
99
+ # catch ArgumentError, it denotes a coercion error in our app
100
+ g.on(ArgumentError)
101
+
102
+ # we use SecurityError for handling forbidden accesses.
103
+ # The default status is 403 here
104
+ g.on(SecurityError){|ex| 403 }
105
+ end
106
+
107
+ get '/some/route/:id' do |id|
108
+ id = Integer(id) # will raise an ArgumentError if +id+ not an integer
109
+
110
+ ...
111
+ end
112
+
113
+ get '/private' do |id|
114
+ raise SecurityError unless logged?
115
+
116
+ ...
117
+ end
118
+
119
+ end
120
+ ```
121
+
122
+ ## Without configuration
123
+
124
+ ```ruby
125
+ ##
126
+ # Catches all errors.
127
+ #
128
+ # Respond with
129
+ # status: 500,
130
+ # headers: {'Content-Type' => 'text/plain'}
131
+ # body: [ "Sorry, an error occured." ]
132
+ #
133
+ use Rack::Robustness
134
+ ```
135
+
136
+ ## Specifying static status, headers and/or body
137
+
138
+ ```ruby
139
+ ##
140
+ # Catches all errors.
141
+ #
142
+ # Respond as specified.
143
+ #
144
+ use Rack::Robustness do |g|
145
+ g.status 400
146
+ g.headers 'Content-Type' => 'text/html'
147
+ g.content_type 'text/html' # shortcut over headers
148
+ g.body "<p>an error occured</p>"
149
+ end
150
+ ```
151
+
152
+ ## Specifying dynamic status, content_type and/or body
153
+
154
+ ```ruby
155
+ ##
156
+ # Catches all errors.
157
+ #
158
+ # Respond as specified.
159
+ #
160
+ use Rack::Robustness do |g|
161
+ g.status{|ex| ArgumentError===ex ? 400 : 500 }
162
+
163
+ # global dynamic headers
164
+ g.headers{|ex| {'Content-Type' => 'text/plain', ...} }
165
+
166
+ # local dynamic and/or static headers
167
+ g.headers 'Content-Type' => lambda{|ex| ... },
168
+ 'Foo' => 'Bar'
169
+
170
+ # dynamic content type
171
+ g.content_type{|ex| ...}
172
+
173
+ # dynamic body (String allowed here)
174
+ g.body{|ex| ex.message }
175
+ end
176
+ ```
177
+
178
+ ## Specific behavior for specific errors
179
+
180
+ ```ruby
181
+ ##
182
+ # Catches all errors using defaults as above
183
+ #
184
+ # Respond to specific errors as specified by 'on' clauses.
185
+ #
186
+ use Rack::Robustness do |g|
187
+ g.status 500 # this is the default behavior, as above
188
+ g.content_type 'text/plain' # ...
189
+
190
+ # Override status on TypeError and descendants
191
+ g.on(TypeError){|ex| 400 }
192
+
193
+ # Override body on ArgumentError and descendants
194
+ g.on(ArgumentError){|ex| ex.message }
195
+
196
+ # Override everything on SecurityError and descendants
197
+ # Default headers will be merged with returned ones so content-type will be
198
+ # "text/plain" unless specified below
199
+ g.on(SecurityError){|ex|
200
+ [ 403, { ... }, [ "Forbidden, sorry" ] ]
201
+ }
202
+ end
203
+ ```
204
+
205
+ ## Don't catch all!
206
+
207
+ ```ruby
208
+ ##
209
+ # Catches only errors specified in 'on' clauses, using defaults as above
210
+ #
211
+ # Re-raise unrecognized errors
212
+ #
213
+ use Rack::Robustness do |g|
214
+ g.no_catch_all
215
+
216
+ g.on(TypeError){|ex| 400 }
217
+ ...
218
+ end
219
+ ```
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ # We run tests by default
2
+ task :default => :test
3
+
4
+ #
5
+ # Install all tasks found in tasks folder
6
+ #
7
+ # See .rake files there for complete documentation.
8
+ #
9
+ Dir["tasks/*.rake"].each do |taskfile|
10
+ load taskfile
11
+ end
@@ -0,0 +1,96 @@
1
+ module Rack
2
+ class Robustness
3
+
4
+ VERSION = "1.0.0".freeze
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
20
+ end
21
+
22
+ ##
23
+ # Configuration
24
+
25
+ def no_catch_all
26
+ @catch_all = false
27
+ end
28
+
29
+ def on(ex_class, &bl)
30
+ @handlers[ex_class] = bl || NIL_HANDLER
31
+ end
32
+
33
+ def status(s=nil, &bl)
34
+ @status = s || bl
35
+ end
36
+
37
+ def headers(h=nil, &bl)
38
+ if h.nil?
39
+ @headers = bl
40
+ else
41
+ @headers.merge!(h)
42
+ end
43
+ end
44
+
45
+ def content_type(ct=nil, &bl)
46
+ headers('Content-Type' => ct || bl)
47
+ end
48
+
49
+ def body(b=nil, &bl)
50
+ @body = b.nil? ? bl : (String===b ? [ b ] : b)
51
+ end
52
+
53
+ ##
54
+ # Rack's call
55
+
56
+ def call(env)
57
+ @app.call(env)
58
+ rescue => ex
59
+ handler = error_handler(ex.class)
60
+ raise unless handler
61
+ handle_response(handler, ex)
62
+ end
63
+
64
+ private
65
+
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 ]
78
+ end
79
+ end
80
+
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)}
85
+ else
86
+ value
87
+ end
88
+ end
89
+
90
+ def error_handler(ex_class)
91
+ return nil if ex_class.nil?
92
+ @handlers.fetch(ex_class){ error_handler(ex_class.superclass) }
93
+ end
94
+
95
+ end # class Robustness
96
+ end # module Rack
@@ -0,0 +1,188 @@
1
+ # We require your library, mainly to have access to the VERSION number.
2
+ # Feel free to set $version manually.
3
+ $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
4
+ require "rack/robustness"
5
+ $version = Rack::Robustness::VERSION.dup.to_s
6
+
7
+ #
8
+ # This is your Gem specification. Default values are provided so that your library
9
+ # should be correctly packaged given what you have described in the .noespec file.
10
+ #
11
+ Gem::Specification.new do |s|
12
+
13
+ ################################################################### ABOUT YOUR GEM
14
+
15
+ # Gem name (required)
16
+ s.name = "rack-robustness"
17
+
18
+ # Gem version (required)
19
+ s.version = $version
20
+
21
+ # A short summary of this gem
22
+ #
23
+ # This is displayed in `gem list -d`.
24
+ s.summary = "Rack::Robustness, the rescue clause of your Rack stack."
25
+
26
+ # A long description of this gem (required)
27
+ #
28
+ # The description should be more detailed than the summary. For example,
29
+ # you might wish to copy the entire README into the description.
30
+ s.description = "Rack::Robustness provides you with an easy way to handle errors in your stack, for making web applications more robust."
31
+
32
+ # The URL of this gem home page (optional)
33
+ s.homepage = "https://github.com/blambeau/rack-robustness"
34
+
35
+ # Gem publication date (required but auto)
36
+ #
37
+ # Today is automatically used by default, uncomment only if
38
+ # you know what you do!
39
+ #
40
+ # s.date = Time.now.strftime('%Y-%m-%d')
41
+
42
+ # The license(s) for the library. Each license must be a short name, no
43
+ # more than 64 characters.
44
+ #
45
+ # s.licences = %w{}
46
+
47
+ # The rubyforge project this gem lives under (optional)
48
+ #
49
+ # s.rubyforge_project = nil
50
+
51
+ ################################################################### ABOUT THE AUTHORS
52
+
53
+ # The list of author names who wrote this gem.
54
+ #
55
+ # If you are providing multiple authors and multiple emails they should be
56
+ # in the same order.
57
+ #
58
+ s.authors = ["Bernard Lambeau"]
59
+
60
+ # Contact emails for this gem
61
+ #
62
+ # If you are providing multiple authors and multiple emails they should be
63
+ # in the same order.
64
+ #
65
+ # NOTE: Somewhat strangly this attribute is always singular!
66
+ # Don't replace by s.emails = ...
67
+ s.email = ["blambeau@gmail.com"]
68
+
69
+ ################################################################### PATHS, FILES, BINARIES
70
+
71
+ # Paths in the gem to add to $LOAD_PATH when this gem is
72
+ # activated (required).
73
+ #
74
+ # The default 'lib' is typically sufficient.
75
+ s.require_paths = ["lib"]
76
+
77
+ # Files included in this gem.
78
+ #
79
+ # By default, we take all files included in the Manifest.txt file on root
80
+ # of the project. Entries of the manifest are interpreted as Dir[...]
81
+ # patterns so that lazy people may use wilcards like lib/**/*
82
+ #
83
+ here = File.expand_path(File.dirname(__FILE__))
84
+ s.files = File.readlines(File.join(here, 'Manifest.txt')).
85
+ inject([]){|files, pattern| files + Dir[File.join(here, pattern.strip)]}.
86
+ collect{|x| x[(1+here.size)..-1]}
87
+
88
+ # Test files included in this gem.
89
+ #
90
+ s.test_files = Dir["test/**/*"] + Dir["spec/**/*"]
91
+
92
+ # The path in the gem for executable scripts (optional)
93
+ #
94
+ s.bindir = "bin"
95
+
96
+ # Executables included in the gem.
97
+ #
98
+ s.executables = (Dir["bin/*"]).collect{|f| File.basename(f)}
99
+
100
+ ################################################################### REQUIREMENTS & INSTALL
101
+ # Remember the gem version requirements operators and schemes:
102
+ # = Equals version
103
+ # != Not equal to version
104
+ # > Greater than version
105
+ # < Less than version
106
+ # >= Greater than or equal to
107
+ # <= Less than or equal to
108
+ # ~> Approximately greater than
109
+ #
110
+ # Don't forget to have a look at http://lmgtfy.com/?q=Ruby+Versioning+Policies
111
+ # for setting your gem version.
112
+ #
113
+ # For your requirements to other gems, remember that
114
+ # ">= 2.2.0" (optimistic: specify minimal version)
115
+ # ">= 2.2.0", "< 3.0" (pessimistic: not greater than the next major)
116
+ # "~> 2.2" (shortcut for ">= 2.2.0", "< 3.0")
117
+ # "~> 2.2.0" (shortcut for ">= 2.2.0", "< 2.3.0")
118
+ #
119
+
120
+ #
121
+ # One call to add_dependency('gem_name', 'gem version requirement') for each
122
+ # runtime dependency. These gems will be installed with your gem.
123
+ # One call to add_development_dependency('gem_name', 'gem version requirement')
124
+ # for each development dependency. These gems are required for developers
125
+ #
126
+ s.add_development_dependency("rake", "~> 10.0")
127
+ s.add_development_dependency("rspec", "~> 2.12")
128
+ s.add_development_dependency("rack", "~> 1.5")
129
+ s.add_development_dependency("rack-test", "~> 0.6")
130
+
131
+
132
+ # The version of ruby required by this gem
133
+ #
134
+ # Uncomment and set this if your gem requires specific ruby versions.
135
+ #
136
+ # s.required_ruby_version = ">= 0"
137
+
138
+ # The RubyGems version required by this gem
139
+ #
140
+ # s.required_rubygems_version = ">= 0"
141
+
142
+ # The platform this gem runs on. See Gem::Platform for details.
143
+ #
144
+ # s.platform = nil
145
+
146
+ # Extensions to build when installing the gem.
147
+ #
148
+ # Valid types of extensions are extconf.rb files, configure scripts
149
+ # and rakefiles or mkrf_conf files.
150
+ #
151
+ s.extensions = []
152
+
153
+ # External (to RubyGems) requirements that must be met for this gem to work.
154
+ # It’s simply information for the user.
155
+ #
156
+ s.requirements = nil
157
+
158
+ # A message that gets displayed after the gem is installed
159
+ #
160
+ # Uncomment and set this if you want to say something to the user
161
+ # after gem installation
162
+ #
163
+ s.post_install_message = nil
164
+
165
+ ################################################################### SECURITY
166
+
167
+ # The key used to sign this gem. See Gem::Security for details.
168
+ #
169
+ # s.signing_key = nil
170
+
171
+ # The certificate chain used to sign this gem. See Gem::Security for
172
+ # details.
173
+ #
174
+ # s.cert_chain = []
175
+
176
+ ################################################################### RDOC
177
+
178
+ # An ARGV style array of options to RDoc
179
+ #
180
+ # See 'rdoc --help' about this
181
+ #
182
+ s.rdoc_options = []
183
+
184
+ # Extra files to add to RDoc such as README
185
+ #
186
+ s.extra_rdoc_files = Dir["README.md"] + Dir["CHANGELOG.md"] + Dir["LICENCE.md"]
187
+
188
+ end
@@ -0,0 +1,11 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'rack'
3
+ require 'rack/robustness'
4
+ require 'rack/test'
5
+
6
+ module SpecHelpers
7
+ end
8
+
9
+ RSpec.configure do |c|
10
+ c.include SpecHelpers
11
+ end
@@ -0,0 +1,253 @@
1
+ require 'spec_helper'
2
+ describe Rack::Robustness do
3
+ include Rack::Test::Methods
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
+ shared_examples_for 'A transparent middleware for happy paths' do
21
+
22
+ it 'let happy responses unchanged' do
23
+ get '/happy'
24
+ last_response.status.should eq(200)
25
+ last_response.content_type.should eq('text/plain')
26
+ last_response.body.should eq('happy')
27
+ end
28
+ end
29
+
30
+ context 'with the default configuration' do
31
+ let(:app){
32
+ mock_app
33
+ }
34
+
35
+ it_should_behave_like 'A transparent middleware for happy paths'
36
+
37
+ it 'set a status 500 with a standard error message by default' do
38
+ get '/argument-error'
39
+ last_response.status.should eq(500)
40
+ last_response.content_type.should eq("text/plain")
41
+ last_response.body.should eq("Sorry, a fatal error occured.")
42
+ end
43
+ end
44
+
45
+ context 'with a status, content_type and body constants' do
46
+ let(:app){
47
+ mock_app do |g|
48
+ g.status 501
49
+ g.content_type "text/test"
50
+ g.body "An error occured"
51
+ end
52
+ }
53
+
54
+ it_should_behave_like 'A transparent middleware for happy paths'
55
+
56
+ it 'set the specified status and body on errors' do
57
+ get '/argument-error'
58
+ last_response.status.should eq(501)
59
+ last_response.content_type.should eq("text/test")
60
+ last_response.body.should eq("An error occured")
61
+ end
62
+ end
63
+
64
+ context 'with headers' do
65
+ let(:app){
66
+ mock_app do |g|
67
+ g.headers 'Content-Type' => 'text/test',
68
+ 'Foo' => 'Bar'
69
+ end
70
+ }
71
+
72
+ it_should_behave_like 'A transparent middleware for happy paths'
73
+
74
+ it 'set the specified headers on error' do
75
+ get '/argument-error'
76
+ last_response.headers['Foo'].should eq('Bar')
77
+ last_response.content_type.should eq("text/test")
78
+ end
79
+ end
80
+
81
+ context 'with a dynamic status, content_type and body' do
82
+ let(:app){
83
+ mock_app do |g|
84
+ g.status {|ex| ArgumentError===ex ? 400 : 500}
85
+ g.content_type{|ex| ArgumentError===ex ? "text/arg" : 'text/other'}
86
+ g.body {|ex| ex.message }
87
+ end
88
+ }
89
+
90
+ it_should_behave_like 'A transparent middleware for happy paths'
91
+
92
+ it 'correctly sets the status, content_type and body on ArgumentError' do
93
+ get '/argument-error'
94
+ last_response.status.should eq(400)
95
+ last_response.content_type.should eq('text/arg')
96
+ last_response.body.should eq('an argument error')
97
+ end
98
+
99
+ it 'correctly sets the status, content_type and body on TypeError' do
100
+ get '/type-error'
101
+ last_response.status.should eq(500)
102
+ last_response.content_type.should eq('text/other')
103
+ last_response.body.should eq('a type error')
104
+ end
105
+ end
106
+
107
+ context 'with dynamic headers I' do
108
+ let(:app){
109
+ mock_app do |g|
110
+ g.headers{|ex|
111
+ {'Content-Type' => ArgumentError===ex ? "text/arg" : 'text/other' }
112
+ }
113
+ end
114
+ }
115
+
116
+ it_should_behave_like 'A transparent middleware for happy paths'
117
+
118
+ it 'correctly sets the specified headers on an ArgumentError' do
119
+ get '/argument-error'
120
+ last_response.content_type.should eq("text/arg")
121
+ end
122
+
123
+ it 'correctly sets the specified headers on a TypeError' do
124
+ get '/type-error'
125
+ last_response.content_type.should eq("text/other")
126
+ end
127
+ end
128
+
129
+ context 'with dynamic headers II' do
130
+ let(:app){
131
+ mock_app do |g|
132
+ g.headers 'Content-Type' => lambda{|ex| ArgumentError===ex ? "text/arg" : 'text/other'},
133
+ 'Foo' => 'Bar'
134
+ end
135
+ }
136
+
137
+ it_should_behave_like 'A transparent middleware for happy paths'
138
+
139
+ it 'correctly sets the specified headers on an ArgumentError' do
140
+ get '/argument-error'
141
+ last_response.headers['Foo'].should eq('Bar')
142
+ last_response.content_type.should eq("text/arg")
143
+ end
144
+
145
+ it 'correctly sets the specified headers on a TypeError' do
146
+ get '/type-error'
147
+ last_response.headers['Foo'].should eq('Bar')
148
+ last_response.content_type.should eq("text/other")
149
+ end
150
+ end
151
+
152
+ context 'when responding to specific errors with a full response' do
153
+ let(:app){
154
+ mock_app do |g|
155
+ g.headers 'Foo' => 'Bar', 'Content-Type' => 'default/one'
156
+ g.on(ArgumentError){|ex| [401, {'Content-Type' => 'text/arg'}, [ ex.message ] ] }
157
+ g.on(TypeError){|ex| [402, {}, [ ex.message ] ] }
158
+ end
159
+ }
160
+
161
+ after do
162
+ # if merges the default headers in any way
163
+ last_response.headers['Foo'].should eq('Bar')
164
+ end
165
+
166
+ it 'uses the response on ArgumentError' do
167
+ get '/argument-error'
168
+ last_response.status.should eq(401)
169
+ last_response.content_type.should eq('text/arg')
170
+ last_response.body.should eq("an argument error")
171
+ end
172
+
173
+ it 'uses the response on TypeError' do
174
+ get '/type-error'
175
+ last_response.status.should eq(402)
176
+ last_response.content_type.should eq('default/one')
177
+ last_response.body.should eq("a type error")
178
+ end
179
+ end
180
+
181
+ context 'when responding to specific errors with a single status' do
182
+ let(:app){
183
+ mock_app do |g|
184
+ g.on(ArgumentError){|ex| 401 }
185
+ end
186
+ }
187
+
188
+ it 'uses the status and fallback to defaults for the rest' do
189
+ get '/argument-error'
190
+ last_response.status.should eq(401)
191
+ last_response.content_type.should eq('text/plain')
192
+ last_response.body.should eq("Sorry, a fatal error occured.")
193
+ end
194
+ end
195
+
196
+ context 'when responding to specific errors with a single body' do
197
+ let(:app){
198
+ mock_app do |g|
199
+ g.on(ArgumentError){|ex| ex.message }
200
+ end
201
+ }
202
+
203
+ it 'uses it as body and fallback to defaults for the rest' do
204
+ get '/argument-error'
205
+ last_response.status.should eq(500)
206
+ last_response.content_type.should eq('text/plain')
207
+ last_response.body.should eq("an argument error")
208
+ end
209
+ end
210
+
211
+ context 'when configured with no_catch_all' do
212
+ let(:app){
213
+ mock_app do |g|
214
+ g.no_catch_all
215
+ g.on(ArgumentError){|ex| 401 }
216
+ end
217
+ }
218
+
219
+ it 'matches known errors' do
220
+ get '/argument-error'
221
+ last_response.status.should eq(401)
222
+ end
223
+
224
+ it 'raises on unknown error' do
225
+ lambda{
226
+ get '/type-error'
227
+ }.should raise_error(TypeError)
228
+ end
229
+ end
230
+
231
+ context 'when responding to specific errors without body' do
232
+ let(:app){
233
+ mock_app do |g|
234
+ g.no_catch_all
235
+ g.status(401)
236
+ g.on(ArgumentError)
237
+ end
238
+ }
239
+
240
+ it 'matches known errors' do
241
+ get '/argument-error'
242
+ last_response.status.should eq(401)
243
+ last_response.body.should eq("Sorry, a fatal error occured.")
244
+ end
245
+
246
+ it 'raises on unknown error' do
247
+ lambda{
248
+ get '/type-error'
249
+ }.should raise_error(TypeError)
250
+ end
251
+ end
252
+
253
+ end
data/tasks/gem.rake ADDED
@@ -0,0 +1,73 @@
1
+ # Installs rake tasks for gemming and packaging
2
+ #
3
+ # This file installs the 'rake package', 'rake gem' tasks and associates
4
+ # (clobber_package, repackage, ...). It is automatically generated by Noe
5
+ # from your .noespec file, and should therefore be configured there, under
6
+ # the variables/rake_tasks/gem entry, as illustrated below:
7
+ #
8
+ # variables:
9
+ # rake_tasks:
10
+ # gem:
11
+ # package_dir: pkg
12
+ # need_tar: false
13
+ # need_tar_gz: false
14
+ # need_tar_bz2: false
15
+ # need_zip: false
16
+ # ...
17
+ #
18
+ # If you have specific needs requiring manual intervention on this file,
19
+ # don't forget to set safe-override to false in your noe specification:
20
+ #
21
+ # template-info:
22
+ # manifest:
23
+ # tasks/gem.rake:
24
+ # safe-override: false
25
+ #
26
+ begin
27
+ require 'rubygems/package_task'
28
+
29
+ # Dynamically load the gem spec
30
+ gemspec_file = File.expand_path('../../rack-robustness.gemspec', __FILE__)
31
+ gemspec = Kernel.eval(File.read(gemspec_file))
32
+
33
+ Gem::PackageTask.new(gemspec) do |t|
34
+
35
+ # Name of the package
36
+ t.name = gemspec.name
37
+
38
+ # Version of the package
39
+ t.version = gemspec.version
40
+
41
+ # Directory used to store the package files
42
+ t.package_dir = "pkg"
43
+
44
+ # True if a gzipped tar file (tgz) should be produced
45
+ t.need_tar = false
46
+
47
+ # True if a gzipped tar file (tar.gz) should be produced
48
+ t.need_tar_gz = false
49
+
50
+ # True if a bzip2'd tar file (tar.bz2) should be produced
51
+ t.need_tar_bz2 = false
52
+
53
+ # True if a zip file should be produced (default is false)
54
+ t.need_zip = false
55
+
56
+ # List of files to be included in the package.
57
+ t.package_files = gemspec.files
58
+
59
+ # Tar command for gzipped or bzip2ed archives.
60
+ t.tar_command = "tar"
61
+
62
+ # Zip command for zipped archives.
63
+ t.zip_command = "zip"
64
+
65
+ end
66
+ rescue LoadError
67
+ task :gem do
68
+ abort 'rubygems/package_task is not available. You should verify your rubygems installation'
69
+ end
70
+ task :package do
71
+ abort 'rubygems/package_task is not available. You should verify your rubygems installation'
72
+ end
73
+ end
@@ -0,0 +1,71 @@
1
+ # Installs a rake task for for running examples written using rspec.
2
+ #
3
+ # This file installs the 'rake spec_test' (aliased as 'rake spec') as well as
4
+ # extends 'rake test' to run spec tests, if any. It is automatically generated
5
+ # by Noe from your .noespec file, and should therefore be configured there,
6
+ # under the variables/rake_tasks/spec_test entry, as illustrated below:
7
+ #
8
+ # variables:
9
+ # rake_tasks:
10
+ # spec_test:
11
+ # pattern: spec/**/*_spec.rb
12
+ # verbose: true
13
+ # rspec_opts: [--color, --backtrace]
14
+ # ...
15
+ #
16
+ # If you have specific needs requiring manual intervention on this file,
17
+ # don't forget to set safe-override to false in your noe specification:
18
+ #
19
+ # template-info:
20
+ # manifest:
21
+ # tasks/spec_test.rake:
22
+ # safe-override: false
23
+ #
24
+ # This file has been written to conform to RSpec v2.4.0. More information about
25
+ # rspec and options of the rake task defined below can be found on
26
+ # http://relishapp.com/rspec
27
+ #
28
+ begin
29
+ require "rspec/core/rake_task"
30
+ desc "Run RSpec code examples"
31
+ RSpec::Core::RakeTask.new(:spec_test) do |t|
32
+ # Glob pattern to match files.
33
+ t.pattern = "spec/**/test_*.rb"
34
+
35
+ # Whether or not to fail Rake when an error occurs (typically when
36
+ # examples fail).
37
+ t.fail_on_error = true
38
+
39
+ # A message to print to stderr when there are failures.
40
+ t.failure_message = nil
41
+
42
+ # Use verbose output. If this is set to true, the task will print the
43
+ # executed spec command to stdout.
44
+ t.verbose = true
45
+
46
+ # Use rcov for code coverage?
47
+ t.rcov = false
48
+
49
+ # Path to rcov.
50
+ t.rcov_path = "rcov"
51
+
52
+ # Command line options to pass to rcov. See 'rcov --help' about this
53
+ t.rcov_opts = []
54
+
55
+ # Command line options to pass to ruby. See 'ruby --help' about this
56
+ t.ruby_opts = []
57
+
58
+ # Path to rspec
59
+ t.rspec_path = "rspec"
60
+
61
+ # Command line options to pass to rspec. See 'rspec --help' about this
62
+ t.rspec_opts = ["--color", "--backtrace"]
63
+ end
64
+ rescue LoadError => ex
65
+ task :spec_test do
66
+ abort 'rspec is not available. In order to run spec, you must: gem install rspec'
67
+ end
68
+ ensure
69
+ task :spec => [:spec_test]
70
+ task :test => [:spec_test]
71
+ end
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-robustness
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Bernard Lambeau
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-02-26 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '10.0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '10.0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '2.12'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '2.12'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rack
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '1.5'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '1.5'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rack-test
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: '0.6'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: '0.6'
78
+ description: Rack::Robustness provides you with an easy way to handle errors in your
79
+ stack, for making web applications more robust.
80
+ email:
81
+ - blambeau@gmail.com
82
+ executables: []
83
+ extensions: []
84
+ extra_rdoc_files:
85
+ - README.md
86
+ - CHANGELOG.md
87
+ - LICENCE.md
88
+ files:
89
+ - rack-robustness.gemspec
90
+ - CHANGELOG.md
91
+ - Gemfile
92
+ - Gemfile.lock
93
+ - lib/rack/robustness.rb
94
+ - LICENCE.md
95
+ - Manifest.txt
96
+ - Rakefile
97
+ - README.md
98
+ - spec/spec_helper.rb
99
+ - spec/test_robustness.rb
100
+ - tasks/gem.rake
101
+ - tasks/spec_test.rake
102
+ homepage: https://github.com/blambeau/rack-robustness
103
+ licenses: []
104
+ post_install_message:
105
+ rdoc_options: []
106
+ require_paths:
107
+ - lib
108
+ required_ruby_version: !ruby/object:Gem::Requirement
109
+ none: false
110
+ requirements:
111
+ - - ! '>='
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
114
+ segments:
115
+ - 0
116
+ hash: 2934394792950840478
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ none: false
119
+ requirements:
120
+ - - ! '>='
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ requirements: []
124
+ rubyforge_project:
125
+ rubygems_version: 1.8.24
126
+ signing_key:
127
+ specification_version: 3
128
+ summary: Rack::Robustness, the rescue clause of your Rack stack.
129
+ test_files:
130
+ - spec/spec_helper.rb
131
+ - spec/test_robustness.rb