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 5     5   49230 use strict;
  5         13  
  5         130  
4 5     5   27 use warnings;
  5         10  
  5         113  
5 5     5   20 no warnings 'uninitialized';
  5         10  
  5         121  
6              
7 5     5   637 use HTTP::Request::JSON;
  5         12  
  5         98  
8 5     5   22 use LWP::JSON::Tiny;
  5         9  
  5         67  
9 5     5   22 use Scalar::Util ();
  5         8  
  5         69  
10 5     5   24 use parent 'LWP::UserAgent';
  5         10  
  5         34  
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 2819 my $self = shift;
47 4         8 my $url = shift;
48              
49 4         14 $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 900 my $self = shift;
62 1         2 my $url = shift;
63              
64 1         4 my @parameters = $self->_mangle_request_arguments(@_);
65 1 50       13 if ($self->SUPER::can('put')) {
66 1         8 $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 926 my $self = shift;
81 1         3 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 4 my ($self, @parameters) = @_;
95 1         4 $self->_send_unimplemented_http_method(PATCH => @parameters);
96             }
97              
98             sub _send_unimplemented_http_method {
99 1     1   7 require HTTP::Request::Common;
100 1         4 my ($self, $method, @parameters) = @_;
101 1 50       6 my @suff = $self->_process_colonic_headers(\@parameters,
102             (ref($parameters[1]) ? 2 : 1));
103 1         24 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   10 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       20 if (ref($_[0])) {
115 5         18 my $throwaway_request = HTTP::Request::JSON->new;
116 5         18 $throwaway_request->json_content($_[0]);
117 5         866 splice(
118             @_, 0, 1,
119             Content => $throwaway_request->content,
120             'Content-Type' => $throwaway_request->content_type,
121             Accept => 'application/json'
122             );
123             }
124 6         199 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 20515 my $self = shift;
136              
137 13         38 $self->rebless_maybe($_[0]);
138 13         228 my $response = $self->SUPER::simple_request(@_);
139 13         27419 $self->rebless_maybe($response);
140 13         230 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 29     29 1 5688 my ($object) = pop;
157              
158             # Obviously, if the object isn't blessed yet, it doesn't make sense
159             # to rebless it.
160 29 50       126 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 29 100       128 if (!$object->can('content_type')) {
171 2 50       18 if ($object->isa('HTTP::Message')) {
172 2         4 eval {
173 2         17 $object->content_type;
174             }
175             }
176             }
177 29 50       194 return 0 if !$object->can('content_type');
178              
179             # And if this isn't JSON, leave it as it is.
180 29 100       68 return 0 if $object->content_type ne 'application/json';
181              
182             # OK, time to rebless it into one of our objects instead.
183 8 100       247 if ($object->isa('HTTP::Response')) {
    50          
184 3         9 bless $object => 'HTTP::Response::JSON';
185 3         11 return 1;
186             } elsif ($object->isa('HTTP::Request')) {
187 5         10 bless $object => 'HTTP::Request::JSON';
188 5         9 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;