v1.21
[catagits/Catalyst-Action-REST.git] / lib / Catalyst / Action / Serialize.pm
1 package Catalyst::Action::Serialize;
2
3 use Moose;
4 use namespace::autoclean;
5
6 extends 'Catalyst::Action::SerializeBase';
7 use Module::Pluggable::Object;
8 use MRO::Compat;
9
10 has _encoders => (
11    is => 'ro',
12    isa => 'HashRef',
13    default => sub { {} },
14 );
15
16 sub execute {
17     my $self = shift;
18     my ( $controller, $c ) = @_;
19
20     $self->maybe::next::method(@_);
21
22     return 1 if $c->req->method eq 'HEAD';
23     return 1 if $c->response->has_body;
24     return 1 if scalar @{ $c->error };
25     return 1 if $c->response->status =~ /^(?:204)$/;
26     return 1 if defined $c->stash->{current_view};
27     return 1 if defined $c->stash->{current_view_instance};
28
29     # on 3xx responses, serialize if there's something to
30     # serialize, no-op if not
31     my $stash_key = (
32        $controller->{'serialize'} ?
33            $controller->{'serialize'}->{'stash_key'} :
34                 $controller->{'stash_key'}
35            ) || 'rest';
36     return 1 if $c->response->status =~ /^(?:3\d\d)$/ && ! defined $c->stash->{$stash_key};
37
38     my ( $sclass, $sarg, $content_type ) =
39       $self->_load_content_plugins( "Catalyst::Action::Serialize",
40         $controller, $c );
41     unless ( defined($sclass) ) {
42         if ( defined($content_type) ) {
43             $c->log->info("Could not find a serializer for $content_type");
44         } else {
45             $c->log->info(
46                 "Could not find a serializer for an empty content-type");
47         }
48         return 1;
49     }
50     $c->log->debug(
51         "Serializing with $sclass" . ( $sarg ? " [$sarg]" : '' ) ) if $c->debug;
52
53     $self->_encoders->{$sclass} ||= $sclass->new;
54     my $sobj = $self->_encoders->{$sclass};
55
56     my $rc;
57     eval {
58         if ( defined($sarg) ) {
59             $rc = $sobj->execute( $controller, $c, $sarg );
60         } else {
61             $rc = $sobj->execute( $controller, $c );
62         }
63     };
64     if ($@) {
65         return $self->serialize_bad_request( $c, $content_type, $@ );
66     } elsif (!$rc) {
67         return $self->unsupported_media_type( $c, $content_type );
68     }
69
70     return 1;
71 }
72
73 __PACKAGE__->meta->make_immutable;
74
75 1;
76
77 =head1 NAME
78
79 Catalyst::Action::Serialize - Serialize Data in a Response
80
81 =head1 SYNOPSIS
82
83     package Foo::Controller::Bar;
84
85     __PACKAGE__->config(
86         'default'   => 'text/x-yaml',
87         'stash_key' => 'rest',
88         'map'       => {
89             'text/html'          => [ 'View', 'TT', ],
90             'text/x-yaml'        => 'YAML',
91             'text/x-data-dumper' => [ 'Data::Serializer', 'Data::Dumper' ],
92         }
93     );
94
95     sub end :ActionClass('Serialize') {}
96
97 =head1 DESCRIPTION
98
99 This action will serialize the body of an HTTP Response.  The serializer is
100 selected by introspecting the HTTP Requests content-type header.
101
102 It requires that your Catalyst controller is properly configured to set up the
103 mapping between Content Type's and Serialization classes.
104
105 The specifics of serializing each content-type is implemented as a plugin to
106 L<Catalyst::Action::Serialize>.
107
108 Typically, you would use this ActionClass on your C<end> method.  However,
109 nothing is stopping you from choosing specific methods to Serialize:
110
111   sub foo :Local :ActionClass('Serialize') {
112      .. populate stash with data ..
113   }
114
115 When you use this module, the request class will be changed to
116 L<Catalyst::Request::REST>.
117
118 =head1 CONFIGURATION
119
120 =head2 map
121
122 Takes a hashref, mapping Content-Types to a given serializer plugin.
123
124 =head2 default
125
126 This is the 'fall-back' Content-Type if none of the requested or acceptable
127 types is found in the L</map>. It must be an entry in the L</map>.
128
129 =head2 stash_key
130
131 Specifies the key of the stash entry holding the data that is to be serialized.
132 So if the value is "rest", we will serialize the data under:
133
134   $c->stash->{'rest'}
135
136 =head2 content_type_stash_key
137
138 Specifies the key of the stash entry that optionally holds an overriding
139 Content-Type. If set, and if the specified stash entry has a valid value,
140 then it takes priority over the requested content types.
141
142 This can be useful if you want to dynamically force a particular content type,
143 perhaps for debugging.
144
145 =head1 HELPFUL PEOPLE
146
147 Daisuke Maki pointed out that early versions of this Action did not play
148 well with others, or generally behave in a way that was very consistent
149 with the rest of Catalyst.
150
151 =head1 CUSTOM ERRORS
152
153 For building custom error responses when serialization fails, you can create
154 an ActionRole (and use L<Catalyst::Controller::ActionRole> to apply it to the
155 C<end> action) which overrides C<unsupported_media_type> and/or C<serialize_bad_request>
156 methods.
157
158 =head1 SEE ALSO
159
160 You likely want to look at L<Catalyst::Controller::REST>, which implements
161 a sensible set of defaults for doing a REST controller.
162
163 L<Catalyst::Action::Deserialize>, L<Catalyst::Action::REST>
164
165 =head1 AUTHORS
166
167 See L<Catalyst::Action::REST> for authors.
168
169 =head1 LICENSE
170
171 You may distribute this code under the same terms as Perl itself.
172
173 =cut