File Coverage

blib/lib/Catalyst/TraitFor/Request/ContentNegotiationHelpers.pm
Criterion Covered Total %
statement 13 28 46.4
branch 0 6 0.0
condition n/a
subroutine 6 15 40.0
pod 12 12 100.0
total 31 61 50.8


line stmt bran cond sub pod time code
1             package Catalyst::TraitFor::Request::ContentNegotiationHelpers;
2              
3             our $VERSION = '0.004';
4              
5 2     2   359851 use Moose::Role;
  2         455625  
  2         17  
6 2     2   12627 use HTTP::Headers::ActionPack;
  2         1953  
  2         1090  
7              
8             has content_negotiator => (
9             is => 'bare',
10             required => 1,
11             lazy => 1,
12             builder => '_build_content_negotiator',
13             handles => +{
14             raw_choose_media_type => 'choose_media_type',
15             raw_choose_language => 'choose_language',
16             raw_choose_charset => 'choose_charset',
17             raw_choose_encoding => 'choose_encoding',
18             });
19              
20             sub _build_content_negotiator {
21 2     2   337 return HTTP::Headers::ActionPack->new
22             ->get_content_negotiator;
23             }
24              
25             my $on_best = sub {
26             my ($self, $method, %callbacks) = @_;
27             my $default = delete $callbacks{no_match};
28             if(my $match = $self->$method(keys %callbacks)) {
29             return $callbacks{$match}->($self);
30             } else {
31             return $default ? $default->($self) : undef;
32             }
33             };
34              
35             sub choose_media_type {
36 6     6 1 68020 my $self = shift;
37 6         26 return $self->raw_choose_media_type(\@_, $self->header('Accept'));
38             }
39              
40             sub accepts_media_type {
41 3     3 1 60740 my $self = shift;
42 3         7 return map { $self->choose_media_type($_) } @_;
  3         9  
43             }
44              
45             sub on_best_media_type {
46 1     1 1 1600 return shift->$on_best('choose_media_type', @_);
47             }
48              
49             sub choose_language {
50 0     0 1   my $self = shift;
51 0           return $self->raw_choose_language(\@_, $self->header('Accept-Language'));
52             }
53              
54             sub accepts_language {
55 0     0 1   my $self = shift;
56 0 0         return $self->choose_language(@_) ? 1:0;
57             }
58              
59             sub on_best_language {
60 0     0 1   return shift->$on_best('choose_language', @_);
61             }
62              
63             sub choose_charset {
64 0     0 1   my $self = shift;
65 0           return $self->raw_choose_charset(\@_, $self->header('Accept-Charset'));
66             }
67              
68             sub accepts_charset {
69 0     0 1   my $self = shift;
70 0 0         return $self->choose_charset(@_) ? 1:0;
71             }
72              
73             sub on_best_charset {
74 0     0 1   return shift->$on_best('choose_charset', @_);
75             }
76              
77             sub choose_encoding {
78 0     0 1   my $self = shift;
79 0           return $self->raw_choose_encoding(\@_, $self->header('Accept-Encoding'));
80             }
81              
82             sub accepts_encoding {
83 0     0 1   my $self = shift;
84 0 0         return $self->choose_encoding(@_) ? 1:0;
85             }
86              
87             sub on_best_encoding {
88 0     0 1   return shift->$on_best('choose_encoding', @_);
89             }
90              
91             1;
92              
93             =head1 NAME
94              
95             Catalyst::TraitFor::Request::ContentNegotiationHelpers - assistance with content negotiation
96              
97             =head1 SYNOPSIS
98              
99             For L<Catalyst> v5.90090+
100              
101             package MyApp;
102              
103             use Catalyst;
104              
105             MyApp->request_class_traits(['Catalyst::TraitFor::Request::ContentNegotiationHelpers']);
106             MyApp->setup;
107              
108             For L<Catalyst> older than v5.90090
109              
110             package MyApp;
111              
112             use Catalyst;
113             use CatalystX::RoleApplicator;
114              
115             MyApp->apply_request_class_roles('Catalyst::TraitFor::Request::ContentNegotiationHelpers');
116             MyApp->setup;
117              
118             In a controller:
119              
120             package MyApp::Controller::Example;
121              
122             use Moose;
123             use MooseX::MethodAttributes;
124              
125             sub myaction :Local {
126             my ($self, $c) = @_;
127             my $best_media_type = $c->req->choose_media_type('application/json', 'text/html');
128             }
129              
130             sub choose :Local {
131             my ($self, $c) = @_;
132             my $body = $c->req->on_best_media_type(
133             'no_match' => sub { 'none' },
134             'text/html' => sub { 'html' },
135             'application/json' => sub { 'json' });
136              
137             $c->res->body($body);
138             }
139              
140             sub filter : Local {
141             my ($self, $c) = @_;
142             my @acceptable = $c->req->accepts_media_type('image/jpeg', 'text/html', 'text/plain');
143             }
144              
145              
146             =head1 DESCRIPTION
147              
148             When using L<Catalyst> and developing web APIs it can be desirable to examine
149             the state of HTTP Headers of the request to make decisions about what to do,
150             for example what format to return (HTML, XML, JSON, other). This role can
151             be applied to your L<Catalyst::Request> to add some useful helper methods for
152             the more common types of server side content negotiation.
153              
154             Most of the real work is done by L<HTTP::Headers::ActionPack::ContentNegotiation>,
155             this role just seeks to make it easier to use those features.
156              
157             =head1 ATTRIBUTES
158              
159             This role defines the following attributes.
160              
161             =head2 content_negotiator
162              
163             This is a 'bare' attribute with no direct accessors. It expose a few methods to
164             assist in content negotation.
165              
166             =head1 METHODS
167              
168             This role defines the following methods:
169              
170             =head2 choose_media_type (@array_of_types)
171              
172             Given an array of possible media types ('application/json', 'text/html', etc.)
173             return the one that is the best match for the current request (by looking at the
174             current request ACCEPT header, parsing it and comparing).
175              
176             my $best_type = $c->req->accepts_media_type('image/jpeg', 'text/html', 'text/plain');
177              
178             Returns undefined if no types match.
179              
180             =head2 accepts_media_type ($type | @types)
181              
182             For each media type passed as in a list of arguments, return that media type IF
183             the type is acceptable to the requesting client. Can be used in boolean context
184             to determine if a single or list of types is acceptable or as a filter to permit
185             on those types that are acceptable:
186              
187             if($c->req->accepts_media_type('application/json')) {
188             # handle JSON
189             }
190              
191             my @acceptable = $c->req->accepts_media_type('image/jpeg', 'text/html', 'text/plain');
192              
193             If nothing is acceptable an undef will be returned.
194              
195             =head2 on_best_media_type (%callbacks)
196              
197             Given a hash where the keys are media types and the values are coderefs, execute
198             and return the value of the coderef whose key is the best match for that media type
199             (based on the result of L</choose_media_type>. For example:
200              
201             my $body = $c->req->on_best_media_type(
202             'no_match' => sub { 'none' },
203             'text/html' => sub { 'html' },
204             'application/json' => sub { 'json' });
205              
206             $c->res->body($body);
207              
208             The coderef will receive the current request object as its single argument.
209              
210             If there are no matches, execute the coderef associated with a 'no_match' key
211             or return undef if no such key exists.
212              
213             =head2 choose_language (@array_of_langauges)
214              
215             Given an array of possible media types ('en-US', 'es', etc.)
216             return the one that is the best match for the current request.
217              
218             =head2 accepts_language ($type)
219              
220             Like L</accepts_media_type> but for request language.
221              
222             =head2 on_best_language (%callbacks)
223              
224             Works like L</on_best_media_type> but matches language.
225              
226             =head2 choose_charset (@array_of_character_sets)
227              
228             Given an array of possible media types ("UTF-8", "US-ASCII", etc.)
229             return the one that is the best match for the current request.
230              
231             =head2 accepts_charset ($type)
232              
233             Like L</accepts_media_type> but for request character set.
234              
235             =head2 on_best_charset (%callbacks)
236              
237             Works like L</on_best_media_type> but matches charset.
238              
239             =head2 choose_encoding (@array_of_encodings)
240              
241             Given an array of possible encodings ("gzip", "identity", etc.)
242             return the one that is the best match for the current request.
243              
244             =head2 accepts_encoding ($type)
245              
246             Like L</accepts_media_type> but for request encoding.
247              
248             =head2 on_best_encoding (%callbacks)
249              
250             Works like L</on_best_media_type> but matches encoding.
251              
252             =head2 raw_choose_media_type
253              
254             =head2 raw_choose_language
255              
256             =head2 raw_choose_charset
257              
258             =head2 raw_choose_encoding
259              
260             These are methods that map directly to the underlying L<HTTP::Headers::ActionPack::ContentNegotiation>
261             object. The are basicallty the same functionality except they require two arguments
262             (an additional one to support the HTTP header string). You generally won't need them unless
263             you need to compare am HTTP header that is not one that is part of the current request.
264              
265             =head1 AUTHOR
266            
267             John Napiorkowski L<email:jjnapiork@cpan.org>
268            
269             =head1 SEE ALSO
270            
271             L<Catalyst>, L<Catalyst::Request>, L<HTTP::Headers::ActionPack::ContentNegotiation>
272              
273             =head1 COPYRIGHT & LICENSE
274            
275             Copyright 2015, John Napiorkowski L<email:jjnapiork@cpan.org>
276            
277             This library is free software; you can redistribute it and/or modify it under
278             the same terms as Perl itself.
279              
280             =cut