File Coverage

blib/lib/WWW/VastAI/Role/HTTP.pm
Criterion Covered Total %
statement 65 69 94.2
branch 13 24 54.1
condition 3 15 20.0
subroutine 16 17 94.1
pod 4 4 100.0
total 101 129 78.2


line stmt bran cond sub pod time code
1             package WWW::VastAI::Role::HTTP;
2             our $VERSION = '0.001';
3             # ABSTRACT: Shared synchronous HTTP client role for Vast.ai API consumers
4              
5 10     10   110452 use Moo::Role;
  10         24  
  10         68  
6 10     10   5629 use Carp qw(croak);
  10         46  
  10         873  
7 10     10   2010 use JSON::MaybeXS qw(decode_json encode_json);
  10         44744  
  10         795  
8 10     10   5417 use Log::Any qw($log);
  10         98087  
  10         55  
9 10     10   24415 use URI;
  10         25  
  10         299  
10 10     10   5156 use WWW::VastAI::HTTPRequest;
  10         39  
  10         419  
11 10     10   5041 use WWW::VastAI::LWPIO;
  10         64  
  10         9801  
12              
13             requires 'api_key';
14             requires 'base_url';
15              
16             has io => (
17             is => 'lazy',
18 0     0   0 builder => sub { WWW::VastAI::LWPIO->new },
19             );
20              
21             sub get {
22 15     15 1 54 my ($self, $path, %opts) = @_;
23 15         86 return $self->_request('GET', $path, %opts);
24             }
25              
26             sub post {
27 12     12 1 46 my ($self, $path, $body, %opts) = @_;
28 12         65 return $self->_request('POST', $path, %opts, body => $body);
29             }
30              
31             sub put {
32 10     10 1 37 my ($self, $path, $body, %opts) = @_;
33 10         41 return $self->_request('PUT', $path, %opts, body => $body);
34             }
35              
36             sub delete {
37 9     9 1 44 my ($self, $path, $body, %opts) = @_;
38 9 100       176 return $self->_request('DELETE', $path, %opts, (defined $body ? (body => $body) : ()));
39             }
40              
41             sub _set_auth {
42 46     46   93 my ($self, $headers) = @_;
43 46         193 $headers->{Authorization} = 'Bearer ' . $self->api_key;
44             }
45              
46             sub _build_query_uri {
47 2     2   7 my ($self, $url, $params) = @_;
48              
49 2         26 my $uri = URI->new($url);
50 2 50       26713 for my $name (sort keys %{$params || {}}) {
  2         18  
51 3         475 my $value = $params->{$name};
52 3 50       12 next unless defined $value;
53 3 100       26 $value = encode_json($value) if ref $value;
54 3         26 $uri->query_param($name => $value);
55             }
56              
57 2         1080 return $uri->as_string;
58             }
59              
60             sub _build_request {
61 46     46   155 my ($self, $method, $path, %opts) = @_;
62              
63 46   33     181 my $base_url = $opts{base_url} || $self->base_url;
64 46         98 my $url = $base_url . $path;
65 46 100       123 $url = $self->_build_query_uri($url, $opts{params}) if $opts{params};
66              
67 46         183 my %headers = (
68             'Content-Type' => 'application/json',
69             );
70 46         226 $self->_set_auth(\%headers);
71              
72 46         188 my %args = (
73             method => $method,
74             url => $url,
75             headers => \%headers,
76             );
77              
78 46 100       126 if (exists $opts{body}) {
79 25         182 $args{content} = encode_json($opts{body});
80 25         133 $log->debugf('%s %s body=%s', $method, $url, $args{content});
81             }
82             else {
83 21         125 $log->debugf('%s %s', $method, $url);
84             }
85              
86 46         1321 return WWW::VastAI::HTTPRequest->new(%args);
87             }
88              
89             sub _parse_response {
90 46     46   152 my ($self, $response, $method, $path) = @_;
91              
92 46         140 my $content = $response->content;
93 46         74 my $data;
94 46 50 33     369 if (defined $content && $content =~ /^\s*[\{\[]/) {
    0 0        
95 46         392 $data = decode_json($content);
96             }
97             elsif (defined $content && length $content) {
98 0         0 $data = $content;
99             }
100              
101 46 50 33     269 if ($response->status < 200 || $response->status >= 300) {
102             my $message = ref $data eq 'HASH'
103 0 0 0     0 ? ($data->{msg} || $data->{message} || $data->{error} || $response->status)
    0          
104             : (defined $data ? $data : $response->status);
105 0         0 croak "Vast.ai API error: $message";
106             }
107              
108 46         382 $log->infof('%s %s -> %s', $method, $path, $response->status);
109 46         764 return $data;
110             }
111              
112             sub _request {
113 46     46   179 my ($self, $method, $path, %opts) = @_;
114              
115 46 50       161 croak "No Vast.ai API key configured" unless $self->api_key;
116              
117 46         161 my $req = $self->_build_request($method, $path, %opts);
118 46         13938 my $response = $self->io->call($req);
119 46         45395 return $self->_parse_response($response, $method, $path);
120             }
121              
122             1;
123              
124             __END__