File Coverage

lib/Catalyst/TraitFor/Request/REST.pm
Criterion Covered Total %
statement 35 35 100.0
branch 14 14 100.0
condition 5 6 83.3
subroutine 6 6 100.0
pod 1 1 100.0
total 61 62 98.3


line stmt bran cond sub pod time code
1             package Catalyst::TraitFor::Request::REST;
2             $Catalyst::TraitFor::Request::REST::VERSION = '1.19';
3 14     14   8784 use Moose::Role;
  14         73  
  14         114  
4 14     14   71672 use HTTP::Headers::Util qw(split_header_words);
  14         10664  
  14         1160  
5 14     14   85 use namespace::autoclean;
  14         35  
  14         127  
6              
7             has [qw/ data accept_only /] => ( is => 'rw' );
8              
9             has accepted_content_types => (
10             is => 'ro',
11             isa => 'ArrayRef',
12             lazy => 1,
13             builder => '_build_accepted_content_types',
14             init_arg => undef,
15             );
16              
17             has preferred_content_type => (
18             is => 'ro',
19             isa => 'Str',
20             lazy => 1,
21             builder => '_build_preferred_content_type',
22             init_arg => undef,
23             );
24              
25             sub _build_accepted_content_types {
26 47     47   75 my $self = shift;
27              
28 47         64 my %types;
29              
30             # First, we use the content type in the HTTP Request. It wins all.
31 47 100       236 $types{ $self->content_type } = 3
32             if $self->content_type;
33              
34 47 100 100     4963 if ($self->method eq "GET" && $self->param('content-type')) {
35 4         419 $types{ $self->param('content-type') } = 2;
36             }
37              
38             # Third, we parse the Accept header, and see if the client
39             # takes a format we understand.
40             #
41             # This is taken from chansen's Apache2::UploadProgress.
42 47 100       2389 if ( $self->header('Accept') ) {
43 20 100       1418 $self->accept_only(1) unless keys %types;
44              
45 20         48 my $accept_header = $self->header('Accept');
46 20         1051 my $counter = 0;
47              
48 20         62 foreach my $pair ( split_header_words($accept_header) ) {
49 68         1687 my ( $type, $qvalue ) = @{$pair}[ 0, 3 ];
  68         101  
50 68 100       130 next if $types{$type};
51              
52             # cope with invalid (missing required q parameter) header like:
53             # application/json; charset="utf-8"
54             # http://tools.ietf.org/html/rfc2616#section-14.1
55 64 100 66     232 unless ( defined $pair->[2] && lc $pair->[2] eq 'q' ) {
56 30         28 $qvalue = undef;
57             }
58              
59 64 100       92 unless ( defined $qvalue ) {
60 30         46 $qvalue = 1 - ( ++$counter / 1000 );
61             }
62              
63 64         452 $types{$type} = sprintf( '%.3f', $qvalue );
64             }
65             }
66              
67 47         3126 [ sort { $types{$b} <=> $types{$a} } keys %types ];
  101         970  
68             }
69              
70 6     6   194 sub _build_preferred_content_type { $_[0]->accepted_content_types->[0] }
71              
72             sub accepts {
73 59     59 1 5355 my $self = shift;
74 59         60 my $type = shift;
75              
76 59         49 return grep { $_ eq $type } @{ $self->accepted_content_types };
  118         587  
  59         1988  
77             }
78              
79             1;
80             __END__
81              
82             =head1 NAME
83              
84             Catalyst::TraitFor::Request::REST - A role to apply to Catalyst::Request giving it REST methods and attributes.
85              
86             =head1 SYNOPSIS
87              
88             if ( $c->request->accepts('application/json') ) {
89             ...
90             }
91              
92             my $types = $c->request->accepted_content_types();
93              
94             =head1 DESCRIPTION
95              
96             This is a L<Moose::Role> applied to L<Catalyst::Request> that adds a few
97             methods to the request object to facilitate writing REST-y code.
98             Currently, these methods are all related to the content types accepted by
99             the client.
100              
101             =head1 METHODS
102              
103             =over
104              
105             =item data
106              
107             If the request went through the Deserializer action, this method will
108             return the deserialized data structure.
109              
110             =item accepted_content_types
111              
112             Returns an array reference of content types accepted by the
113             client.
114              
115             The list of types is created by looking at the following sources:
116              
117             =over 8
118              
119             =item * Content-type header
120              
121             If this exists, this will always be the first type in the list.
122              
123             =item * content-type parameter
124              
125             If the request is a GET request and there is a "content-type"
126             parameter in the query string, this will come before any types in the
127             Accept header.
128              
129             =item * Accept header
130              
131             This will be parsed and the types found will be ordered by the
132             relative quality specified for each type.
133              
134             =back
135              
136             If a type appears in more than one of these places, it is ordered based on
137             where it is first found.
138              
139             =item preferred_content_type
140              
141             This returns the first content type found. It is shorthand for:
142              
143             $request->accepted_content_types->[0]
144              
145             =item accepts($type)
146              
147             Given a content type, this returns true if the type is accepted.
148              
149             Note that this does not do any wildcard expansion of types.
150              
151             =back
152              
153             =head1 AUTHORS
154              
155             See L<Catalyst::Action::REST> for authors.
156              
157             =head1 LICENSE
158              
159             You may distribute this code under the same terms as Perl itself.
160              
161             =cut
162