rack-rescue 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. data/Users/dneighman/Projects/rack-rescue/Gemfile +15 -0
  2. data/Users/dneighman/Projects/rack-rescue/Gemfile.lock +93 -0
  3. data/Users/dneighman/Projects/rack-rescue/LICENSE +20 -0
  4. data/Users/dneighman/Projects/rack-rescue/README.textile +80 -0
  5. data/Users/dneighman/Projects/rack-rescue/Rakefile +26 -0
  6. data/Users/dneighman/Projects/rack-rescue/TODO.textile +7 -0
  7. data/Users/dneighman/Projects/rack-rescue/lib/rack-rescue.rb +1 -0
  8. data/Users/dneighman/Projects/rack-rescue/lib/rack/rescue.rb +93 -0
  9. data/Users/dneighman/Projects/rack-rescue/lib/rack/rescue/default_exceptions.rb +16 -0
  10. data/Users/dneighman/Projects/rack-rescue/lib/rack/rescue/exceptions.rb +107 -0
  11. data/Users/dneighman/Projects/rack-rescue/lib/rack/rescue/handler.rb +124 -0
  12. data/Users/dneighman/Projects/rack-rescue/lib/rack/rescue/responder.rb +21 -0
  13. data/Users/dneighman/Projects/rack-rescue/lib/rack/rescue/templates/rack_rescue_templates/error.development.html.erb +19 -0
  14. data/Users/dneighman/Projects/rack-rescue/lib/rack/rescue/templates/rack_rescue_templates/error.html.erb +15 -0
  15. data/Users/dneighman/Projects/rack-rescue/lib/rack/rescue/templates/rack_rescue_templates/error.text.erb +14 -0
  16. data/Users/dneighman/Projects/rack-rescue/lib/rack/rescue/templates/rack_rescue_templates/not_found.text.erb +4 -0
  17. data/Users/dneighman/Projects/rack-rescue/rack-rescue.gemspec +26 -0
  18. data/Users/dneighman/Projects/rack-rescue/spec/rack-rescue_spec.rb +151 -0
  19. data/Users/dneighman/Projects/rack-rescue/spec/rack/rescue/exceptions_spec.rb +85 -0
  20. data/Users/dneighman/Projects/rack-rescue/spec/rack/rescue/fixtures/alternate_errors/alternate_exceptions.foo_env.html.erb +1 -0
  21. data/Users/dneighman/Projects/rack-rescue/spec/rack/rescue/fixtures/alternate_errors/alternate_exceptions.html.erb +1 -0
  22. data/Users/dneighman/Projects/rack-rescue/spec/rack/rescue/fixtures/alternate_errors/custom_exception.text.erb +1 -0
  23. data/Users/dneighman/Projects/rack-rescue/spec/rack/rescue/fixtures/alternate_errors/error.xml.erb +5 -0
  24. data/Users/dneighman/Projects/rack-rescue/spec/rack/rescue/fixtures/alternate_errors/inherit_from_error_backtrace.text.erb +4 -0
  25. data/Users/dneighman/Projects/rack-rescue/spec/rack/rescue/fixtures/alternate_errors/inherit_from_error_header.text.erb +4 -0
  26. data/Users/dneighman/Projects/rack-rescue/spec/rack/rescue/fixtures/alternate_errors/inherit_from_error_message.text.erb +4 -0
  27. data/Users/dneighman/Projects/rack-rescue/spec/rack/rescue/fixtures/alternate_errors/not_found.text.erb +7 -0
  28. data/Users/dneighman/Projects/rack-rescue/spec/rack/rescue/fixtures/alternate_errors/not_found.xml.erb +3 -0
  29. data/Users/dneighman/Projects/rack-rescue/spec/rack/rescue/fixtures/layouts/custom_error_layout.xml.erb +4 -0
  30. data/Users/dneighman/Projects/rack-rescue/spec/rack/rescue/fixtures/layouts/error.html.erb +7 -0
  31. data/Users/dneighman/Projects/rack-rescue/spec/rack/rescue/handler_spec.rb +112 -0
  32. data/Users/dneighman/Projects/rack-rescue/spec/spec.opts +2 -0
  33. data/Users/dneighman/Projects/rack-rescue/spec/spec_helper.rb +22 -0
  34. metadata +213 -0
