File Coverage

blib/lib/Net/Async/Hetzner/Robot.pm
Criterion Covered Total %
statement 81 83 97.5
branch 9 12 75.0
condition 8 19 42.1
subroutine 27 27 100.0
pod 8 8 100.0
total 133 149 89.2


line stmt bran cond sub pod time code
1             package Net::Async::Hetzner::Robot;
2              
3             # ABSTRACT: Async Hetzner Robot API client for IO::Async
4              
5 2     2   170743 use strict;
  2         5  
  2         74  
6 2     2   11 use warnings;
  2         4  
  2         109  
7 2     2   16 use parent 'IO::Async::Notifier';
  2         3  
  2         17  
8              
9 2     2   3939 use Carp qw(croak);
  2         7  
  2         156  
10 2     2   12 use Future;
  2         4  
  2         58  
11 2     2   10 use URI;
  2         3  
  2         64  
12 2     2   339 use HTTP::Request;
  2         976  
  2         63  
13 2     2   1134 use WWW::Hetzner::Robot;
  2         173397  
  2         104  
14 2     2   17 use WWW::Hetzner::HTTPResponse;
  2         3  
  2         2890  
15              
16             our $VERSION = '0.003';
17              
18             sub configure {
19 8     8 1 349317 my ($self, %params) = @_;
20              
21 8 100       41 $self->{user} = delete $params{user} if exists $params{user};
22 8 100       26 $self->{password} = delete $params{password} if exists $params{password};
23 8 50       24 $self->{base_url} = delete $params{base_url} if exists $params{base_url};
24              
25 8         27 $self->SUPER::configure(%params);
26             }
27              
28             sub user {
29 8     8 1 33 my ($self) = @_;
30 8   66     72 $self->{user} // $ENV{HETZNER_ROBOT_USER};
31             }
32              
33             sub password {
34 7     7 1 12 my ($self) = @_;
35 7   33     29 $self->{password} // $ENV{HETZNER_ROBOT_PASSWORD};
36             }
37              
38             sub _check_auth {
39 1     1   18 my ($self) = @_;
40 1 50 33     7 unless ($self->user && $self->password) {
41 1         239 croak "No Robot credentials configured.\n\n"
42             . "Set credentials via:\n"
43             . " Environment: HETZNER_ROBOT_USER and HETZNER_ROBOT_PASSWORD\n"
44             . " Options: user => \$user, password => \$password\n\n"
45             . "Get credentials at: https://robot.hetzner.com/preferences/index\n";
46             }
47             }
48              
49 7   50 7 1 178 sub base_url { $_[0]->{base_url} // 'https://robot-ws.your-server.de' }
50              
51             # Internal: sync Robot instance for request building and response parsing
52             sub _robot {
53 6     6   48 my ($self) = @_;
54 6   33     37 $self->{_robot} //= WWW::Hetzner::Robot->new(
55             user => $self->user,
56             password => $self->password,
57             base_url => $self->base_url,
58             );
59             }
60              
61             # Internal: Net::Async::HTTP instance
62             sub _http {
63 10     10   19 my ($self) = @_;
64 10 50       25 unless ($self->{_http}) {
65 0         0 require Net::Async::HTTP;
66 0         0 $self->{_http} = Net::Async::HTTP->new(
67             user_agent => 'Net-Async-Hetzner/' . $VERSION,
68             max_connections_per_host => 0,
69             );
70             }
71 10         91 return $self->{_http};
72             }
73              
74             sub _add_to_loop {
75 5     5   360 my ($self, $loop) = @_;
76 5         12 $self->add_child($self->_http);
77             }
78              
79             # ============================================================================
80             # ASYNC HTTP TRANSPORT
81             # ============================================================================
82              
83             sub _do_request {
84 5     5   10 my ($self, $req) = @_;
85              
86 5         33 my $uri = URI->new($req->url);
87              
88 5         9026 my $http_req = HTTP::Request->new($req->method => $uri);
89 5         409 my $headers = $req->headers;
90 5         15 for my $key (keys %$headers) {
91 10         343 $http_req->header($key => $headers->{$key});
92             }
93 5 100       182 $http_req->content($req->content) if $req->has_content;
94              
95             return $self->_http->do_request(
96             request => $http_req,
97             )->then(sub {
98 5     5   1016 my ($response) = @_;
99 5   33     12 return Future->done(WWW::Hetzner::HTTPResponse->new(
      50        
100             status => $response->code,
101             content => $response->decoded_content // $response->content // '',
102             ));
103 5         44 });
104             }
105              
106             # ============================================================================
107             # ASYNC API METHODS - all return Futures
108             # ============================================================================
109              
110             sub get {
111 2     2 1 234 my ($self, $path, %params) = @_;
112 2         6 my $robot = $self->_robot;
113 2         67 my $req = $robot->_build_request('GET', $path, %params);
114             return $self->_do_request($req)->then(sub {
115 2     2   1852 my ($response) = @_;
116 2         15 return Future->done($robot->_parse_response($response, 'GET', $path));
117 2         3048 });
118             }
119              
120             sub post {
121 1     1 1 84 my ($self, $path, $data) = @_;
122 1         4 my $robot = $self->_robot;
123 1         31 my $req = $robot->_build_request('POST', $path, body => $data);
124             return $self->_do_request($req)->then(sub {
125 1     1   323 my ($response) = @_;
126 1         6 return Future->done($robot->_parse_response($response, 'POST', $path));
127 1         94 });
128             }
129              
130             sub put {
131 1     1 1 144 my ($self, $path, $data) = @_;
132 1         4 my $robot = $self->_robot;
133 1         35 my $req = $robot->_build_request('PUT', $path, body => $data);
134             return $self->_do_request($req)->then(sub {
135 1     1   246 my ($response) = @_;
136 1         4 return Future->done($robot->_parse_response($response, 'PUT', $path));
137 1         88 });
138             }
139              
140             sub delete {
141 1     1 1 76 my ($self, $path) = @_;
142 1         3 my $robot = $self->_robot;
143 1         23 my $req = $robot->_build_request('DELETE', $path);
144             return $self->_do_request($req)->then(sub {
145 1     1   184 my ($response) = @_;
146 1         5 return Future->done($robot->_parse_response($response, 'DELETE', $path));
147 1         58 });
148             }
149              
150             1;
151              
152             __END__