Moving app lookup to _insert_self_into
[catagits/Catalyst-Action-REST.git] / lib / Catalyst / Request / REST.pm
1 #
2 # REST.pm
3 # Created by: Adam Jacob, Marchex, <adam@hjksolutions.com>
4 # Created on: 10/13/2006 03:54:33 PM PDT
5 #
6 # $Id: $
7
8 package Catalyst::Request::REST;
9
10 use strict;
11 use warnings;
12
13 use base qw/Catalyst::Request Class::Accessor::Fast/;
14
15 use Catalyst::Utils;
16 use HTTP::Headers::Util qw(split_header_words);
17
18 sub _insert_self_into {
19   my ($class, $app_class ) = @_;
20   my $app = Catalyst::Utils::class2appclass( $app_class ) || $app_class;
21
22   my $req_class = $app->request_class;
23   return if $req_class->isa($class);
24   if ($req_class eq 'Catalyst::Request') {
25     $app->request_class($class);
26   } else {
27     die "$app has a custom request class $req_class, "
28       . "which is not a $class; see Catalyst::Request::REST";
29   }
30 }
31
32 =head1 NAME
33
34 Catalyst::Request::REST - A REST-y subclass of Catalyst::Request
35
36 =head1 SYNOPSIS
37
38      if ( $c->request->accepts('application/json') ) {
39          ...
40      }
41
42      my $types = $c->request->accepted_content_types();
43
44 =head1 DESCRIPTION
45
46 This is a subclass of C<Catalyst::Request> that adds a few methods to
47 the request object to faciliate writing REST-y code. Currently, these
48 methods are all related to the content types accepted by the client.
49
50 Note that if you have a custom request class in your application, and it does
51 not inherit from C<Catalyst::Request::REST>, your application will fail with an
52 error indicating a conflict the first time it tries to use
53 C<Catalyst::Request::REST>'s functionality.  To fix this error, make sure your
54 custom request class inherits from C<Catalyst::Request::REST>.
55
56 =head1 METHODS
57
58 If the request went through the Deserializer action, this method will
59 returned the deserialized data structure.
60
61 =cut
62
63 __PACKAGE__->mk_accessors(qw(data accept_only));
64
65 =over 4 
66
67 =item accepted_content_types
68
69 Returns an array reference of content types accepted by the
70 client.
71
72 The list of types is created by looking at the following sources:
73
74 =over 8
75
76 =item * Content-type header
77
78 If this exists, this will always be the first type in the list.
79
80 =item * content-type parameter
81
82 If the request is a GET request and there is a "content-type"
83 parameter in the query string, this will come before any types in the
84 Accept header.
85
86 =item * Accept header
87
88 This will be parsed and the types found will be ordered by the
89 relative quality specified for each type.
90
91 =back
92
93 If a type appears in more than one of these places, it is ordered based on
94 where it is first found.
95
96 =cut
97
98 sub accepted_content_types {
99     my $self = shift;
100
101     return $self->{content_types} if $self->{content_types};
102
103     my %types;
104
105     # First, we use the content type in the HTTP Request.  It wins all.
106     $types{ $self->content_type } = 3
107         if $self->content_type;
108
109     if ($self->method eq "GET" && $self->param('content-type')) {
110         $types{ $self->param('content-type') } = 2;
111     }
112
113     # Third, we parse the Accept header, and see if the client
114     # takes a format we understand.
115     #
116     # This is taken from chansen's Apache2::UploadProgress.
117     if ( $self->header('Accept') ) {
118         $self->accept_only(1) unless keys %types;
119
120         my $accept_header = $self->header('Accept');
121         my $counter       = 0;
122
123         foreach my $pair ( split_header_words($accept_header) ) {
124             my ( $type, $qvalue ) = @{$pair}[ 0, 3 ];
125             next if $types{$type};
126
127             unless ( defined $qvalue ) {
128                 $qvalue = 1 - ( ++$counter / 1000 );
129             }
130
131             $types{$type} = sprintf( '%.3f', $qvalue );
132         }
133     }
134
135     return $self->{content_types} =
136         [ sort { $types{$b} <=> $types{$a} } keys %types ];
137 }
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 =cut
146
147 sub preferred_content_type { $_[0]->accepted_content_types->[0] }
148
149 =item accepts($type)
150
151 Given a content type, this returns true if the type is accepted.
152
153 Note that this does not do any wildcard expansion of types.
154
155 =cut
156
157 sub accepts {
158     my $self = shift;
159     my $type = shift;
160
161     return grep { $_ eq $type } @{ $self->accepted_content_types };
162 }
163
164 =back
165
166 =head1 AUTHOR
167
168 Adam Jacob <adam@stalecoffee.org>, with lots of help from mst and jrockway
169
170 =head1 MAINTAINER
171
172 J. Shirley <jshirley@cpan.org>
173
174 =head1 LICENSE
175
176 You may distribute this code under the same terms as Perl itself.
177
178 =cut
179
180 1;