File Coverage

blib/lib/LWP/UserAgent/JSON.pm
Criterion Covered Total %
statement 62 64 96.8
branch 14 20 70.0
condition n/a
subroutine 15 15 100.0
pod 6 6 100.0
total 97 105 92.3


line stmt bran cond sub pod time code
1             package LWP::UserAgent::JSON;
2              
3 4     4   84817 use strict;
  4         25  
  4         163  
4 4     4   30 use warnings;
  4         8  
  4         165  
5 4     4   28 no warnings 'uninitialized';
  4         10  
  4         176  
6              
7 4     4   498 use HTTP::Request::JSON;
  4         12  
  4         116  
8 4     4   26 use LWP::JSON::Tiny;
  4         11  
  4         86  
9 4     4   23 use Scalar::Util ();
  4         9  
  4         96  
10 4     4   26 use parent 'LWP::UserAgent';
  4         11  
  4         28  
11              
12             our $VERSION = $LWP::JSON::Tiny::VERSION;
13              
14             =head1 NAME
15              
16             LWP::UserAgent::JSON - a subclass of LWP::UserAgent that understands JSON
17              
18             =head1 SYNOPSIS
19              
20             my $user_agent = LWP::UserAgent::JSON->new;
21             my $request = HTTP::Request::JSON->new(...);
22             my $response = $user_agent->request($request);
23             # $response->isa('HTTP::Response::JSON') if we got back JSON
24              
25             =head1 DESCRIPTION
26              
27             This is a subclass of LWP::UserAgent which recognises if it gets
28             JSON output back, and if so returns an L object instead
29             of a L object. It exposes the logic of reblessing the
30             HTTP::Response object in case you get handed a HTTP::Response object by
31             some other method.
32              
33             It also offers a handful of convenience methods to directly convert
34             parameters into JSON for POST, PUT and PATCH requests.
35              
36             =head2 post_json
37              
38             Like LWP::UserAgent::post, except for when it's called as
39             C, in which case $form_ref is turned into
40             JSON. Obviously if you specify Content-Type or Content in subsequent header
41             arguments they'll take precedence.
42              
43             =cut
44              
45             sub post_json {
46 4     4 1 2492 my $self = shift;
47 4         7 my $url = shift;
48              
49 4         12 $self->SUPER::post($url, $self->_mangle_request_arguments(@_));
50             }
51              
52             =head3 put_json
53              
54             A variant on LWP::UserAgent::put with the same transformations as post_json.
55             This requires that your version of LWP supports PUT, i.e. you have LWP 6.00
56             or later.
57              
58             =cut
59              
60             sub put_json {
61 1     1 1 839 my $self = shift;
62 1         2 my $url = shift;
63              
64 1         2 my @parameters = $self->_mangle_request_arguments(@_);
65 1 50       12 if ($self->SUPER::can('put')) {
66 1         22 $self->SUPER::put($url, @parameters);
67             } else {
68 0         0 $self->_send_unimplemented_http_method(PUT => $url, @parameters);
69             }
70             }
71              
72             =head3 patch_json
73              
74             As post_json and put_json, but generates a PATCH request instead.
75             As put_json, you need a semi-modern version of LWP for this.
76              
77             =cut
78              
79             sub patch_json {
80 1     1 1 930 my $self = shift;
81 1         14 my $url = shift;
82              
83 1         4 $self->patch($url, $self->_mangle_request_arguments(@_));
84             }
85              
86             =head3 patch
87              
88             LWP::UserAgent doesn't actually implement a patch method, so it's defined
89             here.
90              
91             =cut
92              
93             sub patch {
94 1     1 1 5 my ($self, @parameters) = @_;
95 1         5 $self->_send_unimplemented_http_method(PATCH => @parameters);
96             }
97              
98             sub _send_unimplemented_http_method {
99 1     1   10 require HTTP::Request::Common;
100 1         5 my ($self, $method, @parameters) = @_;
101 1 50       8 my @suff = $self->_process_colonic_headers(\@parameters,
102             (ref($parameters[1]) ? 2 : 1));
103 1         31 return $self->request(
104             HTTP::Request::Common::request_type_with_data($method, @parameters),
105             @suff);
106             }
107              
108             sub _mangle_request_arguments {
109 6     6   9 my $self = shift;
110              
111             # If we have a reference as the first argument, remove it and replace
112             # it with a series of standard headers, so HTTP::Request::Common doesn't
113             # do its magic.
114 6 100       15 if (ref($_[0])) {
115 5         15 my $throwaway_request = HTTP::Request::JSON->new;
116 5         13 $throwaway_request->json_content($_[0]);
117 5         806 splice(
118             @_, 0, 1,
119             Content => $throwaway_request->content,
120             'Content-Type' => $throwaway_request->content_type,
121             Accept => 'application/json'
122             );
123             }
124 6         201 return @_;
125             }
126              
127             =head2 simple_request
128              
129             As LWP::UserAgent::simple_request, but returns a L
130             object instead of a L object if the response is JSON.
131              
132             =cut
133              
134             sub simple_request {
135 13     13 1 23161 my $self = shift;
136              
137 13         31 $self->rebless_maybe($_[0]);
138 13         199 my $response = $self->SUPER::simple_request(@_);
139 13         25603 $self->rebless_maybe($response);
140 13         193 return $response;
141             }
142              
143             =head2 rebless_maybe
144              
145             In: $object
146             Out: $reblessed
147              
148             Supplied with a HTTP::Request or HTTP::Response object, looks to see if it's a
149             JSON object, and if so reblesses it to be a HTTP::Request::JSON or
150             HTTP::Response::JSON object respectively. Returns whether it reblessed the
151             object or not.
152              
153             =cut
154              
155             sub rebless_maybe {
156 28     28 1 5204 my ($object) = pop;
157              
158             # Obviously, if the object isn't blessed yet, it doesn't make sense
159             # to rebless it.
160 28 50       96 return 0 if !Scalar::Util::blessed($object);
161              
162             # If the object doesn't have a content_type method, maybe that's because
163             # it doesn't have one *yet*?
164             # HTTP::Message is known to build methods like this via an AUTOLOAD,
165             # on demand, so if e.g. this was the response to a GET request where
166             # there was no explicit content type set in the request, and we hadn't
167             # done any content-type stuff in the same process previously, this will
168             # be the first time anyone has even tried to call this method.
169             # So see if we can trigger the creation of this method.
170 28 100       109 if (!$object->can('content_type')) {
171 1 50       6 if ($object->isa('HTTP::Message')) {
172 1         2 eval {
173 1         6 $object->content_type;
174             }
175             }
176             }
177 28 50       110 return 0 if !$object->can('content_type');
178              
179             # And if this isn't JSON, leave it as it is.
180 28 100       62 return 0 if $object->content_type ne 'application/json';
181              
182             # OK, time to rebless it into one of our objects instead.
183 7 100       198 if ($object->isa('HTTP::Response')) {
    50          
184 2         5 bless $object => 'HTTP::Response::JSON';
185 2         6 return 1;
186             } elsif ($object->isa('HTTP::Request')) {
187 5         9 bless $object => 'HTTP::Request::JSON';
188 5         8 return 1;
189             }
190              
191             # Huh. What the hell did we have, then? Oh well.
192 0           return 0;
193             }
194              
195             =head1 AUTHOR
196              
197             Sam Kington
198              
199             The source code for this module is hosted on GitHub
200             L - this is probably the
201             best place to look for suggestions and feedback.
202              
203             =head1 COPYRIGHT
204              
205             Copyright (c) 2015 Sam Kington.
206              
207             =head1 LICENSE
208              
209             This library is free software and may be distributed under the same terms as
210             perl itself.
211              
212             =cut
213              
214             1;