File Coverage

blib/lib/JSON/API.pm
Criterion Covered Total %
statement 118 128 92.1
branch 37 50 74.0
condition 10 16 62.5
subroutine 22 24 91.6
pod 10 10 100.0
total 197 228 86.4


line stmt bran cond sub pod time code
1             package JSON::API;
2 4     4   180734 use strict;
  4         6  
  4         105  
3 4     4   1163 use HTTP::Status qw/:constants/;
  4         8929  
  4         1382  
4 4     4   2443 use LWP::UserAgent;
  4         93583  
  4         101  
5 4     4   1724 use JSON;
  4         24245  
  4         16  
6 4     4   2471 use Data::Dumper;
  4         17970  
  4         207  
7 4     4   1569 use URI::Encode qw/uri_encode/;
  4         31620  
  4         207  
8              
9             BEGIN {
10 4     4   21 use Exporter ();
  4         4  
  4         68  
11 4     4   12 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
  4         5  
  4         311  
12 4     4   9 $VERSION = v1.1.0;
13 4         28 @ISA = qw(Exporter);
14             #Give a hoot don't pollute, do not export more than needed by default
15 4         6 @EXPORT = qw();
16 4         29 @EXPORT_OK = qw();
17 4         3991 %EXPORT_TAGS = ();
18             }
19              
20             sub _debug
21             {
22 154     154   6585 my ($self, @lines) = @_;
23 154         329 my $output = join('\n', @lines);
24 154 100       471 print STDERR $output . "\n" if ($self->{debug});
25             }
26              
27             sub _server
28             {
29 39     39   40 my ($self, $input) = @_;
30 39         143 $input =~ s|^(https?://)?||;
31 39         138 $input =~ m|^([^\s:/]+)(:\d+)?.*|;
32 39   100     172 $input = $1 . ($2 || '');
33 39         60 return $input;
34             }
35              
36             sub _http_req
37             {
38 22     22   59 my ($self, $method, $path, $data, $apphdr) = @_;
39 22         88 $self->_debug('_http_req called with the following:',Dumper($method,$path,$data, $apphdr));
40              
41 22         183 my $url = $self->url($path);
42 22         64 $self->_debug("URL calculated to be: $url");
43 22         159 delete $self->{response};
44              
45 22         94 my $headers = HTTP::Headers->new(
46             'Accept' => 'application/json',
47             'Content-Type' => 'application/json',
48             );
49 22 50 33     1273 if( $apphdr && ref $apphdr ) {
50 0         0 $headers->header( $_, $apphdr->{$_} ) foreach (keys %$apphdr);
51             }
52 22         22 my $json;
53 22 100       57 if (defined $data) {
54 6         18 $json = $self->_encode($data);
55 6 100       22 return (wantarray ? (500, {}) : {}) unless defined $json;
    100          
56             }
57              
58 20         142 my $req = HTTP::Request->new($method, $url, $headers, $json);
59 20         2876 $self->_debug("Requesting: ",Dumper($req));
60 20         205 my $res = $self->{user_agent}->request($req);
61              
62 20         147426 $self->_debug("Response: ",Dumper($res));
63 20         299 $self->{response} = $res;
64 20 100       111 if ($res->is_success) {
    50          
65 14         238 $self->{has_error} = 0;
66 14         32 $self->{error_string} = '';
67 14         28 $self->_debug("Successful request detected");
68             } elsif ($res->code == HTTP_NOT_MODIFIED) {
69             return wantarray ?
70 0 0       0 ($res->code, {}) :
71             {};
72             } else {
73 6         132 $self->{has_error} = 1;
74 6         23 $self->{error_string} = $res->content;
75 6         70 $self->_debug("Error detected: ".$self->{error_string});
76             # If internal warning, return before decoding, as it will fail + overwrite the error_string
77 6 50       18 if ($res->header('client-warning') =~ m/internal response/i) {
78 0 0       0 return wantarray ? ($res->code, {}) : {};
79             }
80             }
81 20 100 100     300 my $decoded = $res->content ? ($self->_decode($res->content) || {}) : {};
82              
83             #FIXME: should we auto-populate an error key in the {} if error detected but no content?
84             return wantarray ?
85 20 100       132 ($res->code, $decoded) :
86             $decoded;
87             }
88              
89             sub _encode
90             {
91 8     8   14 my ($self, $obj) = @_;
92              
93 8         9 my $json = undef;
94             eval {
95 8         24 $json = to_json($obj);
96 5         107 $self->_debug("JSON created: $json");
97 8 50       9 } or do {
98 8 100       71 if ($@) {
99 3         6 $self->{has_error} = 1;
100 3         7 $self->{error_string} = $@;
101 3         36 $self->{error_string} =~ s/\s+at\s+\S+\s+line\s+\d+\.?\s*//;
102 3         11 $self->_debug("Error serializing json from \$obj:" . $self->{error_string});
103             }
104             };
105 8         19 return $json;
106             }
107              
108             sub _decode
109             {
110 20     20   297 my ($self, $json) = @_;
111              
112 20         31 $self->_debug("Deserializing JSON");
113 20         26 my $obj = undef;
114             eval {
115 20         99 $obj = from_json($json);
116 17         466 $self->_debug("Deserializing successful:",Dumper($obj));
117 20 50       33 } or do {
118 20 100       113 if ($@) {
119 3         6 $self->{has_error} = 1;
120 3         6 $self->{error_string} = $@;
121 3         26 $self->{error_string} =~ s/\s+at\s+\S+\s+line\s+\d+\.?\s*//;
122 3         9 $self->_debug("Error deserializing: ".$self->{error_string});
123             }
124             };
125 20         120 return $obj;
126             }
127              
128             sub new
129             {
130 32     32 1 23873 my ($class, $base_url, %parameters) = @_;
131 32 100       125 return undef unless $base_url;
132              
133 31         83 my %ua_opts = %parameters;
134 31         45 map { delete $parameters{$_}; } qw(user pass realm debug);
  124         128  
135              
136 31         132 my $ua = LWP::UserAgent->new(%parameters);
137              
138             my $self = bless ({
139             base_url => $base_url,
140             user_agent => $ua,
141             has_error => 0,
142             error_string => '',
143             debug => $ua_opts{debug},
144 31   33     9728 }, ref ($class) || $class);
145              
146 31         66 my $server = $self->_server($base_url);
147 31 100       57 my $default_port = $base_url =~ m|^https://| ? 443 : 80;
148 31 100       98 $server .= ":$default_port" unless $server =~ /:\d+$/;
149             $ua->credentials($server, $ua_opts{realm}, $ua_opts{user}, $ua_opts{pass})
150 31 100 66     81 if ($ua_opts{realm} && $ua_opts{user} && $ua_opts{pass});
      66        
151              
152 31         101 return $self;
153             }
154              
155             sub get
156             {
157 12     12 1 90335 my ($self, $path, $data, $apphdr) = @_;
158 12 100       49 if ($data) {
159 2         8 my @qp = map { "$_=".uri_encode($data->{$_}, { encode_reserved => 1 }) } sort keys %$data;
  4         1797  
160 2         1350 $path .= "?".join("&", @qp);
161             }
162 12         74 $self->_http_req("GET", $path, undef, $apphdr);
163             }
164              
165             sub put
166             {
167 4     4 1 7479 my ($self, $path, $data, $apphdr) = @_;
168 4         12 $self->_http_req("PUT", $path, $data, $apphdr);
169             }
170              
171             sub post
172             {
173 4     4 1 5950 my ($self, $path, $data, $apphdr) = @_;
174 4         12 $self->_http_req("POST", $path, $data, $apphdr);
175             }
176              
177             sub del
178             {
179 2     2 1 3419 my ($self, $path, $apphdr) = @_;
180 2         6 $self->_http_req("DELETE", $path, undef, $apphdr);
181             }
182              
183             sub url
184             {
185 26     26 1 49 my ($self, $path) = @_;
186 26         138 my $url = $self->{base_url} . "/$path";
187             # REGEX-FU: look through the URL, replace any matches of /+ with '/',
188             # as long as the previous character was not a ':'
189             # (e.g. http://example.com//api//mypath/ becomes http://example.com/api/mypath/
190 26         246 $url =~ s|(?
191 26         115 return $url;
192             }
193              
194             sub response
195             {
196 0     0 1 0 my ($self) = @_;
197              
198 0         0 return $self->{response};
199             }
200              
201             sub header
202             {
203 0     0 1 0 my ($self, $name) = @_;
204              
205 0 0       0 return unless( $self->{response} );
206              
207 0 0       0 unless( $name ) {
208 0         0 return $self->{response}->header_field_names;
209             }
210 0         0 return $self->{response}->header( $name );
211             }
212              
213             sub errstr
214             {
215 5     5 1 2401 my ($self) = @_;
216 5 100       8 return ! $self->was_success ? $self->{error_string} : '';
217             }
218              
219             sub was_success
220             {
221 7     7 1 11 my ($self) = @_;
222 7 100       35 return $self->{has_error} ? 0 : 1;
223             }
224              
225             1;
226              
227             __END__