Ensure author side tests are always run
[catagits/Catalyst-Action-REST.git] / lib / Catalyst / Action / SerializeBase.pm
CommitLineData
e601adda 1package Catalyst::Action::SerializeBase;
2
930013e6 3use Moose;
4use namespace::autoclean;
e601adda 5
930013e6 6extends 'Catalyst::Action';
e601adda 7use Module::Pluggable::Object;
9a76221e 8use Catalyst::Request::REST;
5132f5e4 9use Catalyst::Utils ();
9a76221e 10
f465980c 11our $VERSION = '0.81';
12$VERSION = eval $VERSION;
13
5132f5e4 14sub new {
15 my $class = shift;
16 my $config = shift;
37694e6c 17 Catalyst::Request::REST->_insert_self_into( $config->{class} );
5132f5e4 18 return $class->SUPER::new($config, @_);
19}
e601adda 20
21__PACKAGE__->mk_accessors(qw(_serialize_plugins _loaded_plugins));
22
23sub _load_content_plugins {
24 my $self = shift;
25 my ( $search_path, $controller, $c ) = @_;
26
27 unless ( defined( $self->_loaded_plugins ) ) {
28 $self->_loaded_plugins( {} );
29 }
30
31 # Load the Serialize Classes
32 unless ( defined( $self->_serialize_plugins ) ) {
33 my @plugins;
34 my $mpo =
35 Module::Pluggable::Object->new( 'search_path' => [$search_path], );
36 @plugins = $mpo->plugins;
37 $self->_serialize_plugins( \@plugins );
38 }
39
e601adda 40 # Finally, we load the class. If you have a default serializer,
41 # and we still don't have a content-type that exists in the map,
42 # we'll use it.
43 my $sclass = $search_path . "::";
44 my $sarg;
faf5c20b 45 my $map;
367b3ff4 46
faf5c20b 47 my $config;
48
07682cbc 49 if ( exists $controller->{'serialize'} ) {
9cd203c9 50 $c->log->info("Catalyst::Action::REST - deprecated use of 'serialize' for configuration.");
51 $c->log->info("Please see 'CONFIGURATION' in Catalyst::Controller::REST.");
07682cbc 52 $config = $controller->{'serialize'};
7b8c5be2 53 # if they're using the deprecated config, they may be expecting a
54 # default mapping too.
55 $config->{map} ||= $controller->{map};
faf5c20b 56 } else {
07682cbc 57 $config = $controller;
faf5c20b 58 }
59 $map = $config->{'map'};
c0aef9cd 60
a51e7bbd 61 # pick preferred content type
62 my @accepted_types; # priority order, best first
63 # give top priority to content type specified by stash, if any
64 my $content_type_stash_key = $config->{content_type_stash_key};
65 if ($content_type_stash_key
66 and my $stashed = $c->stash->{$content_type_stash_key}
67 ) {
68 # convert to array if not already a ref
69 $stashed = [ $stashed ] if not ref $stashed;
70 push @accepted_types, @$stashed;
367b3ff4 71 }
a51e7bbd 72 # then content types requested by caller
73 push @accepted_types, @{ $c->request->accepted_content_types };
74 # then the default
75 push @accepted_types, $config->{'default'} if $config->{'default'};
76 # pick the best match that we have a serializer mapping for
77 my ($content_type) = grep { $map->{$_} } @accepted_types;
78
79 return $self->_unsupported_media_type($c, $content_type)
80 if not $content_type;
367b3ff4 81
c0aef9cd 82 # carp about old text/x-json
83 if ($content_type eq 'text/x-json') {
84 $c->log->info('Using deprecated text/x-json content-type.');
85 $c->log->info('Use application/json instead!');
86 }
87
e601adda 88 if ( exists( $map->{$content_type} ) ) {
89 my $mc;
90 if ( ref( $map->{$content_type} ) eq "ARRAY" ) {
91 $mc = $map->{$content_type}->[0];
92 $sarg = $map->{$content_type}->[1];
93 } else {
94 $mc = $map->{$content_type};
95 }
96 # TODO: Handle custom serializers more elegantly.. this is a start,
97 # but how do we determine which is Serialize and Deserialize?
98 #if ($mc =~ /^+/) {
99 # $sclass = $mc;
100 # $sclass =~ s/^+//g;
101 #} else {
102 $sclass .= $mc;
103 #}
104 if ( !grep( /^$sclass$/, @{ $self->_serialize_plugins } ) ) {
105 return $self->_unsupported_media_type($c, $content_type);
106 }
107 } else {
367b3ff4 108 return $self->_unsupported_media_type($c, $content_type);
e601adda 109 }
110 unless ( exists( $self->_loaded_plugins->{$sclass} ) ) {
111 my $load_class = $sclass;
112 $load_class =~ s/::/\//g;
113 $load_class =~ s/$/.pm/g;
114 eval { require $load_class; };
115 if ($@) {
116 $c->log->error(
d4611771 117 "Error loading $sclass for " . $content_type . ": $!" );
e601adda 118 return $self->_unsupported_media_type($c, $content_type);
119 } else {
120 $self->_loaded_plugins->{$sclass} = 1;
121 }
122 }
123
124 if ($search_path eq "Catalyst::Action::Serialize") {
125 if ($content_type) {
126 $c->response->header( 'Vary' => 'Content-Type' );
9a76221e 127 } elsif ($c->request->accept_only) {
e601adda 128 $c->response->header( 'Vary' => 'Accept' );
129 }
130 $c->response->content_type($content_type);
131 }
132
133 return $sclass, $sarg, $content_type;
134}
135
136sub _unsupported_media_type {
137 my ( $self, $c, $content_type ) = @_;
138 $c->res->content_type('text/plain');
139 $c->res->status(415);
2224bad1 140 if (defined($content_type) && $content_type ne "") {
e601adda 141 $c->res->body(
142 "Content-Type " . $content_type . " is not supported.\r\n" );
143 } else {
144 $c->res->body(
145 "Cannot find a Content-Type supported by your client.\r\n" );
146 }
147 return undef;
148}
149
150sub _serialize_bad_request {
151 my ( $self, $c, $content_type, $error ) = @_;
152 $c->res->content_type('text/plain');
153 $c->res->status(400);
154 $c->res->body(
155 "Content-Type " . $content_type . " had a problem with your request.\r\n***ERROR***\r\n$error" );
156 return undef;
157}
158
1591;
160
161=head1 NAME
162
163B<Catalyst::Action::SerializeBase>
164
165Base class for Catalyst::Action::Serialize and Catlayst::Action::Deserialize.
166
167=head1 DESCRIPTION
168
169This module implements the plugin loading and content-type negotiating
170code for L<Catalyst::Action::Serialize> and L<Catalyst::Action::Deserialize>.
171
172=head1 SEE ALSO
173
174L<Catalyst::Action::Serialize>, L<Catalyst::Action::Deserialize>,
175L<Catalyst::Controller::REST>,
176
5cb5f6bb 177=head1 AUTHORS
e601adda 178
5cb5f6bb 179See L<Catalyst::Action::REST> for authors.
e601adda 180
181=head1 LICENSE
182
183You may distribute this code under the same terms as Perl itself.
184
185=cut