File Coverage

lib/Catalyst/Action/Deserialize.pm
Criterion Covered Total %
statement 29 30 96.6
branch 9 10 90.0
condition 3 3 100.0
subroutine 7 7 100.0
pod 1 1 100.0
total 49 51 96.0


line stmt bran cond sub pod time code
1             package Catalyst::Action::Deserialize;
2             $Catalyst::Action::Deserialize::VERSION = '1.21';
3 12     12   447159 use Moose;
  12         27  
  12         66  
4 12     12   65087 use namespace::autoclean;
  12         24  
  12         89  
5              
6             extends 'Catalyst::Action::SerializeBase';
7 12     12   852 use Module::Pluggable::Object;
  12         22  
  12         325  
8 12     12   137 use MRO::Compat;
  12         27  
  12         283  
9 12     12   57 use Moose::Util::TypeConstraints;
  12         19  
  12         91  
10              
11             has plugins => ( is => 'rw' );
12              
13             has deserialize_http_methods => (
14                 traits => ['Hash'],
15                 isa => do {
16                     my $tc = subtype as 'HashRef[Str]';
17                     coerce $tc, from 'ArrayRef[Str]',
18                         via { +{ map { ($_ => 1) } @$_ } };
19                     $tc;
20                 },
21                 coerce => 1,
22                 builder => '_build_deserialize_http_methods',
23                 handles => {
24                     deserialize_http_methods => 'keys',
25                     _deserialize_handles_http_method => 'exists',
26                 },
27             );
28              
29 42     42   1221 sub _build_deserialize_http_methods { [qw(POST PUT OPTIONS DELETE)] }
30              
31             sub execute {
32 22     22 1 98343     my $self = shift;
33 22         54     my ( $controller, $c ) = @_;
34              
35 22 100 100     56     if ( !defined($c->req->data) && $self->_deserialize_handles_http_method($c->request->method) ) {
36 10         49         my ( $sclass, $sarg, $content_type ) =
37                       $self->_load_content_plugins( 'Catalyst::Action::Deserialize',
38                         $controller, $c );
39 10 100       32         return 1 unless defined($sclass);
40 9         16         my $rc;
41 9 100       24         if ( defined($sarg) ) {
42 3         38             $rc = $sclass->execute( $controller, $c, $sarg );
43                     } else {
44 6         55             $rc = $sclass->execute( $controller, $c );
45                     }
46 9 50       41         if ( $rc eq "0" ) {
    100          
47 0         0             return $self->unsupported_media_type( $c, $content_type );
48                     } elsif ( $rc ne "1" ) {
49 2         13             return $self->serialize_bad_request( $c, $content_type, $rc );
50                     }
51                 }
52              
53 19         118     $self->maybe::next::method(@_);
54              
55 19         351     return 1;
56             }
57              
58             __PACKAGE__->meta->make_immutable;
59              
60             =head1 NAME
61            
62             Catalyst::Action::Deserialize - Deserialize Data in a Request
63            
64             =head1 SYNOPSIS
65            
66             package Foo::Controller::Bar;
67            
68             __PACKAGE__->config(
69             'default' => 'text/x-yaml',
70             'stash_key' => 'rest',
71             'map' => {
72             'text/x-yaml' => 'YAML',
73             'text/x-data-dumper' => [ 'Data::Serializer', 'Data::Dumper' ],
74             },
75             );
76            
77             sub begin :ActionClass('Deserialize') {}
78            
79             =head1 DESCRIPTION
80            
81             This action will deserialize HTTP POST, PUT, OPTIONS and DELETE requests.
82             It assumes that the body of the HTTP Request is a serialized object.
83             The serializer is selected by introspecting the requests content-type
84             header.
85            
86             If you want deserialize any other HTTP method besides POST, PUT,
87             OPTIONS and DELETE you can do this by setting the
88             C<< deserialize_http_methods >> list via C<< action_args >>.
89             Just modify the config in your controller and define a list of HTTP
90             methods the deserialization should happen for:
91            
92             __PACKAGE__->config(
93             action_args => {
94             '*' => {
95             deserialize_http_methods => [qw(POST PUT OPTIONS DELETE GET)]
96             }
97             }
98             );
99            
100             See also L<Catalyst::Controller/action_args>.
101            
102             The specifics of deserializing each content-type is implemented as
103             a plugin to L<Catalyst::Action::Deserialize>. You can see a list
104             of currently implemented plugins in L<Catalyst::Controller::REST>.
105            
106             The results of your Deserializing will wind up in $c->req->data.
107             This is done through the magic of L<Catalyst::Request::REST>.
108            
109             While it is common for this Action to be called globally as a
110             C<begin> method, there is nothing stopping you from using it on a
111             single routine:
112            
113             sub foo :Local :Action('Deserialize') {}
114            
115             Will work just fine.
116            
117             When you use this module, the request class will be changed to
118             L<Catalyst::Request::REST>.
119            
120             =head1 RFC 7231 Compliance Mode
121            
122             To maintain backwards compatibility with the module's original functionality,
123             where it was assumed the deserialize and serialize content types are the same,
124             an optional compliance mode can be enabled to break this assumption.
125            
126             __PACKAGE__->config(
127             'compliance_mode' => 1,
128             'default' => 'text/x-yaml',
129             'stash_key' => 'rest',
130             'map' => {
131             'text/x-yaml' => 'YAML',
132             'text/x-data-dumper' => [ 'Data::Serializer', 'Data::Dumper' ],
133             },
134             'deserialize_default => 'application/json',
135             'deserialize_map' => {
136             'application/json' => 'JSON',
137             },
138             );
139            
140             Three extra keys are added to the controller configuration. compliance_mode, a
141             boolean to enable the mode. And a parallel set of content type mappings
142             'deserialize_default' and 'deserialize_map' to mirror the default/map
143             configuration keys.
144            
145             The module will use the default/map keys when negotiating the serializing
146             content type specified by the client in the Accept header. And will use the
147             deserialize_default/deserialize_map in conjunction with the Content-Type
148             header where the client is giving the content type being sent in the request.
149            
150             =head1 CUSTOM ERRORS
151            
152             For building custom error responses when de-serialization fails, you can create
153             an ActionRole (and use L<Catalyst::Controller::ActionRole> to apply it to the
154             C<begin> action) which overrides C<unsupported_media_type> and/or C<serialize_bad_request>
155             methods.
156            
157             =head1 SEE ALSO
158            
159             You likely want to look at L<Catalyst::Controller::REST>, which implements
160             a sensible set of defaults for a controller doing REST.
161            
162             L<Catalyst::Action::Serialize>, L<Catalyst::Action::REST>
163            
164             =head1 AUTHORS
165            
166             See L<Catalyst::Action::REST> for authors.
167            
168             =head1 LICENSE
169            
170             You may distribute this code under the same terms as Perl itself.
171            
172             =cut
173