File Coverage

blib/lib/Catalyst/Plugin/Errors.pm
Criterion Covered Total %
statement 12 52 23.0
branch 0 12 0.0
condition 0 3 0.0
subroutine 4 10 40.0
pod 3 6 50.0
total 19 83 22.8


line stmt bran cond sub pod time code
1             package Catalyst::Plugin::Errors;
2              
3 1     1   1882 use Moose;
  1         4  
  1         10  
4 1     1   7892 use MRO::Compat;
  1         3  
  1         29  
5 1     1   6 use CatalystX::Utils::ContentNegotiation;
  1         2  
  1         23  
6 1     1   5 use Catalyst::Utils;
  1         3  
  1         1014  
7              
8             our %DEFAULT_ERROR_VIEWS = (
9             'text/html' => 'Errors::HTML',
10             'text/plain' => 'Errors::Text',
11             'application/json' => 'Errors::JSON',
12             );
13              
14             my %views = %DEFAULT_ERROR_VIEWS;
15             my @accepted = ();
16             my $default_media_type = 'text/plain';
17              
18             my $normalize_args = sub {
19             my $c = shift;
20             my %args = (ref($_[0])||'') eq 'HASH' ? %{$_[0]} : @_;
21             return %args;
22             };
23              
24             sub generate_error_template_name {
25 0     0 0   my ($c, $code, %args) = @_;
26 0           return 'http_error';
27             }
28              
29             sub finalize_error_args {
30 0     0 1   my ($c, $code, %args) = @_;
31             return (
32 0           code => $code,
33             template => $c->generate_error_template_name($code, %args),
34 0           uri => "@{[ $c->req->uri ]}",
35             %args );
36             }
37            
38             sub setup {
39 0     0 0   my $app = shift;
40 0           my $ret = $app->maybe::next::method(@_);
41 0           my $config = $app->config->{'Plugin::Error'};
42              
43 0 0         %views = %{$config->{views}} if $config->{views};
  0            
44 0 0         $default_media_type = $config->{default_media_type} if $config->{views};
45 0           @accepted = keys %views;
46              
47 0           return $ret;
48             }
49              
50             sub setup_components {
51 0     0 0   my ($app, @args) = @_;
52 0           my $ret = $app->maybe::next::method(@_);
53              
54 0           my $namespace = "${app}::View::Errors";
55 0           my %views_we_have = map { Catalyst::Utils::class2classsuffix($_) => 1 }
56 0           grep { m/$namespace/ }
57 0           keys %{ $app->components };
  0            
58              
59 0           foreach my $view_needed (values %views) {
60 0 0         next if $views_we_have{$view_needed};
61 0 0         $app->log->debug("Injecting Catalyst::View::${view_needed}") if $app->debug;
62 0           Catalyst::Utils::ensure_class_loaded("Catalyst::View::${view_needed}");
63 0           Catalyst::Utils::inject_component(
64             into => $app,
65             component => "Catalyst::View::${view_needed}",
66             as => $view_needed );
67             }
68              
69 0           return $ret;
70             }
71              
72             sub dispatch_error {
73 0     0 1   my ($c, $code, @args) = @_;
74              
75 0           my %args = $c->finalize_error_args($code, $c->$normalize_args(@args));
76 0   0       my $chosen_media_type = CatalystX::Utils::ContentNegotiation::content_negotiator
77             ->choose_media_type(\@accepted, $c->request->header('Accept'))
78             || $default_media_type;
79              
80 0           $c->log->info("Error dispatch to mediatype: $chosen_media_type");
81              
82 0           my $chosen_view = $views{$chosen_media_type};
83 0           my $view_obj = $c->view($chosen_view);
84              
85 0           $c->stash(%args);
86              
87 0 0         if(my $sub = $view_obj->can("http_${code}")) {
    0          
88 0           $view_obj->sub($c, %args);
89             } elsif($view_obj->can('http_default')) {
90 0           $view_obj->http_default($c, $code, %args);
91             } else {
92 0           $c->forward($view_obj, \%args);
93             }
94             }
95              
96             sub detach_error {
97 0     0 1   my $c = shift;
98 0           $c->dispatch_error(@_);
99 0           $c->detach;
100             }
101              
102             __PACKAGE__->meta->make_immutable;
103              
104             =head1 NAME
105              
106             Catalyst::Plugin::Errors - Standard error responses with content negotiation
107              
108             =head1 SYNOPSIS
109              
110             Use in your application class
111              
112             package Example;
113              
114             use Catalyst;
115              
116             __PACKAGE__->setup_plugins([qw/Errors/]);
117             __PACKAGE__->setup();
118             __PACKAGE__->meta->make_immutable();
119              
120             And then you can use it in a controller (or anyplace where you have C<$c> context).
121              
122             package Example::Controller::Root;
123              
124             use Moose;
125             use MooseX::MethodAttributes;
126              
127             extends 'Catalyst::Controller';
128              
129             sub root :Chained(/) PathPart('') CaptureArgs(0) {}
130              
131             sub not_found :Chained(root) PathPart('') Args {
132             my ($self, $c, @args) = @_;
133             $c->detach_error(404);
134             }
135              
136             __PACKAGE__->config(namespace=>'');
137             __PACKAGE__->meta->make_immutable;
138              
139             =head1 DESCRIPTION
140              
141             This is a plugin which installs (if needed) View classes to handle HTTP errors (4xx
142             and 5xx codes) in a regular and content negotiated way. See <CatalystX::Errors>
143             for a high level overview. Documentation here is more API level and the examples
144             are sparse.
145              
146             =head1 METHODS
147              
148             This plugin adds the following methods to your C<$c> context.
149              
150             =head2 dispatch_error ($code, ?%args)
151              
152             Dispatches to an error view based on content negotiation and the provided code. Any additional
153             C<%args> will be passed to the view handler, down to the template so if you have a custom view
154             template you can use this to provide custom template parameters.
155              
156             When dispatching to a C<$view> we use the following rules in order:
157              
158             First if the View has a method C<http_${code}> (where C<$code> is the HTTP status code you are
159             using for the error) we call that method with args C<$c, %args> and expect that method to setup
160             a valid error response.
161              
162             Second, call the method C<http_default> with args C<$c, $code, %args> if that exists.
163              
164             If neither method exists we call C<$c->forward($view)> and C<%args> are added to the stash, along
165             with a stash field 'template' which is set to the value 'http_error'. This should work with most
166             standard L<Catalyst> views that look at the stash field 'template' to find a template name. If you
167             prefer a differnt template name you can override the method 'generate_error_template_name' to make
168             it whatever you wish.
169              
170             =head2 detach_error
171              
172             Calls L</dispatch_error> with the provided arguments and then does a C<$c->detach> which
173             effectively ends processing for the action.
174              
175             =head1 CONFIGURATION & CUSTOMIZATION
176              
177             This plugin can be customized with the following configuration options or via
178             overriding or adapting the following methods
179              
180             =head2 finalize_error_args
181              
182             This method provides the actual arguments given to the error view (args which are for
183             example used in the template for messaging to the end user). You can override this
184             to provide your own version. See the source for how this should work
185              
186             =head2 Configuration keys
187              
188             This plugin defines the following configuration by default, which you can override.
189              
190             package Example;
191              
192             use Catalyst;
193              
194             __PACKAGE__->setup_plugins([qw/Errors/]);
195             __PACKAGE__->config(
196             # This is the configuration which is default. You don't have to actually type
197             # this out. I'm just putting it here to show you what its doing under the hood.
198             'Plugin::Errors' => +{
199             'text/html' => 'Errors::HTML',
200             'text/plain' => 'Errors::Text',
201             'application/json' => 'Errors::JSON',
202             },
203             );
204              
205             __PACKAGE__->setup();
206             __PACKAGE__->meta->make_immutable();
207              
208             By default we map the media types C<text/html>, C<text/plain> and C<application/json> to
209             cooresponding views. This views are injected automatically if you don't provide subclasses
210             or your own view locally. The following views are injected as needed:
211              
212             L<Catalyst::View::Error::HTML>, L<Catalyst::View::Error::Text>, and L<L<Catalyst::View::Error::JSON>.
213              
214             You can check the docs for each of the default views for customization options but you can always
215             make a local subclass inside you application's view directory and tweak as desired (or you can just
216             use your own view or one of the common ones on CPAN).
217              
218             You can also add additional media types mappings.
219              
220             =head1 SEE ALSO
221            
222             L<CatalystX::Errors>.
223              
224             =head1 AUTHOR
225            
226             L<CatalystX::Errors>.
227            
228             =head1 COPYRIGHT & LICENSE
229            
230             L<CatalystX::Errors>.
231              
232             =cut