File Coverage

blib/lib/WWW/GoodData/Agent.pm
Criterion Covered Total %
statement 12 43 27.9
branch 0 16 0.0
condition 0 2 0.0
subroutine 4 9 44.4
pod 5 5 100.0
total 21 75 28.0


line stmt bran cond sub pod time code
1             package WWW::GoodData::Agent;
2              
3             =head1 NAME
4              
5             WWW::GoodData::Agent - HTTP client for GoodData JSON-based API
6              
7             =head1 SYNOPSIS
8              
9             use WWW::GoodData::Agent;
10             my $ua = new WWW::GoodData::Agent;
11             my $metadata = $ua->get ('/md');
12              
13             =head1 DESCRIPTION
14              
15             B is HTTP user agent that makes it easy for follow
16             specifics of the GoodData service API, transparently handles conversion
17             to and from JSON content type and recognizes and handles various kinds
18             of exceptions and error states.
19              
20             It is a subclass of L and follows its semantics unless
21             documented otherwise.
22              
23             =cut
24              
25 1     1   36495 use strict;
  1         2  
  1         42  
26 1     1   5 use warnings;
  1         2  
  1         32  
27              
28 1     1   6 use base qw/LWP::UserAgent/;
  1         10  
  1         1287  
29 1     1   86141 use JSON;
  1         14752  
  1         5  
30              
31             our $VERSION = '1.0';
32              
33             =head1 PROPERTIES
34              
35             =over 4
36              
37             =item root
38              
39             L object pointing to root of the service API.
40              
41             This is used to resolve relative request paths.
42              
43             =back
44              
45             =head1 METHODS
46              
47             =over 4
48              
49             =item new ROOT, PARAMS
50              
51             Creates a new agent instance. First argument is root of
52             the service API, the rest is passed to L as is.
53              
54             Compared to stock L, it has a memory-backed
55             cookie storage and sets the B header to prefer JSON content.
56              
57             =cut
58              
59             sub new
60             {
61 0     0 1   my ($self, $root, @args) = @_;
62 0           $self = $self->SUPER::new (@args);
63 0           $self->{root} = $root;
64 0           $self->agent ("perl-WWW-GoodData/$VERSION ");
65             # Not backed by a file yet
66 0           $self->cookie_jar ({});
67             # Prefer JSON, but deal with whatever else comes in, instead of letting backend return 406s
68 0           $self->default_header (Accept =>
69             'application/json;q=0.9, text/plain;q=0.2, */*;q=0.1');
70 0           return $self;
71             }
72              
73             =item post URI, BODY, PARAMS
74              
75             Constructs and issues a POST request.
76              
77             Compared to stock L, the extra body parameter
78             is encoded into JSON and set as request content, which is the
79             only way to set the request content.
80              
81             The rest of parameters are passed to L untouched.
82              
83             =cut
84              
85             sub post
86             {
87 0     0 1   my ($self, $uri, $body, @args) = @_;
88 0           push @args,'Content-Type' => 'application/json',
89             Content => encode_json ($body);
90 0           return $self->SUPER::post ($uri, @args);
91             }
92              
93             =item put URI, BODY, PARAMS
94              
95             Constructs and issues a PUT request.
96              
97             Compared to stock L, the extra body parameter
98             is encoded into JSON and set as request content, which is the
99             only way to set the request content.
100              
101             The rest of parameters are passed to L untouched.
102              
103             =cut
104              
105             sub put
106             {
107 0     0 1   my ($self, $uri, $body, @args) = @_;
108 0           push @args,'Content-Type' => 'application/json',
109             Content => encode_json ($body);
110 0           return $self->SUPER::put ($uri, @args);
111             }
112              
113             =item delete URI
114              
115             Convenience method for constructing and issuing a DELETE request.
116              
117             =cut
118              
119             sub delete
120             {
121 0     0 1   my ($self, $uri) = @_;
122 0           return $self->request (new HTTP::Request (DELETE => $uri));
123             }
124              
125             =item request PARAMS
126              
127             This call is common for all request types.
128              
129             While API is same as stock L, relative URIs
130             are permitted and extra content processing is done with the response.
131              
132             Namely, errors are either handled or turned into exceptions
133             and known content types (JSON) are decoded.
134              
135             =cut
136              
137             sub request
138             {
139 0     0 1   my ($self, $request, @args) = @_;
140              
141             # URI relative to root
142 0           $request->uri ($request->uri->abs ($self->{root}));
143              
144             # Issue the request
145 0           my $response = $self->SUPER::request ($request, @args);
146              
147             # Pass processed response from subrequest (redirect)
148 0 0         return $response if ref $response eq 'HASH';
149              
150             # Do not bother checking content and type if there's none
151 0 0         return undef if $response->code == 204;
152              
153             # Decode
154 0 0         my $decoded = eval { decode_json ($response->content) }
  0            
155             if $response->header ('Content-Type') =~ /^application\/json(;.*)?/;
156 0 0         $decoded = {
157             type => $response->header ('Content-Type'),
158             raw => $response->content,
159             } unless $decoded;
160              
161             # Error handling
162 0 0         unless ($response->is_success) {
163             # Apache::Error exceptions lack error wrapper
164 0 0         $decoded = $decoded->{error} if exists $decoded->{error};
165 0   0       my $request_id = $response->header ('X-GDC-Request') || "";
166 0 0         $request_id = " (Request ID: $request_id)" if $request_id;
167 0 0         die $response->status_line.$request_id unless exists $decoded->{message};
168 0           die sprintf ($decoded->{message}, @{$decoded->{parameters}}).$request_id;
  0            
169             }
170              
171 0           return $decoded;
172             }
173              
174             =back
175              
176             =head1 SEE ALSO
177              
178             =over
179              
180             =item *
181              
182             L -- Browsable GoodData API
183              
184             =item *
185              
186             L -- Perl HTTP client
187            
188             =back
189              
190             =head1 COPYRIGHT
191              
192             Copyright 2011, 2012, 2013 Lubomir Rintel
193              
194             Copyright 2012 Jan Orel
195              
196             This program is free software; you can redistribute it and/or modify it
197             under the same terms as Perl itself.
198              
199             =head1 AUTHOR
200              
201             Lubomir Rintel C
202              
203             =cut
204              
205             1;