File Coverage

blib/lib/LWP/UserAgent/JSON.pm
Criterion Covered Total %
statement 58 59 98.3
branch 13 18 72.2
condition n/a
subroutine 14 14 100.0
pod 6 6 100.0
total 91 97 93.8


line stmt bran cond sub pod time code
1             package LWP::UserAgent::JSON;
2              
3 5     5   30617 use strict;
  5         9  
  5         132  
4 5     5   22 use warnings;
  5         8  
  5         137  
5 5     5   22 no warnings 'uninitialized';
  5         10  
  5         145  
6              
7 5     5   622 use HTTP::Request::JSON;
  5         12  
  5         124  
8 5     5   24 use LWP::JSON::Tiny;
  5         10  
  5         70  
9 5     5   21 use Scalar::Util ();
  5         11  
  5         68  
10 5     5   25 use parent 'LWP::UserAgent';
  5         14  
  5         41  
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 2724 my $self = shift;
47 4         10 my $url = shift;
48              
49 4         18 $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              
56             =cut
57              
58             sub put_json {
59 1     1 1 831 my $self = shift;
60 1         3 my $url = shift;
61              
62 1         4 $self->SUPER::put($url, $self->_mangle_request_arguments(@_));
63             }
64              
65             =head3 patch_json
66              
67             As post_json and put_json, but generates a PATCH request instead.
68              
69             =cut
70              
71             sub patch_json {
72 1     1 1 831 my $self = shift;
73 1         2 my $url = shift;
74              
75 1         5 $self->patch($url, $self->_mangle_request_arguments(@_));
76             }
77              
78             =head3 patch
79              
80             LWP::UserAgent doesn't actually implement a patch method, so it's defined
81             here.
82              
83             =cut
84              
85             sub patch {
86 1     1 1 8 require HTTP::Request::Common;
87 1         4 my ($self, @parameters) = @_;
88 1 50       7 my @suff = $self->_process_colonic_headers(\@parameters,
89             (ref($parameters[1]) ? 2 : 1));
90 1         29 return $self->request(
91             HTTP::Request::Common::request_type_with_data('PATCH', @parameters),
92             @suff);
93             }
94              
95             sub _mangle_request_arguments {
96 6     6   14 my $self = shift;
97              
98             # If we have a reference as the first argument, remove it and replace
99             # it with a series of standard headers, so HTTP::Request::Common doesn't
100             # do its magic.
101 6 100       24 if (ref($_[0])) {
102 5         20 my $throwaway_request = HTTP::Request::JSON->new;
103 5         20 $throwaway_request->json_content($_[0]);
104 5         992 splice(
105             @_, 0, 1,
106             Content => $throwaway_request->content,
107             'Content-Type' => $throwaway_request->content_type,
108             Accept => 'application/json'
109             );
110             }
111 6         220 return @_;
112             }
113              
114             =head2 simple_request
115              
116             As LWP::UserAgent::simple_request, but returns a L
117             object instead of a L object if the response is JSON.
118              
119             =cut
120              
121             sub simple_request {
122 13     13 1 24696 my $self = shift;
123              
124 13         39 $self->rebless_maybe($_[0]);
125 13         236 my $response = $self->SUPER::simple_request(@_);
126 13         30077 $self->rebless_maybe($response);
127 13         236 return $response;
128             }
129              
130             =head2 rebless_maybe
131              
132             In: $object
133             Out: $reblessed
134              
135             Supplied with a HTTP::Request or HTTP::Response object, looks to see if it's a
136             JSON object, and if so reblesses it to be a HTTP::Request::JSON or
137             HTTP::Response::JSON object respectively. Returns whether it reblessed the
138             object or not.
139              
140             =cut
141              
142             sub rebless_maybe {
143 29     29 1 6349 my ($object) = pop;
144              
145             # Obviously, if the object isn't blessed yet, it doesn't make sense
146             # to rebless it.
147 29 50       180 return 0 if !Scalar::Util::blessed($object);
148              
149             # If the object doesn't have a content_type method, maybe that's because
150             # it doesn't have one *yet*?
151             # HTTP::Message is known to build methods like this via an AUTOLOAD,
152             # on demand, so if e.g. this was the response to a GET request where
153             # there was no explicit content type set in the request, and we hadn't
154             # done any content-type stuff in the same process previously, this will
155             # be the first time anyone has even tried to call this method.
156             # So see if we can trigger the creation of this method.
157 29 100       143 if (!$object->can('content_type')) {
158 2 50       18 if ($object->isa('HTTP::Message')) {
159 2         6 eval {
160 2         17 $object->content_type;
161             }
162             }
163             }
164 29 50       194 return 0 if !$object->can('content_type');
165              
166             # And if this isn't JSON, leave it as it is.
167 29 100       78 return 0 if $object->content_type ne 'application/json';
168              
169             # OK, time to rebless it into one of our objects instead.
170 8 100       264 if ($object->isa('HTTP::Response')) {
    50          
171 3         7 bless $object => 'HTTP::Response::JSON';
172 3         13 return 1;
173             } elsif ($object->isa('HTTP::Request')) {
174 5         14 bless $object => 'HTTP::Request::JSON';
175 5         11 return 1;
176             }
177              
178             # Huh. What the hell did we have, then? Oh well.
179 0           return 0;
180             }
181              
182             =head1 AUTHOR
183              
184             Sam Kington
185              
186             The source code for this module is hosted on GitHub
187             L - this is probably the
188             best place to look for suggestions and feedback.
189              
190             =head1 COPYRIGHT
191              
192             Copyright (c) 2015 Sam Kington.
193              
194             =head1 LICENSE
195              
196             This library is free software and may be distributed under the same terms as
197             perl itself.
198              
199             =cut
200              
201             1;