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,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