File Coverage

blib/lib/Catalyst/TraitFor/Request/ContentNegotiationHelpers.pm
Criterion Covered Total %
statement 6 28 21.4
branch 0 6 0.0
condition n/a
subroutine 2 15 13.3
pod 12 12 100.0
total 20 61 32.7


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