File Coverage

blib/lib/CGI/Application/Plugin/JSON.pm
Criterion Covered Total %
statement 60 63 95.2
branch 9 10 90.0
condition 13 20 65.0
subroutine 14 15 93.3
pod 9 9 100.0
total 105 117 89.7


line stmt bran cond sub pod time code
1             package CGI::Application::Plugin::JSON;
2 1     1   328446 use warnings;
  1         4  
  1         56  
3 1     1   7 use strict;
  1         3  
  1         61  
4 1     1   9 use JSON::Any;
  1         2  
  1         16  
5 1     1   244 use base 'Exporter';
  1         3  
  1         1834  
6              
7             our @EXPORT_OK = qw(
8             to_json
9             from_json
10             json_header
11             json_body
12             json_callback
13             add_json_header
14             clear_json_header
15             json_header_string
16             json_header_value
17             );
18              
19             our %EXPORT_TAGS = ( all => \@EXPORT_OK );
20              
21             =head1 NAME
22              
23             CGI::Application::Plugin::JSON - easy manipulation of JSON headers
24              
25             =cut
26              
27             our $VERSION = '1.02';
28              
29             =head1 SYNOPSIS
30              
31             use CGI::Application::Plugin::JSON ':all';
32              
33             # add_json_header() is cumulative
34             $self->add_json_header( foo => 'Lorem ipsum...');
35             $self->add_json_header( bar => [ 0, 2, 3, 4 ] );
36             $self->add_json_header( baz => { stuff => 1, more_stuff => 2 } );
37              
38             # json_header() is not cumulative
39             $self->json_header( foo => 'Lorem ipsum...');
40              
41             # in case we're printing our own headers
42             print "X-JSON: " . $self->json_header_string();
43              
44             # clear out everything in the outgoing JSON headers
45             $self->clear_json_header();
46              
47             # or send the JSON in the document body
48             $self->json_body( { foo => 'Lorem ipsum', bar => [ 0, 2, 3 ] } );
49              
50             # send the JSON back in the document body, but execute it using a Javascript callback
51             $self->json_callback('alert', { foo => 'Lorem ipsum', bar => [ 0, 2, 3 ] } );
52              
53             =head1 DESCRIPTION
54              
55             When communicating with client-side JavaScript, it is common to send
56             data in C HTTP headers or through the document body as content-type
57             C.
58              
59             This plugin adds a couple of convenience methods to make that just a
60             little bit easier.
61              
62             =head1 HEADER METHODS
63              
64             =head2 json_header
65              
66             This method takes name-value pairs and sets them to be used in the outgoing
67             JSON. It is not cummulative and works similarly to C. Use it
68             only if you have all of the values up front. In most cases L
69             is probably what you want.
70              
71             # only the 2nd call will actually set data that will be sent
72             $self->json_header( foo => 'Lorem ipsum...');
73             $self->json_header( bar => [ 0, 2, 3, 4 ] );
74              
75             =cut
76              
77             sub json_header {
78 2     2 1 4980 my ($self, %data) = @_;
79 2   100     10 my $private = $self->param('__CAP_JSON') || {};
80 2         38 $private->{header} = \%data;
81 2         7 $self->param('__CAP_JSON' => $private);
82 2         30 return ' '; # so it can be used as the return value from an rm
83             }
84              
85             =head2 add_json_header
86              
87             This method takes name-value pairs and sets them to be used in the outgoing
88             JSON. It is cummulative and works similarly to C; meaning multiple
89             calls will add to the hash of outgoing values.
90              
91             # both 'foo' and 'bar' will exist in the hash sent out
92             $self->json_header( foo => 'Lorem ipsum...');
93             $self->json_header( bar => [ 0, 2, 3, 4 ] );
94              
95             =cut
96              
97             sub add_json_header {
98 4     4 1 6907 my ($self, %data) = @_;
99 4   100     10 my $private = $self->param('__CAP_JSON') || {};
100 4   100     67 $private->{header} ||= {};
101 4         5 $private->{header} = { %{$private->{header}}, %data };
  4         15  
102 4         11 $self->param('__CAP_JSON' => $private);
103 4         52 return ' '; # so it can be used as the return value from an rm
104             }
105              
106             =head2 clear_json_header
107              
108             This method will remove anything that was previously set by both L
109             and L. This means that no C header will be sent.
110              
111             =cut
112              
113             sub clear_json_header {
114 1     1 1 4 my $self = shift;
115 1   50     4 my $private = $self->param('__CAP_JSON') || {};
116 1         13 delete $private->{header};
117 1         4 $self->param('__CAP_JSON' => $private);
118             }
119              
120             =head2 json_header_string
121              
122             This method will create the actual HTTP header string that will be sent
123             to the browser. This plugin uses it internally to send the header, but
124             it might be useful to use directly if you are printing your own HTTP headers
125             (using a C of C).
126              
127             $self->header_type('none');
128             print $self->json_header_string();
129              
130             =cut
131              
132             sub json_header_string {
133 2     2 1 2 my $self = shift;
134 2   50     7 my $private = $self->param('__CAP_JSON') || {};
135 2   50     32 return $self->to_json($private->{header} || {});
136             }
137              
138             =head2 json_header_value
139              
140             This method will return the values being sent in the JSON header.
141             If you pass in the key of the value you want, you will get just that
142             value. Else all name-value pairs will be returned.
143              
144             my $value = $self->json_header_value('foo');
145              
146             my %values = $self->json_header_value();
147              
148             =cut
149              
150             sub json_header_value {
151 2     2 1 2940 my ($self, $key) = @_;
152 2   50     8 my $private = $self->param('__CAP_JSON') || {};
153              
154 2 50       43 if( defined $private->{header} ) {
155 2 100       6 if( defined $key ) {
156 1         7 return $private->{header}->{$key};
157             } else {
158 1         2 return %{$private->{header}};
  1         12  
159             }
160             } else {
161 0         0 return;
162             }
163             }
164              
165             =head1 BODY METHODS
166              
167             =head2 json_body
168              
169             This method will take the given Perl structure, turn it
170             into JSON, set the appropriate content-type, and then
171             return the JSON.
172              
173             return $self->json_body({ foo => 'stuff', bar => [0,1,2,3]} );
174              
175             =cut
176              
177             sub json_body {
178 1     1 1 1580 my ($self, $data) = @_;
179 1   50     4 my $private = $self->param('__CAP_JSON') || {};
180 1         26 $private->{json_body} = 1;
181 1         4 $self->param(__CAP_JSON => $private);
182 1         51 return $self->to_json($data);
183             }
184              
185             =head2 json_callback
186              
187             This method will take the given Perl structure, turn it
188             into JSON, set the appropriate content-type, and then
189             return a Javascript snippet where the given callback
190             is called with the resulting JSON.
191              
192             return $self->json_callback('alert', { foo => 'stuff', bar => [0,1,2,3]} );
193              
194             # would result in something like the following being sent to the client
195             alert({ foo => 'stuff', bar => [0,1,2,3]});
196              
197             =cut
198              
199             sub json_callback {
200 1     1 1 3126 my ($self, $callback, $data) = @_;
201 1   50     4 my $private = $self->param('__CAP_JSON') || {};
202 1         18 $private->{json_callback} = 1;
203 1         3 $self->param(__CAP_JSON => $private);
204 1         15 return $callback . '(' . $self->to_json($data) . ')';
205             }
206             =head1 MISC METHODS
207              
208             =head2 to_json
209              
210             This method is just a convenient wrapper around L's C.
211              
212             =cut
213              
214             sub to_json {
215 4     4 1 7 my ($self, $data) = @_;
216 4         23 return JSON::Any->encode($data);
217             }
218              
219             =head2 from_json
220              
221             This method is just a convenient wrapper around L's C.
222              
223             =cut
224              
225             sub from_json {
226 0     0 1 0 my ($self, $data) = @_;
227 0         0 return JSON::Any->decode($data);
228             }
229              
230             sub import {
231 1     1   15 my $caller = scalar(caller);
232 1         21 $caller->add_callback( postrun => \&_send_headers );
233              
234 1         759 __PACKAGE__->export_to_level(1, @_);
235             }
236              
237             sub _send_headers {
238 5     5   181 my $self = shift;
239 5   50     13 my $private = $self->param('__CAP_JSON') || {};
240              
241 5 100       75 if( defined $private->{header} ) {
242 2         7 $self->header_add( '-x-json' => $self->json_header_string );
243             }
244              
245 5 100       158 if( defined $private->{json_body} ) {
    100          
246 1         4 $self->header_add('-type' => 'application/json');
247             } elsif ( defined $private->{json_callback} ) {
248 1         4 $self->header_add('-type' => 'text/javascript');
249             }
250             }
251              
252             1;
253              
254             __END__