File Coverage

blib/lib/Catalyst/ResponseHelpers.pm
Criterion Covered Total %
statement 35 94 37.2
branch 0 20 0.0
condition 0 31 0.0
subroutine 12 25 48.0
pod 13 13 100.0
total 60 183 32.7


line stmt bran cond sub pod time code
1 1     1   12298 use 5.010;
  1         2  
2 1     1   3 use strict;
  1         1  
  1         15  
3 1     1   2 use warnings;
  1         1  
  1         17  
4 1     1   461 use utf8;
  1         8  
  1         3  
5             package Catalyst::ResponseHelpers;
6 1     1   371 use parent qw< Exporter::Tiny >;
  1         259  
  1         6  
7 1     1   2863 use HTTP::Status qw< :constants :is status_message >;
  1         2538  
  1         314  
8 1     1   645 use Path::Tiny;
  1         9391  
  1         46  
9 1     1   418 use Safe::Isa qw< $_isa >;
  1         334  
  1         75  
10 1     1   449 use Encode qw< encode_utf8 >;
  1         6582  
  1         50  
11 1     1   428 use IO::String;
  1         2534  
  1         23  
12 1     1   454 use URI;
  1         2897  
  1         20  
13 1     1   356 use URI::QueryParam;
  1         445  
  1         707  
