File Coverage

blib/lib/WebService/DigitalOcean/Role/UserAgent.pm
Criterion Covered Total %
statement 43 56 76.7
branch 5 12 41.6
condition 0 2 0.0
subroutine 10 12 83.3
pod 1 1 100.0
total 59 83 71.0


line stmt bran cond sub pod time code
1             package WebService::DigitalOcean::Role::UserAgent;
2             # ABSTRACT: User Agent Role for DigitalOcean WebService
3 2     2   9339 use Moo::Role;
  2         3  
  2         15  
4 2     2   583 use LWP::UserAgent;
  2         3  
  2         47  
5 2     2   7 use JSON ();
  2         5  
  2         66  
6 2     2   12 use DateTime;
  2         2  
  2         69  
7 2     2   10 use Types::Standard qw/is_HashRef/;
  2         2  
  2         36  
8 2     2   972 use utf8;
  2         4  
  2         16  
9              
10             our $VERSION = '0.026'; # VERSION
11              
12             has ua => (
13             is => 'lazy',
14             );
15              
16             sub _build_ua {
17 0     0   0 my ($self) = @_;
18              
19 0   0     0 my $version = __PACKAGE__->VERSION || 'devel';
20              
21 0         0 my @headers = (
22             'Authorization' => 'Bearer ' . $self->token,
23             'Content-Type' => 'application/json; charset=utf-8',
24             );
25              
26 0         0 my $ua = LWP::UserAgent->new(
27             agent => 'WebService::DigitalOcean/' . $version,
28             default_headers => HTTP::Headers->new(@headers),
29             );
30              
31 0         0 $ua->env_proxy;
32              
33 0         0 return $ua;
34             }
35              
36             sub _build_request {
37 1     1   3 my ( $self, $method, $uri, $data ) = @_;
38              
39 1         10 my $full_url = $self->api_base_url . $uri;
40 1 50       43 my $encoded_data = $data ? JSON::encode_json($data) : undef;
41 1         3 my $headers = undef;
42              
43 1         13 return HTTP::Request->new( $method, $full_url, $headers, $encoded_data );
44             }
45              
46             sub _send_request {
47 0     0   0 my ($self, $request) = @_;
48              
49 0         0 my $response = $self->ua->request($request);
50              
51 0         0 return $response;
52             }
53              
54             sub make_request {
55 1     1 1 5 my ($self, $method, $uri, $data) = @_;
56              
57 1         7 my $request = $self->_build_request( $method, $uri, $data );
58 1         12036 my $response = $self->_send_request($request);
59              
60 1         14 my $result = {
61             request_object => $request,
62             response_object => $response,
63             is_success => $response->is_success,
64             status_line => $response->status_line,
65             };
66              
67 1         43 my $ratelimit = $self->_get_ratelimit( $response->headers );
68              
69 1         815 my $content = $self->_get_content(
70             # avoid ``wantarray`` problems by setting scalar context
71             my $ct = $response->content_type,
72             my $dc = $response->decoded_content,
73             );
74              
75 1         20 return { %$result, %$ratelimit, %$content };
76             }
77              
78             sub _get_ratelimit {
79 1     1   9 my ($self, $headers) = @_;
80              
81 1         7 my $limit = $headers->header('RateLimit-Limit');
82              
83 1 50       97 if (!$limit) {
84 0         0 return {};
85             }
86              
87             return {
88 1         7 ratelimit => {
89             limit => $limit,
90             remaining => $headers->header('RateLimit-Remaining'),
91             reset => DateTime->from_epoch(
92             epoch => $headers->header('RateLimit-Reset')
93             ),
94             }
95             };
96             }
97              
98             sub _get_content {
99 1     1   291 my ($self, $content_type, $content) = @_;
100              
101 1 50       5 if ($content_type ne 'application/json') {
102             # Delete method returns 'application/octet-stream' according to the API
103             # docs, though it is a blank string here. No need to warn on this
104             # expected behavior.
105 0 0       0 warn "Unexpected Content-Type " . $content_type
106             if length $content_type;
107              
108 0         0 return {};
109             }
110             else {
111 1         61 my $decoded_response = JSON::decode_json( $content );
112              
113 1 50       12 if ( !is_HashRef($decoded_response) ) {
114 0         0 return { content => $decoded_response };
115             }
116              
117 1         5 my $meta = delete $decoded_response->{meta};
118 1         6 my $links = delete $decoded_response->{links};
119              
120 1         6 my @values = values %$decoded_response;
121              
122 1 50       7 my $c = scalar @values == 1
123             ? $values[0]
124             : $decoded_response
125             ;
126              
127             return {
128 1         10 meta => $meta,
129             links => $links,
130             content => $c,
131             };
132             }
133             }
134              
135             1;
136              
137             __END__
138              
139             =pod
140              
141             =encoding UTF-8
142              
143             =head1 NAME
144              
145             WebService::DigitalOcean::Role::UserAgent - User Agent Role for DigitalOcean WebService
146              
147             =head1 VERSION
148              
149             version 0.026
150              
151             =head1 DESCRIPTION
152              
153             Role used to make requests to the DigitalOcean API, and to format their response.
154              
155             =head1 METHODS
156              
157             =head2 make_request
158              
159             my $res = $self->make_request(POST => '/domains', {
160             name => 'example.com',
161             ip_address => '12.34.56.78',
162             });
163              
164             =head3 Arguments
165              
166             =over
167              
168             =item C<Str> $method
169              
170             The HTTP verb, such as POST, GET, PUT, etc.
171              
172             =item C<Str> $path
173              
174             Path to the resource in the URI, to be prepended with $self->api_base_url.
175              
176             =item C<HashRef> $data (optional)
177              
178             The content to be JSON encoded and sent to DigitalOcean's API.
179              
180             =back
181              
182             =head3 Returns
183              
184             HashRef containing:
185              
186             =over
187              
188             =item L<HTTP::Response> response_object
189              
190             =item C<Bool> is_success
191              
192             Shortcut to $res->{response_object}{is_success}.
193              
194             =item C<Str> status_line
195              
196             Shortcut to $res->{response_object}{status_line}.
197              
198             =item C<HashRef> content
199              
200             The JSON decoded content the API has responded with.
201              
202             =item C<HashRef> ratelimit
203              
204             RateLimit headers parsed.
205              
206             =over
207              
208             =item C<Int> limit
209              
210             =item C<Int> remaining
211              
212             =item L<DateTime> reset
213              
214             =back
215              
216             =back
217              
218             Makes requests to the DigitalOcean, and parses the response.
219              
220             All requests made from other methods use L</make_request> to make them.
221              
222             More info: L<< https://developers.digitalocean.com/#introduction >>.
223              
224             =head1 AUTHOR
225              
226             André Walker <andre@cpan.org>
227              
228             =head1 COPYRIGHT AND LICENSE
229              
230             This software is Copyright (c) 2015 by André Walker.
231              
232             This is free software, licensed under:
233              
234             The GNU General Public License, Version 2, June 1991
235              
236             =cut