Wednesday 2 December 2009

Handling errors from Mason in Catalyst

Errors recorded ($c->error()) in Catalyst actions can be forwarded to an error page by overriding the end action in the Root (or other) controller:

sub end : Private{
my ( $self, $c ) = @_;

if ( scalar @{ $c->error } ) {
$c->stash->{errors} = $c->error;
$c->stash->{template} = 'mhtml/error.mhtml';
$c->forward('MyApp::View::Mason');
$c->error(0);
}

return 1 if $c->response->status =~ /^3\d\d$/;
return 1 if $c->response->body;

unless ( $c->response->content_type ) {
$c->response->content_type('text/html; charset=utf-8'); #needed for AJAX IE7 prob
}

$c->forward( 'MyApp::View::Mason' ) unless $c->response->body; #forward to this rather than c->$view('Mason') ) gets Netscape working

}
But what if there is an error in the View? For example all actions perform okay with no $c->error() statements being called, then the application is forwarded to a view in which there is some sort of error - perhaps a stash variable has not been tested for and an attempt is made to use it. The result is some rather messy output, useful to the developer but not to a user. With a little overhead we can catch the view's errors and render them in a suitable error page, by overriding the process subroutine in the view:


eg lib/MyApp/View/Mason.pm:
sub process {
my ($self, $c) = @_;

my $component_path = $self->get_component_path($c);
my $output = $self->render($c, component_path);

if (blessed($output) && $output->isa('HTML::Mason::Exception')) {

chomp $output;
my $error = qq/Couldn't render component "$component_path"/;
$c->log->error($error." - error was \"$output\"");
my @errors = ();
push @errors, $error;
$c->stash->{errors} = \@errors;
$c->stash->{template} = 'error.mhtml';
$component_path = $self->get_component_path($c);
$output = $self->render($c, $component_path);

}

unless ($c->response->content_type) {
$c->response->content_type('text/html; charset=utf-8');
}

$c->response->body($output);

return 1;
}
Note that the error page you now forward to should be robust enough, ie best to keep it basic.