14              
15             our $VERSION = '1.00';
16              
17             =encoding utf-8
18              
19             =head1 NAME
20              
21             Catalyst::ResponseHelpers - Concise response constructors for Catalyst controllers
22              
23             =head1 SYNOPSIS
24              
25             use Catalyst::ResponseHelpers qw< :helpers :status >;
26              
27             sub show_user : Chained('/') PathPart('user') Args(1) {
28             my ($c, $id) = @_;
29             my $user = load_user($id)
30             or return NotFound($c, "The user id <$id> couldn't be found.");
31             ...
32             }
33              
34             =head1 DESCRIPTION
35              
36             Various helper functions for setting up the current L<Catalyst::Response>
37             object. All response helpers call C<Catalyst/detach> to stop request
38             processing. For clarity in your controller actions, it is nevertheless
39             recommended that you call these helpers as values to L<return()|perlfunc/return>.
40              
41             =head1 EXPORTS
42              
43             By default, only the helper methods documented below are exported. You may
44             explicitly request them using the C<:helpers> tag.
45              
46             You may also request C<:status>, which re-exports the C<:constants> from
47             L<HTTP::Status> into your package. This is useful for custom status codes.
48              
49             =cut
50              
51             our %EXPORT_TAGS = (
52             status => $HTTP::Status::EXPORT_TAGS{constants},
53             helpers => [qw[
54             Ok
55             Forbidden
56             NotFound
57             ClientError
58             ServerError
59             TextPlain
60             AsJSON
61             FromFile
62             FromCharString
63             FromHandle
64             Redirect
65             RedirectToUrl
66             ReturnWithMsg
67             ]],
68             );
69             our @EXPORT = @{ $EXPORT_TAGS{helpers} };
70             our @EXPORT_OK = map { @$_ } values %EXPORT_TAGS;
71              
72             =head1 FUNCTIONS
73              
74             =head2 ReturnWithMsg($c, $mid)
75              
76             Redirects to the request’s C<return> parameter, or C</> if no such parameter
77             exists or if the given URI appears to be external to the app. The given
78             C<$mid> is set as a query parameter, and should be the result of a
79             C<< $c->set_status_msg >> or C<< $c->set_error_msg >> call. These context
80             methods are normally provided by L<Catalyst::Plugin::StatusMessage>.
81              
82             =head2 Redirect($c, $action_or_action_path, @args?)
83              
84             Passes arguments to L<Catalyst/uri_for_action> and redirects to the returned
85             URL.
86              
87             =head2 RedirectToUrl($c, $url, $status?)
88              
89             Redirects to the given URL, with an optional custom status. Status defaults to
90             302 (HTTP_FOUND).
91              
92             =cut
93              
94             sub ReturnWithMsg {
95 0     0 1   my ($c, $mid) = @_;
96 0           my $base = $c->req->base;
97 0           my $return = URI->new( $c->req->param('return') );
98 0 0 0       $return = $c->uri_for('/') unless $return and $return =~ m{^\Q$base\E}i;
99 0           $return->query_param_append( mid => $mid );
100 0           RedirectToUrl($c, $return);
101             }
102              
103             sub Redirect {
104 0     0 1   my ($c, $action, @rest) = @_;
105 0           RedirectToUrl($c, $c->uri_for_action($action, @rest));
106             }
107              
108             sub RedirectToUrl {
109 0     0 1   my ($c, $url, $status) = @_;
110 0           $c->response->redirect($url, $status);
111 0           $c->detach;
112             }
113              
114             =head2 Ok($c, $status?, $msg?)
115              
116             Sets a body-less 200 OK response by default, with an optional body via
117             L</TextPlain> iff a message is provided. Both the status and message may be
118             omitted or provided. If the message is omitted, a body-less response is set.
119              
120             =cut
121              
122             sub Ok {
123 0     0 1   my ($c, $status, $msg) = @_;
124 0 0 0       ($status, $msg) = (undef, $status)
125             if @_ == 2 and not is_success($status);
126 0   0       $status //= HTTP_OK;
127              
128 0 0         if (defined $msg) {
129 0           TextPlain($c, $status, $msg);
130             } else {
131 0           $c->response->status($status);
132 0           $c->response->body(undef);
133 0           $c->detach;
134             }
135             }
136              
137             =head2 Forbidden($c, $msg?)
138              
139             Sets a plain text 403 Forbidden response, with an optional custom message.
140              
141             =head2 NotFound($c, $msg?)
142              
143             Sets a plain text 404 Not Found response, with an optional custom message.
144              
145             =cut
146              
147             sub Forbidden {
148 0     0 1   my ($c, $msg) = @_;
149 0           TextPlain($c, HTTP_FORBIDDEN, $msg);
150             }
151              
152             sub NotFound {
153 0     0 1   my ($c, $msg) = @_;
154 0           TextPlain($c, HTTP_NOT_FOUND, $msg);
155             }
156              
157             =head2 ClientError($c, $status?, $msg?)
158              
159             Sets a plain text 400 Bad Request response by default, with an optional
160             custom message. Both the status and message may be omitted or provided.
161              
162             =head2 ServerError($c, $status?, $msg?)
163              
164             Sets a plain text 500 Internal Server Error response by default, with an
165             optional custom message. Both the status and message may be omitted or
166             provided. The error is logged via L<Catalyst/log>.
167              
168             =cut
169              
170             sub ClientError {
171 0     0 1   my ($c, $status, $msg) = @_;
172 0 0 0       ($status, $msg) = (undef, $status)
173             if @_ == 2 and not is_client_error($status);
174 0   0       TextPlain($c, $status // HTTP_BAD_REQUEST, $msg);
175             }
176              
177             sub ServerError {
178 0     0 1   my ($c, $status, $msg) = @_;
179 0 0 0       ($status, $msg) = (undef, $status)
180             if @_ == 2 and not is_server_error($status);
181 0   0       $status //= HTTP_INTERNAL_SERVER_ERROR;
182 0           $c->log->error("HTTP $status: $msg");
183 0           TextPlain($c, $status, $msg);
184             }
185              
186             =head2 TextPlain($c, $status?, $msg?)
187              
188             Sets a plain text 200 OK response by default, with an optional custom
189             message. Both the status and message may be omitted or provided.
190              
191             =cut
192              
193             sub TextPlain {
194 0     0 1   my ($c, $status, $msg) = @_;
195 0 0 0       ($status, $msg) = (undef, $status)
196             if @_ == 2 and not status_message($status);
197 0   0       $status //= HTTP_OK;
198 0           $c->response->status($status);
199 0           $c->response->content_type("text/plain");
200 0   0       $c->response->body($msg // status_message($status));
201 0           $c->detach;
202             }
203              
204             =head2 AsJSON($c, $status?, $data)
205              
206             Sets a JSON 200 OK response by default, with an optional custom status. Data
207             should be serializable by a view named C<JSON> provided by your application
208             (e.g. via L<Catalyst::View::JSON>).
209              
210             =cut
211              
212             sub AsJSON {
213 0     0 1   my ($c, $status, $data) = @_;
214 0 0         ($status, $data) = (undef, $status)
215             if @_ == 2;
216 0   0       $status //= HTTP_OK;
217 0           $c->response->status($status);
218 0           $c->stash( json => $data );
219 0           $c->view('JSON')->process($c);
220 0           $c->detach;
221             }
222              
223             =head2 FromFile($c, $filename, $mime_type, $headers?)
224              
225             Sets a response from the contents of the filename using the specified MIME
226             type. C<Content-Length> and C<Last-Modified> are set from the file.
227              
228             The C<Content-Disposition> is set to C<attachment> by default, usually forcing
229             a download.
230              
231             An optional arrayref of additional headers may also be provided, which is
232             passed through to L</FromHandle>.
233              
234             =head2 FromCharString($c, $string, $mime_type, $headers?)
235              
236             Sets a response from the contents of a B<character> string using the specified
237             MIME type. The character string will be encoded as UTF-8 bytes.
238              
239             The C<Content-Disposition> is set to C<attachment> by default, usually forcing
240             a download.
241              
242             An optional arrayref of additional headers may also be provided, which is
243             passed through to L</FromHandle>.
244              
245             =head2 FromHandle($c, $handle, $mime_type, $headers?)
246              
247             Sets a response from the contents of the filehandle using the specified MIME
248             type. An optional arrayref of additional headers may also be provided, which
249             is passed to L<the response’s|Catalyst::Response> L<HTTP::Headers> object.
250              
251             The C<Content-Disposition> is set to C<attachment> by default, usually forcing
252             a download.
253              
254             =cut
255              
256             sub FromFile {
257 0     0 1   my ($c, $file) = (shift, shift);
258 0 0         $file = path($file)
259             unless $file->$_isa("Path::Tiny");
260 0           return FromHandle($c, $file->openr_raw, @_);
261             }
262              
263             sub FromCharString {
264 0     0 1   my ($c, $string) = (shift, shift);
265 0           my $handle = IO::String->new( encode_utf8($string) );
266 0           return FromHandle($c, $handle, @_);
267             }
268              
269             sub FromHandle {
270 0     0 1   my ($c, $handle, $mime, $headers) = @_;
271 0           my $h = $c->response->headers;
272              
273 0           $c->response->body( $handle );
274 0           $c->response->header('Content-Disposition' => 'attachment');
275              
276             # Default to UTF-8 for text content unless otherwise specified
277 0           $h->content_type( $mime );
278 0 0 0       $h->content_type( "$mime; charset=utf-8" )
279             if $h->content_is_text and not $h->content_type_charset;
280              
281 0 0         $h->header( @$headers )
282             if $headers;
283 0           $c->detach;
284             }
285              
286             =head1 AUTHOR
287              
288             Thomas Sibley E<lt>trsibley@uw.eduE<gt>
289              
290             =head1 THANKS
291              
292             Inspired in part by seeing John Napiorkowski’s (jnap)
293             L<experimental response helpers in CatalystX::Example::Todo|https://github.com/jjn1056/CatalystX-Example-Todo/blob/master/lib/Catalyst/ResponseHelpers.pm>.
294              
295             =head1 COPYRIGHT
296              
297             Copyright 2015- by the University of Washington
298              
299             =head1 LICENSE
300              
301             This library is free software; you can redistribute it and/or modify
302             it under the same terms as Perl itself.
303              
304             =cut
305              
306             1;