4d15ac2b6dbce93c1a0525aa4e869fd33e8ec4a6
[catagits/Catalyst-Runtime.git] / lib / Catalyst / ActionRole / ConsumesContent.pm
1 package Catalyst::ActionRole::ConsumesContent;
2
3 use Moose::Role;
4
5 requires 'match', 'match_captures';
6
7 has allowed_content_types => (
8   is=>'ro',
9   required=>1,
10   lazy=>1,
11   isa=>'ArrayRef',
12   builder=>'_build_allowed_content_types');
13
14 has normalized => (
15   is=>'ro',
16   required=>1,
17   lazy=>1,
18   isa=>'HashRef',
19   builder=>'_build_normalized');
20
21
22 sub _build_normalized {
23   return +{
24     JSON => 'application/json',
25     JS => 'application/javascript',
26     PERL => 'application/perl',
27     HTML => 'text/html',
28     XML => 'text/XML',
29     Plain => 'text/plain',
30     UrlEncoded => 'application/x-www-form-urlencoded',
31     Multipart => 'multipart/form-data',
32     HTMLForm => ['application/x-www-form-urlencoded','multipart/form-data'],
33   };
34 }
35
36 sub _build_allowed_content_types {
37     my $self = shift;
38     my @proto = map {split ',', $_ } @{$self->attributes->{Consumes}};
39     my @converted = map {
40       if(my $normalized = $self->normalized->{$_}) {
41         ref $normalized ? @$normalized : ($normalized);
42       } else {
43         $_;
44       }
45     } @proto;
46
47     return \@converted;
48 }
49
50 around ['match','match_captures'] => sub {
51     my ($orig, $self, $ctx, @args) = @_;
52     if(my $content_type = $ctx->req->content_type) {
53         return 0 unless $self->can_consume($content_type);
54     }
55     return $self->$orig($ctx, @args);
56 };
57
58 sub can_consume {
59     my ($self, $request_content_type) = @_;
60     my @matches = grep { lc($_) eq lc($request_content_type) }
61       @{$self->allowed_content_types};
62     return @matches ? 1:0;
63 }
64
65 around 'list_extra_info' => sub {
66   my ($orig, $self, @args) = @_;
67   return {
68     %{ $self->$orig(@args) }, 
69     CONSUMES => $self->allowed_content_types,
70   };
71 };
72
73 1;
74
75 =head1 NAME
76
77 Catalyst::ActionRole::ConsumesContent - Match on HTTP Request Content-Type
78
79 =head1 SYNOPSIS
80
81     package MyApp::Web::Controller::MyController;
82
83     use base 'Catalyst::Controller';
84
85     sub start : POST Chained('/') CaptureArg(0) { ... }
86
87       sub is_json       : Chained('start') Consumes('application/json') { ... }
88       sub is_urlencoded : Chained('start') Consumes('application/x-www-form-urlencoded') { ... }
89       sub is_multipart  : Chained('start') Consumes('multipart/form-data') { ... }
90       
91       ## Alternatively, for common types...
92
93       sub is_json       : Chained('start') Consume(JSON) { ... }
94       sub is_urlencoded : Chained('start') Consume(HTMLForm)URLEncoded { ... }
95       sub is_multipart  : Chained('start') ConsumeFormData { ... }
96
97       ## Or allow more than one type
98       
99       sub is_more_than_one
100         : Chained('start')
101         : Consumes('application/x-www-form-urlencoded')
102         : Consumes('multipart/form-data')
103       {
104         ## ... 
105       }
106
107       1;
108
109 =head1 DESCRIPTION
110
111 This is an action role that lets your L<Catalyst::Action> match on the content
112 type of the incoming request.  
113
114 Generally when there's a PUT or POST request, there's a request content body
115 with a matching MIME content type.  Commonly this will be one of the types
116 used with classic HTML forms ('application/x-www-form-urlencoded' for example)
117 but there's nothing stopping you specifying any valid content type.
118
119 For matching purposes, we match strings but the casing is insensitive.
120
121 =head1 REQUIRES
122
123 This role requires the following methods in the consuming class.
124
125 =head2 match
126
127 =head2 match_captures
128
129 Returns 1 if the action matches the existing request and zero if not.
130
131 =head1 METHODS
132
133 This role defines the following methods
134
135 =head2 match
136
137 =head2 match_captures
138
139 Around method modifier that return 1 if the request content type matches one of the
140 allowed content types (see L</http_methods>) and zero otherwise.
141
142 =head2 allowed_content_types
143
144 An array of strings that are the allowed content types for matching this action.
145
146 =head2 can_consume
147
148 Boolean.  Does the current request match content type with what this actionrole
149 can consume?
150
151 =head1 AUTHORS
152
153 Catalyst Contributors, see Catalyst.pm
154
155 =head1 COPYRIGHT
156
157 This library is free software. You can redistribute it and/or modify it under
158 the same terms as Perl itself.
159
160 =cut