File Coverage

blib/lib/Catalyst/View/JSON/PerRequest.pm
Criterion Covered Total %
statement 22 22 100.0
branch 2 4 50.0
condition n/a
subroutine 8 8 100.0
pod n/a
total 32 34 94.1


line stmt bran cond sub pod time code
1             package Catalyst::View::JSON::PerRequest;
2              
3 1     1   20800 use Moo;
  1         2  
  1         4  
4 1     1   2004 use CatalystX::InjectComponent;
  1         2854  
  1         23  
5 1     1   364 use Catalyst::View::JSON::_PerRequest;
  1         1  
  1         422  
6              
7             our $VERSION = 0.009;
8             our $DEFAULT_JSON_CLASS = 'JSON::MaybeXS';
9             our $DEFAULT_VIEW_MODEL = 'JSON::ViewData';
10             our %JSON_INIT_ARGS = (
11             utf8 => 1,
12             convert_blessed => 1);
13              
14             extends 'Catalyst::View';
15             with 'Catalyst::Component::InstancePerContext';
16              
17             has json => (
18             is=>'ro',
19             required=>1,
20             init_arg=>undef,
21             lazy=>1,
22             default=>sub {
23             my $self = shift;
24 1     1   82 eval "use ${\$self->json_class}; 1" ||
  1         2  
  1         49  
25             die "Can't use ${\$self->json_class}, $@";
26              
27             return $self->json_class->new(
28             $self->json_init_args);
29             });
30              
31             sub HANDLE_ENCODE_ERROR {
32 1     1   2 my ($view, $err) = @_;
33 1         26 $view->detach_internal_server_error({ error => "$err"});
34             }
35              
36             has handle_encode_error => (
37             is=>'ro',
38             predicate=>'has_handle_encode_error');
39              
40             has default_view_model => (
41             is=>'ro',
42             required=>1,
43             default=>sub {
44             return $DEFAULT_VIEW_MODEL;
45             });
46              
47             has json_class => (
48             is=>'ro',
49             require=>1,
50             default=>sub {
51             return $DEFAULT_JSON_CLASS;
52             });
53              
54             has json_init_args => (
55             is=>'ro',
56             required=>1,
57             lazy=>1,
58             default=>sub {
59             my $self = shift;
60             my %init = (%JSON_INIT_ARGS, $self->has_json_extra_init_args ?
61             %{$self->json_extra_init_args} : ());
62              
63             return \%init;
64             });
65              
66             has json_extra_init_args => (
67             is=>'ro',
68             predicate=>'has_json_extra_init_args');
69              
70             has callback_param => ( is=>'ro', predicate=>'has_callback_param');
71              
72             sub COMPONENT {
73 1     1   197016 my ($class, $app, $args) = @_;
74 1         7 $args = $class->merge_config_hashes($class->config, $args);
75 1         31486 $class->_inject_default_view_model_into($app);
76 1         5770 return $class->new($app, $args);
77             }
78              
79             sub _inject_default_view_model_into {
80 1     1   3 my ($class, $app) = @_;
81 1         9 CatalystX::InjectComponent->inject(
82             into => $app,
83             component => 'Catalyst::Model::JSON::ViewData',
84             as => 'Model::JSON::ViewData' );
85             }
86              
87             sub build_per_context_instance {
88 6     6   248258 my ($self, $c, @args) = @_;
89 6 50       29 return bless +{
    50          
90             ctx=>$c,
91             parent=>$self,
92             json=>$self->json,
93             ($self->has_handle_encode_error ? (handle_encode_error=>$self->handle_encode_error) :()),
94             ($self->has_callback_param ? (callback_param=>$self->callback_param) :())
95             }, 'Catalyst::View::JSON::_PerRequest';
96             }
97              
98             1;
99              
100             =head1 NAME
101              
102             Catalyst::View::JSON::PerRequest - JSON View that owns its data
103              
104             =head1 SYNOPSIS
105              
106             MyApp->inject_components(
107             'View::JSON' => { from_component => 'Catalyst::View::JSON::PerRequest' }
108             );
109              
110             # In a controller...
111              
112             sub root :Chained(/) CaptureArgs(0) {
113             my ($self, $c) = @_;
114             $c->view('JSON')->data->set(z=>1);
115             }
116              
117             sub midpoint :Chained(root) CaptureArgs(0) {
118             my ($self, $c) = @_;
119             $c->view('JSON')->data->set(y=>1);
120             }
121              
122             sub endpoint :Chained(midpoint) Args(0) {
123             my ($self, $c) = @_;
124             $c->view('JSON')->created({
125             a => 1,
126             b => 2,
127             c => 3,
128             });
129             }
130              
131             =head1 DESCRIPTION
132              
133             This is a L<Catalyst::View> that produces JSON response from a given model.
134             It differs from some of the more classic JSON producing views (such as
135             L<Catalyst::View::JSON> in that is is a per request view (one view for each
136             request) and it defines a 'data' method to hold information to use to produce
137             a view.
138              
139             It also generates some local response helpers. You may or may not find this
140             approach leads to cleaner code.
141              
142             =head1 METHODS
143              
144             This view defines the following methods
145              
146             =head2 data (?$model)
147              
148             Used to set the view data model, and/or to called methods on it (for example
149             to set attributes that will later be used in the JSON response.).
150              
151             The default is an injected model based on L<Catalyst::Model::JSON::ViewData>
152             which you should review for basic usage. I recommend setting it to a custom
153             model that better encapsulates your view data. You may use any model in your
154             L<Catalyst> application as long as it does the method "TO_JSON".
155              
156             You may only set the view data model once. If you don't set it and just call
157             methods on it, the default view model is automatically used.
158              
159             B<NOTE> In order to help prevent namespace collision, your custom view model is
160             allowed to defined a method 'set' which is used to set attribute values on your
161             model. Set should take two arguments, a key and a value.
162              
163             =head2 res
164              
165             =head2 response
166              
167             $view->response($status, @headers, \%data||$object);
168             $view->response($status, \%data||$object);
169             $view->response(\%data||$object);
170             $view->response($status);
171             $view->response($status, @headers);
172              
173             Used to setup a response. Calling this method will setup an http status, finalize
174             headers and set a body response for the JSON. Content type will be set to
175             'application/json' automatically (you don't need to set this in a header).
176              
177             =head2 Method '->response' Helpers
178              
179             We map status codes from L<HTTP::Status> into methods to make sending common
180             request types more simple and more descriptive. The following are the same:
181              
182             $c->view->response(200, @args);
183             $c->view->ok(@args);
184              
185             do { $c->view->response(200, @args); $c->detach };
186             $c->view->detach_ok(@args);
187              
188             See L<HTTP::Status> for a full list of all the status code helpers.
189              
190             =head2 render ($data)
191              
192             Given a Perl data will return the JSON encoded version.
193              
194             my $json = $c->view->render(\%data);
195              
196             Should be a reference or object that does 'TO_JSON'
197              
198             =head2 process
199              
200             used as a target for $c->forward. This is mostly here for compatibility with some
201             existing methodology. For example allows using this view with the Renderview action
202             class (common practice). I'd consider it a depracated approach, personally.
203              
204             =head1 ATTRIBUTES
205              
206             This View defines the following attributes that can be set during configuration
207              
208             =head2 callback_param
209              
210             Optional. If set, we use this to get a method name for JSONP from the query parameters.
211              
212             For example if 'callback_param' is 'callback' and the request is:
213              
214             localhost/foo/bar?callback=mymethod
215              
216             Then the JSON response will be wrapped in a function call similar to:
217              
218             mymethod({
219             'foo': 'bar',
220             'baz': 'bin});
221              
222             Which is a common technique for overcoming some cross-domain restrictions of
223             XMLHttpRequest.
224              
225             There are some restrictions to the value of the callback method, for security.
226             For more see: L<http://ajaxian.com/archives/jsonp-json-with-padding>
227              
228             =head2 default_view_model
229              
230             The L<Catalyst> model that is the default model for your JSON return. The
231             default is set to a local instance of L<Catalyst::Model::JSON::ViewData>
232              
233             =head2 json_class
234              
235             The class used to perform JSON encoding. Default is L<JSON::MaybeXS>
236              
237             =head2 json_init_args
238              
239             Arguments used to initialize the L</json_class>. Defaults to:
240              
241             our %JSON_INIT_ARGS = (
242             utf8 => 1,
243             convert_blessed => 1);
244              
245             =head2 json_extra_init_args
246              
247             Allows you to 'tack on' some arguments to the JSON initialization without
248             messing with the defaults. Unless you really need to override the defaults
249             this is the method you should use.
250              
251             =head2 handle_encode_error
252              
253             A reference to a subroutine that is called when there is a failure to encode
254             the data given into a JSON format. This can be used globally as an attribute
255             on the defined configuration for the view, and you can set it or overide the
256             global settings on a context basis.
257              
258             Setting this optional attribute will capture and handle error conditions. We
259             will NOT bubble the error up to the global L<Catalyst> error handling (we don't
260             set $c->error for example). If you want that you need to set it yourself in
261             a custom handler, or don't define one.
262              
263             The subroutine receives two arguments: the view object and the exception. You
264             must setup a new, valid response. For example:
265              
266             package MyApp::View::JSON;
267              
268             use Moo;
269             extends 'Catalyst::View::JSON::PerRequest';
270              
271             package MyApp;
272              
273             use Catalyst;
274              
275             MyApp->config(
276             default_view =>'JSON',
277             'View::JSON' => {
278             handle_encode_error => sub {
279             my ($view, $err) = @_;
280             $view->detach_bad_request({ err => "$err"});
281             },
282             },
283             );
284              
285             MyApp->setup;
286              
287             Or setup/override per context:
288              
289             sub error :Local Args(0) {
290             my ($self, $c) = @_;
291              
292             $c->view->handle_encode_error(sub {
293             my ($view, $err) = @_;
294             $view->detach_bad_request({ err => "$err"});
295             });
296              
297             $c->view->ok( $bad_data );
298             }
299              
300             B<NOTE> If you mess up the return value (you return something that can't be
301             encoded) a second exception will occur which will NOT be handled and will then
302             bubble up to the main application.
303              
304             B<NOTE> The view package contains a global function to a usable default
305             error handler, should you wish to use something consistent and reasonably
306             valid. Example:
307              
308             MyApp->config(
309             default_view =>'JSON',
310             'View::JSON' => {
311             handle_encode_error => \&Catalyst::View::JSON::PerRequest::HANDLE_ENCODE_ERROR,
312             },
313             );
314              
315             The example handler is defined like this:
316              
317             sub HANDLE_ENCODE_ERROR {
318             my ($view, $err) = @_;
319             $view->detach_internal_server_error({ error => "$err"});
320             }
321              
322             =head1 UTF-8 NOTES
323              
324             Generally a view should not do any encoding since the core L<Catalyst>
325             framework handles all this for you. However, historically the popular
326             Catalyst JSON views and related ecosystem (such as L<Catalyst::Action::REST>)
327             have done UTF8 encoding and as a result for compatibility core Catalyst code
328             will assume a response content type of 'application/json' is already UTF8
329             encoded. So even though this is a new module, we will continue to maintain this
330             historical situation for compatibility reasons. As a result the UTF8 encoding
331             flags will be enabled and expect the contents of $c->res->body to be encoded
332             as expected. If you set your own JSON class for encoding, or set your own
333             initialization arguments, please keep in mind this expectation.
334              
335             =head1 SEE ALSO
336              
337             L<Catalyst>, L<Catalyst::View>, L<Catalyst::View::JSON>,
338             L<CatalystX::InjectComponent>, L<Catalyst::Component::InstancePerContext>,
339             L<JSON::MaybeXS>
340              
341             =head1 AUTHOR
342            
343             John Napiorkowski L<email:jjnapiork@cpan.org>
344            
345             =head1 COPYRIGHT & LICENSE
346            
347             Copyright 2015, John Napiorkowski L<email:jjnapiork@cpan.org>
348            
349             This library is free software; you can redistribute it and/or modify it under
350             the same terms as Perl itself.
351              
352             =cut