rack-rescue 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,124 @@
1
+ module Rack
2
+ class Rescue
3
+ class Handler
4
+ include Pancake::Mixins::Render
5
+
6
+ # Setup the default root for the error templates
7
+ roots << ::File.join(::File.expand_path(::File.dirname(__FILE__)), "templates")
8
+ push_paths(:error_templates, "rack_rescue_templates", "**/*")
9
+
10
+ attr_accessor :exception, :status, :default_template, :default_format
11
+ attr_reader :name
12
+
13
+ # Rack::Rescue::Handler looks for templates in the form
14
+ # <template_name>.<format>.<engine_name>
15
+ #
16
+ # By default, the format is :text, but you can configure this to be any format you like
17
+ # @see Rack::Rescue::Handler.default_format=
18
+ # @api public
19
+ def self.default_format
20
+ @default_format ||= :text
21
+ end
22
+
23
+ # Set the default format to format of your chosing
24
+ #
25
+ # @example
26
+ # Rack::Rescue::Handler.default_format = :html
27
+ #
28
+ # @see Rack::Rescue::Handler.default_format
29
+ # @api public
30
+ def self.default_format=(format)
31
+ @default_format = format
32
+ end
33
+
34
+ # Looks to se if a template is avaialble
35
+ #
36
+ # @example
37
+ # Rack::Rescue::Handler.template?(:obscure_error, :format => :html)
38
+ #
39
+ # @see Pancake::Mixins::Render::ClassMethods#template
40
+ def self.template?(name, opts = {})
41
+ !!template(name, opts)
42
+ rescue Pancake::Mixins::Render::TemplateNotFound
43
+ false
44
+ end
45
+
46
+ # The defaule path name for the paths_for label
47
+ #
48
+ # @example
49
+ # Rack::Rescue::Handler.push_paths(:error_templates, ".", "**/*")
50
+ # Rack::Rescue::Handler._template_path_name should return :error_templates
51
+ #
52
+ # @see Pancake::Mixins::Render::ClassMethods._template_path_name
53
+ # @api overwritable
54
+ def self._template_path_name(opts = {})
55
+ :error_templates
56
+ end
57
+
58
+ # Provides the name for the template with the relevant options
59
+ #
60
+ # The template name should be the filename of the template up and until the extension for the template engine.
61
+ #
62
+ # The template name can be one of two forms.
63
+ # The first preference includes the format, and the rack environment.
64
+ #
65
+ # @example
66
+ # # ENV['RACK_ENV'] == "test" && format == :html
67
+ # my_template.test.html.haml
68
+ #
69
+ # # The second preference is to use simply the format
70
+ # my_template.html.haml
71
+ #
72
+ # @example
73
+ # # To find a template: my_template.html.erb
74
+ #
75
+ # Rack::Rescue::Handler._template_name_for("my_template", :format => :html) #=> "my_template.html"
76
+ # @see Pancake::Mixins::Render::ClassMethods._template_name_for
77
+ # @api overwritable
78
+ def self._template_name_for(name,opts)
79
+ @template_names ||= {}
80
+ format = opts.fetch(:format, default_format)
81
+ env = ENV['RACK_ENV']
82
+ key = [name, env, format]
83
+ @template_names[key] ||= begin
84
+ names = []
85
+ names << "#{name}.#{env}.#{format}" if env && format
86
+ names << "#{name}.#{format}" if format
87
+ names << "#{name}"
88
+ names
89
+ end
90
+ end
91
+
92
+ def initialize(exception, opts = {}, &blk)
93
+ @exception = exception
94
+ @name = Exceptions.exception_name(exception)
95
+ @status = opts.fetch(:status, 500)
96
+ @default_template = opts.fetch(:template, 'error')
97
+ @default_format = opts[:format] if opts[:format]
98
+ end
99
+
100
+ # The main workhorse of the handler
101
+ # This should be called with the error you want to render.
102
+ # The error will be provided to the template in the local "error" variable
103
+ # @api private
104
+ def render_error(error, opts = {})
105
+ opts = opts.dup
106
+ template_name = opts.fetch(:template_name, default_template)
107
+ opts[:format] ||= default_format || self.class.default_format
108
+ opts[:error] ||= error
109
+ opts[:status] = self.status
110
+
111
+ if self.class.template?(template_name, opts)
112
+ tn = template_name
113
+ else
114
+ tn = 'error'
115
+ unless self.class.template?(tn, opts)
116
+ opts[:format] = :text
117
+ end
118
+ end
119
+
120
+ render(tn, opts)
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,21 @@
1
+ module Rack
2
+ class Rescue
3
+ class Responder
4
+ include Pancake::Mixins::RequestHelper
5
+ include Pancake::Mixins::ResponseHelper
6
+
7
+ def initialize(env, rescuer)
8
+ @rescuer = rescuer
9
+ @env = env
10
+ end
11
+
12
+ def negotiate!(opts = {})
13
+ negotiate_content_type!(@rescuer.formats, opts)
14
+ rescue Pancake::Errors::NotAcceptable
15
+ mt = Pancake::MimeTypes.group(:text).first
16
+ headers["Content-Type"] = mt.type_strings.first
17
+ :text
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,19 @@
1
+ <div class='exception'>
2
+ <% content_block :error_header do %>
3
+ <h1>Error: <%= error.class %></h1>
4
+ <% end %>
5
+
6
+ <% content_block :error_message do %>
7
+ <div class='error'>
8
+ <%= error.message %>
9
+ </div>
10
+ <% end %>
11
+
12
+ <% content_block :error_backtrace do %>
13
+ <ul class='error backtrace'>
14
+ <% error.backtrace.each do |line| %>
15
+ <li><%= line %></li>
16
+ <% end %>
17
+ </ul>
18
+ <% end %>
19
+ </div>
@@ -0,0 +1,15 @@
1
+ <div class='exception'>
2
+ <% content_block :error_header do %>
3
+ <h1><%= status %> Error</h1>
4
+ <% end %>
5
+
6
+ <% content_block :error_message do %>
7
+ <div class='error'>
8
+ <%= error.message %>
9
+ </div>
10
+ <% end %>
11
+
12
+ <% content_block :error_backtrace do %>
13
+ &nbsp;
14
+ <% end %>
15
+ </div>
@@ -0,0 +1,14 @@
1
+ <% content_block :error_header do %>
2
+ Error: <%= error.class %>
3
+ <% end %>
4
+
5
+ <% content_block :error_message do %>
6
+ Error Message: <%= error.message %>
7
+ <% end %>
8
+
9
+ <% content_block :error_backtrace do %>
10
+ Error Backtrace:
11
+ <% if error && error.backtrace %>
12
+ <%= error.backtrace.join("\n") %>
13
+ <% end %>
14
+ <% end %>
@@ -0,0 +1,4 @@
1
+ <% inherits_from :error %>
2
+ <% content_block :error_header do %>
3
+ Not Found: <%= error.class %>
4
+ <% end %>
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'bundler'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'rack-rescue'
6
+ s.version = '0.1.0'
7
+ s.homepage = %q{http://github.com/hassox/rack-rescue}
8
+ s.authors = ["Daniel Neighman"]
9
+ s.autorequire = %q{rack/rescue}
10
+ s.date = Date.today
11
+ s.description = %q{Rescue Handler for Rack}
12
+ s.summary =%q{Rescue Handler for Rack}
13
+ s.email = %q{has.sox@gmail.com}
14
+
15
+ s.rdoc_options = ["--charset=UTF-8"]
16
+ s.require_paths = ["lib"]
17
+ s.rubygems_version = %q{1.3.6}
18
+
19
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
20
+ s.files = Dir[File.join(Dir.pwd, "**/*")]
21
+
22
+ s.add_bundler_dependencies
23
+
24
+ end
25
+
26
+
@@ -0,0 +1,151 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "RackRescue" do
4
+ class RRUnknownException < RuntimeError; end
5
+
6
+ after do
7
+ Rack::Rescue.error_handlers.clear
8
+ end
9
+
10
+ def safe_endpoint(msg = "OK")
11
+ lambda{|e| Rack::Response.new(msg)}
12
+ end
13
+
14
+ def raise_endpoint(exception, msg)
15
+ lambda{|e| raise exception, msg }
16
+ end
17
+
18
+ def build_stack(endpoint = safe_endpoint)
19
+ Rack::Builder.new do
20
+ use Rack::Rescue
21
+ run endpoint
22
+ end
23
+ end
24
+
25
+ before do
26
+ @env = Rack::MockRequest.env_for("/")
27
+ end
28
+
29
+ it "should do nothing if there is no downstream exception" do
30
+ result = build_stack.call(@env)
31
+ result.body.map.join.should == "OK"
32
+ end
33
+
34
+ it "should render the error with the defautl text error handler if it's an unkonwn exception" do
35
+ @env['HTTP_ACCEPT'] = "text/plain"
36
+ bad_app = raise_endpoint(RRUnknownException, "Unknown Brew")
37
+ stack = build_stack(bad_app)
38
+ r = stack.call(@env)
39
+ r[0].should == 500
40
+ body = r[2].body.map.join
41
+ body.should include("Error")
42
+ body.should include("Unknown Brew")
43
+ end
44
+
45
+ describe "A known exception" do
46
+ it "should render the template for the error" do
47
+ bad_app = raise_endpoint(Pancake::Errors::NotFound, "Action Not Found")
48
+ stack = build_stack(bad_app)
49
+ @env['HTTP_ACCEPT'] = 'text/plain'
50
+ r = stack.call(@env)
51
+
52
+ body = r[2].body.map.join
53
+ body.should include("Pancake::Errors::NotFound")
54
+ end
55
+
56
+ it "should set the correct status code" do
57
+ bad_app = raise_endpoint(Pancake::Errors::NotFound, "Action Not Found")
58
+ stack = build_stack(bad_app)
59
+ @env['HTTP_ACCEPT'] = "text/plain"
60
+ r = stack.call(@env)
61
+
62
+ status = r[0]
63
+ status.should == 404
64
+ body = r[2].body.map.join
65
+ body.should include("Action Not Found")
66
+ end
67
+ end
68
+
69
+ it "should provide a list of default formats" do
70
+ Rack::Rescue.default_formats.should == Pancake::MimeTypes.groups.keys.map(&:to_sym)
71
+ rr = Rack::Rescue.new SUCCESS_APP
72
+ rr.formats.should == Pancake::MimeTypes.groups.keys.map(&:to_sym)
73
+ end
74
+
75
+ it "should let me overwrite the formats avaiable" do
76
+ rr = Rack::Rescue.new(SUCCESS_APP, :formats => [:text, :html])
77
+ rr.formats.should == [:text, :html]
78
+ end
79
+
80
+ it "should inspect the rack env and negotiate the content" do
81
+ env = Rack::MockRequest.env_for("/")
82
+ env['HTTP_ACCEPT'] = "text/xml"
83
+
84
+ rr = Rack::Rescue.new(raise_endpoint(RuntimeError, "Boo"))
85
+ result = rr.call(env)
86
+ result[0].should == 500
87
+ result[2].body.to_s.should include("XML Error Template")
88
+ end
89
+
90
+ it "should inspect the extension of the " do
91
+ env = Rack::MockRequest.env_for("/")
92
+ env['HTTP_ACCEPT'] = "text/xml"
93
+
94
+ rr = Rack::Rescue.new(raise_endpoint(RuntimeError, "Boo"))
95
+ result = rr.call(env)
96
+ result[0].should == 500
97
+ result[2].body.to_s.should include("XML Error Template")
98
+ end
99
+
100
+ it "should wrap the error in a layout if there's one present" do
101
+ endpoint = raise_endpoint(RuntimeError, "Bad Error")
102
+
103
+ layout_dir = File.join(File.expand_path(File.dirname(__FILE__)), 'rack', 'rescue', 'fixtures', 'layouts')
104
+
105
+ File.exists?(File.join(layout_dir, "error.html.erb")).should be_true
106
+
107
+ app = Rack::Builder.new do
108
+ use Wrapt do |wrapt|
109
+ wrapt.layout_dirs << layout_dir
110
+ end
111
+
112
+ use Rack::Rescue
113
+ run endpoint
114
+ end
115
+
116
+ env = Rack::MockRequest.env_for("/foo.html")
117
+ r = app.call(env)
118
+ r[0].should == 500
119
+ body = r[2].body.to_s
120
+ body.should include("<h1>Error Layout</h1>")
121
+ end
122
+
123
+ it "should allow me to add error handlers and they should execute on each exception" do
124
+ layout_dir = File.join(File.expand_path(File.dirname(__FILE__)), 'rack', 'rescue', 'fixtures', 'layouts')
125
+ Rack::Rescue.add_handler do |exception, env, options|
126
+ options[:format].should == :html
127
+ options[:layout].should == 'error'
128
+ end
129
+ Rack::Rescue.add_handler do |exception, env, options|
130
+ options[:format] = :xml
131
+ options[:layout] = 'custom_error_layout'
132
+ end
133
+ Rack::Rescue.add_handler do |exception, env, options|
134
+ options[:format].should == :xml
135
+ options[:layout].should == 'custom_error_layout'
136
+ end
137
+
138
+ app = Rack::Builder.new do
139
+ use Wrapt do |wrapt|
140
+ wrapt.layout_dirs << layout_dir
141
+ end
142
+ use Rack::Rescue
143
+ the_app = lambda{|e| raise "Halp"}
144
+ run the_app
145
+ end
146
+
147
+ result = app.call(Rack::MockRequest.env_for("/"))
148
+ result[2].body.join.should include("Custom Error Layout")
149
+ end
150
+
151
+ end
@@ -0,0 +1,85 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '../../../spec_helper')
2
+
3
+ describe Rack::Rescue::Exceptions do
4
+ subject { Rack::Rescue::Exceptions.new(false) }
5
+
6
+ before do
7
+ subject.add("MyNotFoundException")
8
+ end
9
+
10
+ it "should allow me to access the exception" do
11
+ subject["MyNotFoundException"].should be_an_instance_of(Rack::Rescue::Handler)
12
+ end
13
+
14
+ it "should provide me with a hash of all handlers" do
15
+ handler = subject.exception_handlers["MyNotFoundException"]
16
+ subject.exception_handlers.should == {"MyNotFoundException" => handler}
17
+ end
18
+
19
+ it "should allow me to remove handling of an exception" do
20
+ subject.delete("MyNotFoundException")
21
+ subject["MyNotFoundHandler"].should be_nil
22
+ end
23
+
24
+ it "should set the status of the exception to 500 by default" do
25
+ subject["MyNotFoundException"].status.should == 500
26
+ end
27
+
28
+ it "should allow me to change the status of an exception" do
29
+ handler = subject["MyNotFoundException"]
30
+ handler.status = 404
31
+ handler.status.should == 404
32
+ end
33
+
34
+ describe "accessing handlers" do
35
+ before do
36
+ @handler = subject["MyNotFoundException"]
37
+ end
38
+
39
+ it "should allow me to access the exception with the constnat" do
40
+ subject[MyNotFoundException].should == @handler
41
+ end
42
+
43
+ it "should allow me to access the exception with an instance" do
44
+ subject[MyNotFoundException.new].should == @handler
45
+ end
46
+ end
47
+
48
+ it "should add to the default list" do
49
+ e = Rack::Rescue::Exceptions
50
+ lambda do
51
+ e.add_defaults("MyException", :status => 400)
52
+ end.should change(e::DEFAULT_HANDLERS, :size).by(1)
53
+ end
54
+
55
+ it "should add to the default list with a string" do
56
+ e = Rack::Rescue::Exceptions
57
+ e.add_defaults("MyException", "SomethingElse", "Another")
58
+ lambda do
59
+ e.remove_defaults("SomethingElse", "Another")
60
+ end.should change(Rack::Rescue::Exceptions::DEFAULT_HANDLERS, :size).by(-2)
61
+ end
62
+
63
+ it "should add to the default list with a string" do
64
+ e = Rack::Rescue::Exceptions
65
+ e.add_defaults("Foo::MyException", "Foo::SomethingElse", "Another")
66
+ lambda do
67
+ e.remove_defaults(/^Foo/)
68
+ end.should change(Rack::Rescue::Exceptions::DEFAULT_HANDLERS, :size).by(-2)
69
+ end
70
+
71
+ describe "Pre-Packaged Exceptions" do
72
+ subject{ Rack::Rescue::Exceptions.new }
73
+ describe "Not found errors" do
74
+ it{ subject["DataMapper::ObjectNotFoundError" ].status.should == 404 }
75
+ it{ subject["ActiveRecord::RecordNotFound" ].status.should == 404 }
76
+ it{ subject["Pancake::Errors::NotFound" ].status.should == 404 }
77
+ it{ subject["Pancake::Errors::UnknownRouter" ].status.should == 500 }
78
+ it{ subject["Pancake::Errors::UnknownConfiguration" ].status.should == 500 }
79
+ it{ subject["Pancake::Errors::Unauthorized" ].status.should == 401 }
80
+ it{ subject["Pancake::Errors::Forbidden" ].status.should == 403 }
81
+ it{ subject["Pancake::Errors::Server" ].status.should == 500 }
82
+ it{ subject["Pancake::Errors::NotAcceptable" ].status.should == 406 }
83
+ end
84
+ end
85
+ end