Merge branch 'lairdm-master'
[catagits/Catalyst-Action-REST.git] / lib / Catalyst / Action / Deserialize.pm
CommitLineData
7ad87df9 1package Catalyst::Action::Deserialize;
2
930013e6 3use Moose;
4use namespace::autoclean;
7ad87df9 5
930013e6 6extends 'Catalyst::Action::SerializeBase';
7ad87df9 7use Module::Pluggable::Object;
def65dcc 8use MRO::Compat;
599755b6 9use Moose::Util::TypeConstraints;
7ad87df9 10
05009b91 11has plugins => ( is => 'rw' );
7ad87df9 12
588cbecc 13has deserialize_http_methods => (
599755b6 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,
588cbecc 22 builder => '_build_deserialize_http_methods',
23 handles => {
599755b6 24 deserialize_http_methods => 'keys',
25 _deserialize_handles_http_method => 'exists',
588cbecc 26 },
27);
28
29sub _build_deserialize_http_methods { [qw(POST PUT OPTIONS DELETE)] }
30
7ad87df9 31sub execute {
32 my $self = shift;
e601adda 33 my ( $controller, $c ) = @_;
7ad87df9 34
5bf53db4 35 if ( !defined($c->req->data) && $self->_deserialize_handles_http_method($c->request->method) ) {
9a76221e 36 my ( $sclass, $sarg, $content_type ) =
37 $self->_load_content_plugins( 'Catalyst::Action::Deserialize',
38 $controller, $c );
39 return 1 unless defined($sclass);
e601adda 40 my $rc;
eccb2137 41 if ( defined($sarg) ) {
e601adda 42 $rc = $sclass->execute( $controller, $c, $sarg );
7ad87df9 43 } else {
e601adda 44 $rc = $sclass->execute( $controller, $c );
45 }
9a76221e 46 if ( $rc eq "0" ) {
b3996af8 47 return $self->unsupported_media_type( $c, $content_type );
9a76221e 48 } elsif ( $rc ne "1" ) {
b3996af8 49 return $self->serialize_bad_request( $c, $content_type, $rc );
7ad87df9 50 }
9a76221e 51 }
e601adda 52
def65dcc 53 $self->maybe::next::method(@_);
e601adda 54
55 return 1;
eccb2137 56}
7ad87df9 57
05009b91 58__PACKAGE__->meta->make_immutable;
59
398c5a1b 60=head1 NAME
61
62Catalyst::Action::Deserialize - Deserialize Data in a Request
63
64=head1 SYNOPSIS
65
66 package Foo::Controller::Bar;
67
68 __PACKAGE__->config(
faf5c20b 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 },
398c5a1b 75 );
76
e601adda 77 sub begin :ActionClass('Deserialize') {}
398c5a1b 78
79=head1 DESCRIPTION
80
588cbecc 81This action will deserialize HTTP POST, PUT, OPTIONS and DELETE requests.
398c5a1b 82It assumes that the body of the HTTP Request is a serialized object.
83The serializer is selected by introspecting the requests content-type
84header.
85
588cbecc 86If you want deserialize any other HTTP method besides POST, PUT,
87OPTIONS and DELETE you can do this by setting the
88C<< deserialize_http_methods >> list via C<< action_args >>.
89Just modify the config in your controller and define a list of HTTP
90methods 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
100See also L<Catalyst::Controller/action_args>.
101
398c5a1b 102The specifics of deserializing each content-type is implemented as
103a plugin to L<Catalyst::Action::Deserialize>. You can see a list
104of currently implemented plugins in L<Catalyst::Controller::REST>.
105
106The results of your Deserializing will wind up in $c->req->data.
107This is done through the magic of L<Catalyst::Request::REST>.
108
e601adda 109While it is common for this Action to be called globally as a
110C<begin> method, there is nothing stopping you from using it on a
111single routine:
398c5a1b 112
e601adda 113 sub foo :Local :Action('Deserialize') {}
398c5a1b 114
e601adda 115Will work just fine.
398c5a1b 116
9a76221e 117When you use this module, the request class will be changed to
118L<Catalyst::Request::REST>.
119
c0008fc7 120=head1 RFC 7231 Compliance Mode
121
122To maintain backwards compatibility with the module's original functionality,
123where it was assumed the deserialize and serialize content types are the same,
124an 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
140Three extra keys are added to the controller configuration. compliance_mode, a
141boolean to enable the mode. And a parallel set of content type mappings
142'deserialize_default' and 'deserialize_map' to mirror the default/map
143configuration keys.
144
145The module will use the default/map keys when negotiating the serializing
146content type specified by the client in the Accept header. And will use the
147deserialize_default/deserialize_map in conjunction with the Content-Type
148header where the client is giving the content type being sent in the request.
149
b3996af8 150=head1 CUSTOM ERRORS
151
152For building custom error responses when de-serialization fails, you can create
153an ActionRole (and use L<Catalyst::Controller::ActionRole> to apply it to the
b74200b3 154C<begin> action) which overrides C<unsupported_media_type> and/or C<serialize_bad_request>
b3996af8 155methods.
156
398c5a1b 157=head1 SEE ALSO
158
159You likely want to look at L<Catalyst::Controller::REST>, which implements
160a sensible set of defaults for a controller doing REST.
161
162L<Catalyst::Action::Serialize>, L<Catalyst::Action::REST>
163
5cb5f6bb 164=head1 AUTHORS
398c5a1b 165
5cb5f6bb 166See L<Catalyst::Action::REST> for authors.
398c5a1b 167
168=head1 LICENSE
169
170You may distribute this code under the same terms as Perl itself.
171
172=cut