Index: lib/merb/merb_view_context.rb
===================================================================
--- lib/merb/merb_view_context.rb	(revision 371)
+++ lib/merb/merb_view_context.rb	(working copy)
@@ -6,6 +6,7 @@
   PROTECTED_IVARS = %w[@_new_cookie
                        @method
                        @env
+                       @controller
                        @_body
                        @_fingerprint_before
                        @_session
Index: lib/merb/merb_exceptions.rb
===================================================================
--- lib/merb/merb_exceptions.rb	(revision 371)
+++ lib/merb/merb_exceptions.rb	(working copy)
@@ -4,6 +4,90 @@
 end
 
 module Merb
+  
+  module ControllerExceptions
+    class Base < StandardError
+      include Merb::RenderMixin
+      include Merb::ControllerMixin
+      
+      def _template_root
+        @controller._template_root
+      end
+      
+      def set_controller(controller)
+        @controller = controller
+        @controller.status = status
+      end
+
+      def request;  @controller.request;  end      
+      def params;   @controller.params;   end
+      def cookies;  @controller.cookies;  end
+      def headers;  @controller.headers;  end
+      def session;  @controller.session;  end
+      def response; @controller.response; end      
+      
+      def status
+        self.class.const_get(:STATUS)
+      end
+      
+      def call_action
+        # before filters?
+        out = merb_action
+        # after filters?
+        
+        # overwrite status changes during action
+        @controller.status = status
+        out
+      end
+      
+      def merb_action
+        "Error #{status}! (this would be something explaining the error code)"
+      end
+    end
+    
+    # created with: 
+    # ruby -r rubygems -e "require 'mongrel'; puts Mongrel::HTTP_STATUS_CODES.keys.sort.map {|code| %Q{class #{Mongrel::HTTP_STATUS_CODES[code].gsub /[^\w]/,''} < Base; STATUS = #{code}; end} }.join(%Q{\n})"
+    # we could do some magic here, but i think it's more instructive to just
+    # have these values copied
+    class Continue < Base; STATUS = 100; end
+    class SwitchingProtocols < Base; STATUS = 101; end
+    class OK < Base; STATUS = 200; end
+    class Created < Base; STATUS = 201; end
+    class Accepted < Base; STATUS = 202; end
+    class NonAuthoritativeInformation < Base; STATUS = 203; end
+    class NoContent < Base; STATUS = 204; end
+    class ResetContent < Base; STATUS = 205; end
+    class PartialContent < Base; STATUS = 206; end
+    class MultipleChoices < Base; STATUS = 300; end
+    class MovedPermanently < Base; STATUS = 301; end
+    class MovedTemporarily < Base; STATUS = 302; end
+    class SeeOther < Base; STATUS = 303; end
+    class NotModified < Base; STATUS = 304; end
+    class UseProxy < Base; STATUS = 305; end
+    class BadRequest < Base; STATUS = 400; end
+    class Unauthorized < Base; STATUS = 401; end
+    class PaymentRequired < Base; STATUS = 402; end
+    class Forbidden < Base; STATUS = 403; end
+    class NotFound < Base; STATUS = 404; end
+    class MethodNotAllowed < Base; STATUS = 405; end
+    class NotAcceptable < Base; STATUS = 406; end
+    class ProxyAuthenticationRequired < Base; STATUS = 407; end
+    class RequestTimeout < Base; STATUS = 408; end
+    class Conflict < Base; STATUS = 409; end
+    class Gone < Base; STATUS = 410; end
+    class LengthRequired < Base; STATUS = 411; end
+    class PreconditionFailed < Base; STATUS = 412; end
+    class RequestEntityTooLarge < Base; STATUS = 413; end
+    class RequestURITooLarge < Base; STATUS = 414; end
+    class UnsupportedMediaType < Base; STATUS = 415; end
+    class InternalServerError < Base; STATUS = 500; end
+    class NotImplemented < Base; STATUS = 501; end
+    class BadGateway < Base; STATUS = 502; end
+    class ServiceUnavailable < Base; STATUS = 503; end
+    class GatewayTimeout < Base; STATUS = 504; end
+    class HTTPVersionnotsupported < Base; STATUS = 505; end
+  end
+  
   class MerbError < StandardError; end  
   class Noroutefound < MerbError; end  
   class MissingControllerFile < MerbError; end
Index: lib/merb/merb_controller.rb
===================================================================
--- lib/merb/merb_controller.rb	(revision 371)
+++ lib/merb/merb_controller.rb	(working copy)
@@ -96,15 +96,24 @@
 
     def dispatch(action=:index)
       start = Time.now
-      if !self.class.callable_actions.include?(action.to_s)
-        set_status(404)
-        @_body = IO.read(DIST_ROOT / 'public/404.html') rescue "404: Not Found"
-        MERB_LOGGER.info "Action: #{action} not in callable_actions: #{self.class.callable_actions}"
-      else
-        setup_session
-        super(action)
-        finalize_session
+      begin
+        if !self.class.callable_actions.include?(action.to_s)
+          # <TODO> raise ControllerExceptions::NotFound. no need for public/404.html
+          set_status(404)
+          @_body = IO.read(DIST_ROOT / 'public/404.html') rescue "404: Not Found"
+          # </TODO>
+          MERB_LOGGER.info "Action: #{action} not in callable_actions: #{self.class.callable_actions}"
+        else
+          setup_session
+          super(action)
+          finalize_session
+        end
+      rescue ControllerExceptions::Base => e
+        # do not pass status, the exception will set that
+        e.set_controller(self) # for access to session, params, etc
+        @_body = e.call_action
       end
+      
       @_benchmarks[:action_time] = Time.now - start
       MERB_LOGGER.info("Time spent in #{self.class}##{action} action: #{@_benchmarks[:action_time]} seconds")
     end
@@ -119,6 +128,11 @@
        @_status
     end
     
+    # TODO - this is not the best stratagy
+    def status=(s)
+      @_status = s
+    end
+    
     # Accessor for @_request. Please use request and never @_request directly.
     def request
        @_request
Index: lib/merb/mixins/render_mixin.rb
===================================================================
--- lib/merb/mixins/render_mixin.rb	(revision 371)
+++ lib/merb/mixins/render_mixin.rb	(working copy)
@@ -91,7 +91,13 @@
     # Renders the buffalo.xerb template for the current controller.
     #
     def render(opts={}, &blk)
-      action = opts[:action] || params[:action]
+      
+      action = if kind_of?(Merb::ControllerExceptions::Base)
+        self.class.name.snake_case.split('::').last
+      else
+        opts[:action] || params[:action]
+      end
+      
       opts[:layout] ||= _layout 
       
       case
@@ -234,6 +240,8 @@
       def find_template(opts={})
         if template = opts[:template]
           path = _template_root / template
+        elsif opts[:action] and kind_of?(Merb::ControllerExceptions::Base)
+          path = _template_root / 'exceptions' / opts[:action]
         elsif action = opts[:action]
           segment = self.class.name.snake_case.split('::').join('/')
           path = _template_root / segment / action
Index: specs/merb/merb_dispatch_spec.rb
===================================================================
--- specs/merb/merb_dispatch_spec.rb	(revision 371)
+++ specs/merb/merb_dispatch_spec.rb	(working copy)
@@ -462,4 +462,9 @@
     controller.params[:post_id].should == '1'
     controller.body.should == :stats
   end
+  
+  it "should show the error page" do
+    controller, action = request(:get, '/foo/error')
+    controller.body.should == "oh no!"
+  end
 end    
Index: specs/merb/merb_render_spec.rb
===================================================================
--- specs/merb/merb_render_spec.rb	(revision 371)
+++ specs/merb/merb_render_spec.rb	(working copy)
@@ -137,5 +137,21 @@
     content = c.render(:action => "test")
     content.clean.should == "Hello!"
   end  
+  
+  it "should render exception's view without layout" do 
+    c = Examples.build @in.req, @in.env, {}, @res
+    x = AdminAccessRequired.new
+    x.set_controller(c)
+    content = x.render :layout => :none 
+    content.clean.should == "An Error Occurred!"
+  end
+  
+  it "should render exception's view with layout" do 
+    c = Examples.build @in.req, @in.env, {}, @res
+    x = AdminAccessRequired.new
+    x.set_controller(c)
+    content = x.render
+    content.clean.should == "An Error Occurred!"
+  end
 
 end
Index: specs/fixtures/controllers/dispatch_spec_controllers.rb
===================================================================
--- specs/fixtures/controllers/dispatch_spec_controllers.rb	(revision 371)
+++ specs/fixtures/controllers/dispatch_spec_controllers.rb	(working copy)
@@ -7,9 +7,20 @@
   def bar
     "bar"
   end
+  
+  def error
+    raise AdminAccessRequired
+    "Hello World!"
+  end
 
 end
 
+class AdminAccessRequired < Merb::ControllerExceptions::Unauthorized
+  def merb_action
+    "oh no!"
+  end
+end
+
 class Posts < Merb::Controller
   # GET /posts
   # GET /posts.xml
Index: specs/fixtures/views/exceptions/admin_access_required.herb
===================================================================
--- specs/fixtures/views/exceptions/admin_access_required.herb	(revision 0)
+++ specs/fixtures/views/exceptions/admin_access_required.herb	(revision 0)
@@ -0,0 +1 @@
+<%= "An Error Occurred!" %>
\ No newline at end of file
