File Coverage

blib/lib/WWW/GoodData/Agent.pm
Criterion Covered Total %
statement 12 42 28.5
branch 0 14 0.0
condition 0 2 0.0
subroutine 4 9 44.4
pod 5 5 100.0
total 21 72 29.1


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   13598 use strict;
  1         2  
  1         24  
26 1     1   3 use warnings;
  1         1  
  1         23  
27              
28 1     1   4 use base qw/LWP::UserAgent/;
  1         1  
  1         602  
29 1     1   45573 use JSON;
  1         12289  
  1         7  
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             # Decode
151 0 0         my $decoded = eval { decode_json ($response->content) }
  0            
152             if $response->header ('Content-Type') =~ /^application\/json(;.*)?/;
153 0 0         $decoded = {
154             type => $response->header ('Content-Type'),
155             raw => $response->content,
156             } unless $decoded;
157              
158             # Error handling
159 0 0         unless ($response->is_success) {
160             # Apache::Error exceptions lack error wrapper
161 0 0         $decoded = $decoded->{error} if exists $decoded->{error};
162 0   0       my $request_id = $response->header ('X-GDC-Request') || "";
163 0 0         $request_id = " (Request ID: $request_id)" if $request_id;
164 0 0         die $response->status_line.$request_id unless exists $decoded->{message};
165 0           die sprintf ($decoded->{message}, @{$decoded->{parameters}}).$request_id;
  0            
166             }
167              
168 0           return $decoded;
169             }
170              
171             =back
172              
173             =head1 SEE ALSO
174              
175             =over
176              
177             =item *
178              
179             L -- Browsable GoodData API
180              
181             =item *
182              
183             L -- Perl HTTP client
184            
185             =back
186              
187             =head1 COPYRIGHT
188              
189             Copyright 2011, Lubomir Rintel
190              
191             This program is free software; you can redistribute it and/or modify it
192             under the same terms as Perl itself.
193              
194             =head1 AUTHOR
195              
196             Lubomir Rintel C
197              
198             =cut
199              
200             1;