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