File Coverage

lib/Catalyst/Action/SerializeBase.pm
Criterion Covered Total %
statement 81 99 81.8
branch 25 46 54.3
condition 2 9 22.2
subroutine 8 8 100.0
pod 0 2 0.0
total 116 164 70.7


line stmt bran cond sub pod time code
1             package Catalyst::Action::SerializeBase;
2             $Catalyst::Action::SerializeBase::VERSION = '1.21';
3 12     12   5569 use Moose;
  12         26  
  12         58  
4 12     12   63367 use namespace::autoclean;
  12         24  
  12         62  
5              
6             extends 'Catalyst::Action';
7 12     12   676 use Module::Pluggable::Object;
  12         22  
  12         263  
8 12     12   2171 use Catalyst::Request::REST;
  12         30  
  12         316  
9 12     12   89 use Catalyst::Utils ();
  12         20  
  12         10053  
10              
11             after BUILDARGS => sub {
12                 my $class = shift;
13                 my $config = shift;
14                 Catalyst::Request::REST->_insert_self_into( $config->{class} );
15             };
16              
17             has [qw(_serialize_plugins _loaded_plugins)] => ( is => 'rw' );
18              
19             sub _load_content_plugins {
20 18     18   36     my $self = shift;
21 18         45     my ( $search_path, $controller, $c ) = @_;
22              
23 18 100       503     unless ( defined( $self->_loaded_plugins ) ) {
24 11         297         $self->_loaded_plugins( {} );
25                 }
26              
27             # Load the Serialize Classes
28 18 100       515     unless ( defined( $self->_serialize_plugins ) ) {
29 11         27         my @plugins;
30 11         94         my $mpo =
31                       Module::Pluggable::Object->new( 'search_path' => [$search_path], );
32 11         119         @plugins = $mpo->plugins;
33 11         70211         $self->_serialize_plugins( \@plugins );
34                 }
35              
36             # Finally, we load the class. If you have a default serializer,
37             # and we still don't have a content-type that exists in the map,
38             # we'll use it.
39 18         52     my $sclass = $search_path . "::";
40 18         84     my $sarg;
41                 my $map;
42 18         0     my $compliance_mode;
43 18         0     my $default;
44              
45 18         0     my $config;
46                 
47 18 50       52     if ( exists $controller->{'serialize'} ) {
48 0         0         $c->log->info("Catalyst::Action::REST - deprecated use of 'serialize' for configuration.");
49 0         0         $c->log->info("Please see 'CONFIGURATION' in Catalyst::Controller::REST.");
50 0         0         $config = $controller->{'serialize'};
51             # if they're using the deprecated config, they may be expecting a
52             # default mapping too.
53 0   0     0         $config->{map} ||= $controller->{map};
54                 } else {
55 18         31         $config = $controller;
56                 }
57 18         35     $map = $config->{'map'};
58 18 50       50     $default = $config->{'default'} if $config->{'default'};
59              
60             # If we're in RFC 7231 compliance mode we need to determine if we're
61             # serializing or deserializing, then set the request object to
62             # look at the appropriate set of supported content types.
63 18         40     $compliance_mode = $config->{'compliance_mode'};
64 18 50       37     if($compliance_mode) {
65 0         0 my $serialize_mode = (split '::', $search_path)[-1];
66 0 0       0 if($serialize_mode eq 'Deserialize') {
    0          
67             # Tell the request object to only look at the Content-Type header
68 0         0 $c->request->set_content_type_only();
69              
70             # If we're in compliance mode and doing deserializing we want
71             # to use the allowed content types for deserializing, not the
72             # serializer map
73 0         0 $map = $config->{'deserialize_map'};
74 0 0       0 $default = $config->{'deserialize_default'} if $config->{'deserialize_default'};
75             } elsif($serialize_mode eq 'Serialize') {
76             # Tell the request object to only look at the Accept header
77 0         0 $c->request->set_accept_only();
78             }
79                 }
80              
81             # pick preferred content type
82 18         24     my @accepted_types; # priority order, best first
83             # give top priority to content type specified by stash, if any
84 18         33     my $content_type_stash_key = $config->{content_type_stash_key};
85 18 50 33     59     if ($content_type_stash_key
86                     and my $stashed = $c->stash->{$content_type_stash_key}
87                 ) {
88             # convert to array if not already a ref
89 0 0       0         $stashed = [ $stashed ] if not ref $stashed;
90 0         0         push @accepted_types, @$stashed;
91                 }
92             # then content types requested by caller
93 18         27     push @accepted_types, @{ $c->request->accepted_content_types };
  18         361  
94             # then the default
95 18 50       45     push @accepted_types, $default if $default;
96             # pick the best match that we have a serializer mapping for
97 18         37     my ($content_type) = grep { $map->{$_} } @accepted_types;
  19         59  
98              
99 18 100       67     return $self->unsupported_media_type($c, $content_type)
100                     if not $content_type;
101              
102             # carp about old text/x-json
103 17 100       55     if ($content_type eq 'text/x-json') {
104 2         8         $c->log->info('Using deprecated text/x-json content-type.');
105 2         54         $c->log->info('Use application/json instead!');
106                 }
107              
108 17 50       97     if ( exists( $map->{$content_type} ) ) {
109 17         28         my $mc;
110 17 100       44         if ( ref( $map->{$content_type} ) eq "ARRAY" ) {
111 6         14             $mc = $map->{$content_type}->[0];
112 6         11             $sarg = $map->{$content_type}->[1];
113                     } else {
114 11         20             $mc = $map->{$content_type};
115                     }
116             # TODO: Handle custom serializers more elegantly.. this is a start,
117             # but how do we determine which is Serialize and Deserialize?
118             #if ($mc =~ /^+/) {
119             # $sclass = $mc;
120             # $sclass =~ s/^+//g;
121             #} else {
122 17         41         $sclass .= $mc;
123             #}
124 17 50       28         if ( !grep( /^$sclass$/, @{ $self->_serialize_plugins } ) ) {
  17         498  
125 0         0             return $self->unsupported_media_type($c, $content_type);
126                     }
127                 } else {
128 0         0         return $self->unsupported_media_type($c, $content_type);
129                 }
130 17 100       470     unless ( exists( $self->_loaded_plugins->{$sclass} ) ) {
131 11         75         my $load_class = $sclass;
132 11         183         $load_class =~ s/::/\//g;
133 11         94         $load_class =~ s/$/.pm/g;
134 11         23         eval { require $load_class; };
  11         3309  
135 11 50       41         if ($@) {
136 0         0             $c->log->error(
137                             "Error loading $sclass for " . $content_type . ": $!" );
138 0         0             return $self->unsupported_media_type($c, $content_type);
139                     } else {
140 11         335             $self->_loaded_plugins->{$sclass} = 1;
141                     }
142                 }
143              
144 17 100       53     if ($search_path eq "Catalyst::Action::Serialize") {
145 8 50       154         unless( $c->response->header( 'Vary' ) ) {
146 8 50       2004             if ($content_type) {
    0          
147 8         166                 $c->response->header( 'Vary' => 'Content-Type' );
148                         } elsif ($c->request->accept_only) {
149 0         0                 $c->response->header( 'Vary' => 'Accept' );
150                         }
151                     }
152 8         1671         $c->response->content_type($content_type);
153                 }
154              
155 17         1369     return $sclass, $sarg, $content_type;
156             }
157              
158             sub unsupported_media_type {
159 1     1 0 4     my ( $self, $c, $content_type ) = @_;
160 1         4     $c->res->content_type('text/plain');
161 1         215     $c->res->status(415);
162 1 50 33     107     if (defined($content_type) && $content_type ne "") {
163 0         0         $c->res->body(
164                         "Content-Type " . $content_type . " is not supported.\r\n" );
165                 } else {
166 1         4         $c->res->body(
167                         "Cannot find a Content-Type supported by your client.\r\n" );
168                 }
169 1         48     return undef;
170             }
171              
172             sub serialize_bad_request {
173 3     3 0 10     my ( $self, $c, $content_type, $error ) = @_;
174 3         10     $c->res->content_type('text/plain');
175 3         672     $c->res->status(400);
176 3         320     $c->res->body(
177                     "Content-Type " . $content_type . " had a problem with your request.\r\n***ERROR***\r\n$error" );
178 3         161     return undef;
179             }
180              
181             __PACKAGE__->meta->make_immutable;
182              
183             1;
184              
185             =head1 NAME
186            
187             Catalyst::Action::SerializeBase - Base class for Catalyst::Action::Serialize and Catlayst::Action::Deserialize.
188            
189             =head1 DESCRIPTION
190            
191             This module implements the plugin loading and content-type negotiating
192             code for L<Catalyst::Action::Serialize> and L<Catalyst::Action::Deserialize>.
193            
194             =head1 SEE ALSO
195            
196             L<Catalyst::Action::Serialize>, L<Catalyst::Action::Deserialize>,
197             L<Catalyst::Controller::REST>,
198            
199             =head1 AUTHORS
200            
201             See L<Catalyst::Action::REST> for authors.
202            
203             =head1 LICENSE
204            
205             You may distribute this code under the same terms as Perl itself.
206            
207             =cut
208