@@ -0,0 +1,15 @@
1
+ # A sample Gemfile
2
+ source :rubygems
3
+
4
+ gem 'rake'
5
+ gem 'rack'
6
+ gem 'extlib'
7
+ gem 'hashie'
8
+ gem 'dirge'
9
+ gem 'any_view', '>=0.2'
10
+ gem 'tilt', '>=0.10'
11
+ gem 'pancake', '>=0.2'
12
+
13
+ group(:test) do
14
+ gem 'rspec'
15
+ end
@@ -0,0 +1,93 @@
1
+ ---
2
+ dependencies:
3
+ pancake:
4
+ group:
5
+ - :default
6
+ version: ">= 0.2"
7
+ rake:
8
+ group:
9
+ - :default
10
+ version: ">= 0"
11
+ any_view:
12
+ group:
13
+ - :default
14
+ version: ">= 0.2"
15
+ hashie:
16
+ group:
17
+ - :default
18
+ version: ">= 0"
19
+ rspec:
20
+ group:
21
+ - :test
22
+ version: ">= 0"
23
+ dirge:
24
+ group:
25
+ - :default
26
+ version: ">= 0"
27
+ rack:
28
+ group:
29
+ - :default
30
+ version: ">= 0"
31
+ extlib:
32
+ group:
33
+ - :default
34
+ version: ">= 0"
35
+ tilt:
36
+ group:
37
+ - :default
38
+ version: ">= 0.10"
39
+ specs:
40
+ - rake:
41
+ version: 0.8.7
42
+ - builder:
43
+ version: 2.1.2
44
+ - i18n:
45
+ version: 0.3.7
46
+ - memcache-client:
47
+ version: 1.8.3
48
+ - tzinfo:
49
+ version: 0.3.22
50
+ - activesupport:
51
+ version: 3.0.0.beta3
52
+ - columnize:
53
+ version: 0.3.1
54
+ - linecache:
55
+ version: "0.43"
56
+ - ruby-debug-base:
57
+ version: 0.10.3
58
+ - ruby-debug:
59
+ version: 0.10.3
60
+ - tilt:
61
+ version: "0.10"
62
+ - any_view:
63
+ version: 0.2.2
64
+ - dirge:
65
+ version: 0.0.4
66
+ - extlib:
67
+ version: 0.9.15
68
+ - fuzzyhash:
69
+ version: 0.0.11
70
+ - hashie:
71
+ version: 0.2.0
72
+ - json:
73
+ version: 1.4.3
74
+ - rack:
75
+ version: 1.1.0
76
+ - rack-accept-media-types:
77
+ version: "0.9"
78
+ - rack-test:
79
+ version: 0.5.4
80
+ - thor:
81
+ version: 0.13.6
82
+ - usher:
83
+ version: 0.7.1
84
+ - wrapt:
85
+ version: 0.1.5
86
+ - pancake:
87
+ version: 0.2.0
88
+ - rspec:
89
+ version: 1.3.0
90
+ hash: 5cef1217c6aaf844669831ef4a76bb2057a382b7
91
+ sources:
92
+ - Rubygems:
93
+ uri: http://gemcutter.org
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Daniel Neighman
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,80 @@
1
+ h1. Rack::Rescue
2
+
3
+ Rack Rescue is a middleware for handling exceptions in your rack application.
4
+
5
+ Rack::Rescue will set the correct status, render a template, and optionally run any number of callbacks when an exception is experienced.
6
+
7
+ Just insert the middleware in your stack, and any unhandled exception will trigger the handling. Unknown exceptions trigger a basic error page, with a 500 status.
8
+
9
+ Rather than launch into a long-winded chat about what it can do, I'll suffice with a list.
10
+
11
+ * Per exception status codes
12
+ * Per exception templates (with a fallback template)
13
+ * Content type negotiation (:html, :xml, :js etc)
14
+ * cascading tempate locations
15
+ * template naming of the usual <name>.<format>.<(erb|haml|other tilt template)>
16
+ * template naming including the RACK_ENV
17
+ ** <name>.<RACK_ENV>.<format>.<(tilt template engine ext)>
18
+ ** If the more specific template is not found, it will fall back to just name, format and ext
19
+ * template inheritance
20
+ * Arbitrary exception hooks
21
+
22
+ h2. Adding Exceptions
23
+
24
+ <pre><code>
25
+ # Add to the default list
26
+ Rack::Rescue::Exceptions.add_defaults("MyException", "AnotherException", :status => 404)
27
+ Rack::Rescue::Exceptions.add_defaults("DifferetOne", :status => 455, :template => "different")
28
+
29
+ # Add to a specific Rack::Rescue
30
+ use Rack::Rescue do |rr|
31
+ rr.add("SomeException", :status => 478, :template => "something")
32
+ end
33
+ </code></pre>
34
+
35
+ h2. Add a template location
36
+
37
+ By default templates should be located in
38
+
39
+ &lt;handler root&gt;/rack_rescue_templates/**/*
40
+
41
+ To add a new handler root
42
+
43
+ <pre><code>
44
+ Rack::Rescue::Handler.roots << File.join(Dir.pwd, "views")
45
+ </pre></code>
46
+
47
+ This will then look in "./views/rack_rescue_template" directory for you templates. If it doesn't find them in there it will fall back to the templates located in any other roots, or finall to the default templates it ships with.
48
+
49
+ h2. Template Naming
50
+
51
+ Templates are named with the following form, in the following preference:
52
+
53
+ 1. name.env.format.ext # error.development.html.erb
54
+ 2. name.format.ext # error.html.erb
55
+
56
+ If the first is not matched, the second will be also checked
57
+
58
+ h2. Arbitrary Exception hooks
59
+
60
+ When an exception is detected, any exception hooks that have been declared are executed before the template is rendered and the response returned to the client.
61
+
62
+ <pre><code>
63
+ Rack::Rescue.add_handler do |exception, env, options|
64
+ # email me the exception!
65
+ end
66
+ </code></pre>
67
+
68
+ h1. Note on Patches/Pull Requests
69
+
70
+ * Fork the project.
71
+ * Make your feature addition or bug fix.
72
+ * Add tests for it. This is important so I don't break it in a
73
+ future version unintentionally.
74
+ * Commit, do not mess with rakefile, version, or history.
75
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
76
+ * Send me a pull request. Bonus points for topic branches.
77
+
78
+ h2. Copyright
79
+
80
+ Copyright (c) 2010 Daniel Neighman. See LICENSE for details.
@@ -0,0 +1,26 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ require 'spec/rake/spectask'
5
+ Spec::Rake::SpecTask.new(:spec) do |spec|
6
+ spec.libs << 'lib' << 'spec'
7
+ spec.spec_files = FileList['spec/**/*_spec.rb']
8
+ end
9
+
10
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
11
+ spec.libs << 'lib' << 'spec'
12
+ spec.pattern = 'spec/**/*_spec.rb'
13
+ spec.rcov = true
14
+ end
15
+
16
+ task :default => :spec
17
+
18
+ require 'rake/rdoctask'
19
+ Rake::RDocTask.new do |rdoc|
20
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
21
+
22
+ rdoc.rdoc_dir = 'rdoc'
23
+ rdoc.title = "rack-rescue #{version}"
24
+ rdoc.rdoc_files.include('README*')
25
+ rdoc.rdoc_files.include('lib/**/*.rb')
26
+ end
@@ -0,0 +1,7 @@
1
+ h1. TODO
2
+
3
+ * Add simple helpers for the view
4
+ * Add default templates for differnt formats (content negotiation)
5
+ * Make the default handler inheritance safe (DEFAULT_HANLDERS)
6
+ * Provide a format check to use in preference to the normal content_negotiation
7
+ * Make it layout aware
@@ -0,0 +1 @@
1
+ require 'rack/rescue'
@@ -0,0 +1,93 @@
1
+ require 'pancake'
2
+ module Rack
3
+ # Rack Middleware for rescuing exceptions and responding to the client
4
+ # With a page that is descriptive
5
+ class Rescue
6
+ autoload :Exceptions, 'rack/rescue/default_exceptions'
7
+ autoload :Handler, 'rack/rescue/handler'
8
+ autoload :Responder, 'rack/rescue/responder'
9
+
10
+ inheritable_inner_classes "Exceptions", "Handler", "Responder"
11
+ class_inheritable_accessor :error_handlers
12
+ self.error_handlers = []
13
+
14
+ # Make sure there are a minimum set of formats available
15
+ [:html, :xml, :text, :js, :json].each do |f|
16
+ ::Pancake::MimeTypes.group(f)
17
+ end
18
+
19
+ def self.default_formats
20
+ return @default_formats if @default_formats
21
+
22
+ if ENV['RACK_ENV'] == 'production'
23
+ @default_formats = Pancake::MimeTypes.groups.keys.map(&:to_sym)
24
+ else
25
+ Pancake::MimeTypes.groups.keys.map(&:to_sym)
26
+ end
27
+ end
28
+
29
+ def self.add_handler(&blk)
30
+ error_handlers << blk
31
+ end
32
+
33
+ def initialize(app, options = {})
34
+ @app = app
35
+ load_defaults = options.fetch(:load_default_exceptions, true)
36
+ @exceptions_map = Exceptions.new(load_defaults)
37
+ @formats = options[:formats]
38
+ yield @exceptions_map if block_given?
39
+ end
40
+
41
+ def formats
42
+ @formats || self.class.default_formats
43
+ end
44
+
45
+ def formats=(fmts)
46
+ @formats = fmts
47
+ end
48
+
49
+ def call(env)
50
+ @app.call(env)
51
+ rescue Exception => e
52
+ handle_exception(env, e)
53
+ end
54
+
55
+ private
56
+ # Apply the layout if it exists
57
+ # @api private
58
+ def apply_layout(env, content, opts)
59
+ if layout = env['layout']
60
+ layout.format = opts[:format]
61
+ layout.content = content
62
+ layout.template_name = opts[:layout] if layout.template_name?(opts[:layout], opts)
63
+ layout
64
+ else
65
+ content
66
+ end
67
+ end
68
+
69
+ def handle_exception(env, e)
70
+ # negotiate the content
71
+ request = Rack::Request.new(env)
72
+ responder = Responder.new(env, self)
73
+ opts = {}
74
+ if request.path =~ /\.(.\w)$/
75
+ opts[:format] = $1
76
+ end
77
+ responder.negotiate! opts
78
+
79
+ opts[:format] = responder.content_type
80
+ opts[:layout] ||= 'error'
81
+
82
+ self.class.error_handlers.each{|blk| blk.call(e, env, opts)}
83
+
84
+ handler = @exceptions_map[e].nil? ? @exceptions_map[RuntimeError] : @exceptions_map[e]
85
+
86
+ resp, status = handler.render_error(e, opts), handler.status
87
+
88
+ response = apply_layout(env, resp, opts)
89
+
90
+ Rack::Response.new(response, status, responder.headers).finish
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,16 @@
1
+ require 'rack/rescue/exceptions'
2
+
3
+ class Rack::Rescue::Exceptions
4
+ DEFAULT_HANDLERS = [
5
+ ["RuntimeError", {:status => 500}],
6
+ ["DataMapper::ObjectNotFoundError", {:status => 404}],
7
+ ["ActiveRecord::RecordNotFound", {:status => 404}],
8
+ ["Pancake::Errors::NotFound", {:status => 404, :template => :not_found}],
9
+ ["Pancake::Errors::UnknownRouter", {:status => 500}],
10
+ ["Pancake::Errors::UnknownConfiguration", {:status => 500}],
11
+ ["Pancake::Errors::Unauthorized", {:status => 401}],
12
+ ["Pancake::Errors::Forbidden", {:status => 403}],
13
+ ["Pancake::Errors::Server", {:status => 500}],
14
+ ["Pancake::Errors::NotAcceptable", {:status => 406}]
15
+ ].map{|(name, opts)| Rack::Rescue::Handler.new(name, opts)}
16
+ end
@@ -0,0 +1,107 @@
1
+ module Rack
2
+ class Rescue
3
+ class Exceptions
4
+
5
+ # Add default exceptions to handle.
6
+ #
7
+ # @example
8
+ # Rack::Rescue::Exceptions.add_defaults("MyException", "AnotherException", :status => 404)
9
+ #
10
+ # @api public
11
+ # @see Rack::Rescue::Exceptions::Handler
12
+ # @see Rack::Rescue::Exceptions#add
13
+ def self.add_defaults(*exceptions)
14
+ opts = Hash === exceptions.last ? exceptions.pop : {}
15
+ exceptions.each do |e|
16
+ DEFAULT_HANDLERS << Handler.new(e, opts)
17
+ end
18
+ end
19
+
20
+ # Remove a deafult exception from the list
21
+ #
22
+ # @example
23
+ # Rack::Rescue::Exceptions.remove_defaults("MyException", "AnotherException")
24
+ # Rack::Rescue::Exceptions.remove_defaults(/MyKindOfException/)
25
+ #
26
+ # @api public
27
+ # @see Rack::Rescue::Exceptions.add_defaults
28
+ def self.remove_defaults(*exceptions)
29
+ removed = []
30
+ exceptions.each do |e|
31
+ DEFAULT_HANDLERS.each do |h|
32
+ match = case e
33
+ when String
34
+ h.name == e
35
+ when Regexp
36
+ h.name =~ e
37
+ end
38
+ removed << h if match
39
+ end
40
+ end
41
+ removed.each{|r| DEFAULT_HANDLERS.delete(r) }
42
+ end
43
+
44
+ # @param load_defaults Load the default list of exceptions into this instance
45
+ def initialize(load_defaults = true)
46
+ @exception_handlers = {}
47
+ load_defaults! if load_defaults
48
+ end
49
+
50
+ # Loads the default list of exceptions defined in
51
+ # Rack::Rescue::Exceptions::DEFAULT_HANDLERS
52
+ # @api public
53
+ def load_defaults!
54
+ DEFAULT_HANDLERS.each do |handler|
55
+ exception_handlers[handler.name] = handler
56
+ end
57
+ end
58
+
59
+ # Add an exception handler to Rack::Rescue.
60
+ # Whenever Rack::Rescue rescues an exception, it will check it's list to see what to do with it.
61
+ #
62
+ # @param exceptions a list of exceptions with an optional options hash on the end
63
+ #
64
+ # @option :status [Integer] An integer representing the http response code that should be associated with this exception
65
+ #
66
+ # @api public
67
+ # @see Rack::Rescue::Exceptions.add_defaults
68
+ # @see Rack::Rescue::Exceptions::Handler
69
+ def add(*exceptions)
70
+ opts = Hash === exceptions.last ? exceptions.pop : {}
71
+ exceptions.each do |e|
72
+ name = exception_name(e)
73
+ exception_handlers[name] = Handler.new(e, opts)
74
+ end
75
+ end
76
+
77
+ # Remove an exception handler from this instance of Rack::Rescue::Exceptions
78
+ # This will not remove a default handler
79
+ def delete(e)
80
+ exception_handlers.delete(exception_name(e))
81
+ end
82
+
83
+ def exception_handlers
84
+ @exception_handlers
85
+ end
86
+
87
+ def [](exception)
88
+ exception_handlers[exception_name(exception)]
89
+ end
90
+
91
+ def self.exception_name(e)
92
+ case e
93
+ when String
94
+ e
95
+ when Class
96
+ e.name
97
+ else
98
+ e.class.name
99
+ end
100
+ end
101
+
102
+ def exception_name(e)
103
+ self.class.exception_name(e)
104
+ end
105
+ end
106
+ end
107
+ end