File Coverage

blib/lib/Apertur/SDK/HTTPClient.pm
Criterion Covered Total %
statement 42 104 40.3
branch 3 38 7.8
condition 0 9 0.0
subroutine 12 15 80.0
pod 3 3 100.0
total 60 169 35.5


line stmt bran cond sub pod time code
1             package Apertur::SDK::HTTPClient;
2              
3 1     1   5 use strict;
  1         1  
  1         28  
4 1     1   2 use warnings;
  1         1  
  1         32  
5              
6 1     1   807 use LWP::UserAgent;
  1         70410  
  1         51  
7 1     1   11 use HTTP::Request;
  1         2  
  1         37  
8 1     1   744 use HTTP::Request::Common qw(POST);
  1         2926  
  1         108  
9 1     1   907 use JSON qw(encode_json decode_json);
  1         12875  
  1         8  
10              
11 1     1   790 use Apertur::SDK::Error;
  1         4  
  1         49  
12 1     1   656 use Apertur::SDK::Error::Authentication;
  1         3  
  1         39  
13 1     1   604 use Apertur::SDK::Error::NotFound;
  1         4  
  1         39  
14 1     1   574 use Apertur::SDK::Error::RateLimit;
  1         3  
  1         23  
15 1     1   364 use Apertur::SDK::Error::Validation;
  1         3  
  1         1140  
16              
17             sub new {
18 3     3 1 14 my ($class, %args) = @_;
19 3         5 my $base_url = $args{base_url};
20 3         17 $base_url =~ s{/+$}{};
21              
22 3         6 my $auth_header = '';
23 3 100       7 if ($args{api_key}) {
    50          
24 2         5 $auth_header = "Bearer $args{api_key}";
25             }
26             elsif ($args{oauth_token}) {
27 1         2 $auth_header = "Bearer $args{oauth_token}";
28             }
29              
30 3         19 my $ua = LWP::UserAgent->new(
31             agent => 'Apertur-SDK-Perl/0.01',
32             timeout => 30,
33             );
34              
35 3         3887 return bless {
36             base_url => $base_url,
37             auth_header => $auth_header,
38             ua => $ua,
39             }, $class;
40             }
41              
42             sub request {
43 0     0 1   my ($self, $method, $path, %opts) = @_;
44              
45 0           my $url = $self->{base_url} . $path;
46 0   0       my $headers = $opts{headers} || {};
47              
48             $headers->{'Authorization'} = $self->{auth_header}
49 0 0         if $self->{auth_header};
50              
51 0           my $req;
52 0 0         if ($opts{multipart}) {
53             # Multipart form upload
54             $req = POST(
55             $url,
56             Content_Type => 'form-data',
57             Content => $opts{multipart},
58 0           );
59             # Apply auth header on top of the multipart request
60             $req->header('Authorization' => $headers->{'Authorization'})
61 0 0         if $headers->{'Authorization'};
62             # Apply any extra headers
63 0           for my $key (keys %$headers) {
64 0 0         next if $key eq 'Authorization';
65 0           $req->header($key => $headers->{$key});
66             }
67             }
68             else {
69 0           $req = HTTP::Request->new(uc($method), $url);
70 0           for my $key (keys %$headers) {
71 0           $req->header($key => $headers->{$key});
72             }
73 0 0         if (defined $opts{body}) {
74 0   0       $req->content_type($headers->{'Content-Type'} // 'application/json');
75 0           $req->content($opts{body});
76             }
77             }
78              
79 0           my $res;
80 0 0         if (defined $opts{timeout}) {
81 0           my $prev = $self->{ua}->timeout;
82 0           $self->{ua}->timeout($opts{timeout});
83 0           $res = eval { $self->{ua}->request($req) };
  0            
84 0           my $err = $@;
85 0           $self->{ua}->timeout($prev);
86 0 0         die $err if $err;
87             }
88             else {
89 0           $res = $self->{ua}->request($req);
90             }
91 0           my $status = $res->code;
92              
93 0 0         if ($status >= 400) {
94 0           $self->_handle_error($res);
95             }
96              
97 0 0         if ($status == 204) {
98 0           return undef;
99             }
100              
101 0           return decode_json($res->decoded_content);
102             }
103              
104             sub request_raw {
105 0     0 1   my ($self, $method, $path, %opts) = @_;
106              
107 0           my $url = $self->{base_url} . $path;
108 0   0       my $headers = $opts{headers} || {};
109              
110             $headers->{'Authorization'} = $self->{auth_header}
111 0 0         if $self->{auth_header};
112              
113 0           my $req = HTTP::Request->new(uc($method), $url);
114 0           for my $key (keys %$headers) {
115 0           $req->header($key => $headers->{$key});
116             }
117              
118 0           my $res = $self->{ua}->request($req);
119 0           my $status = $res->code;
120              
121 0 0         if ($status >= 400) {
122 0           $self->_handle_error($res);
123             }
124              
125 0           return $res->content;
126             }
127              
128             sub _handle_error {
129 0     0     my ($self, $res) = @_;
130 0           my $status = $res->code;
131              
132 0           my $body = {};
133 0           eval {
134 0           $body = decode_json($res->decoded_content);
135             };
136 0 0         if ($@) {
137 0           $body = { message => "HTTP $status" };
138             }
139              
140 0   0       my $message = $body->{message} || "HTTP $status";
141 0           my $code = $body->{code};
142              
143 0 0         if ($status == 401) {
    0          
    0          
    0          
144 0           Apertur::SDK::Error::Authentication->throw(message => $message);
145             }
146             elsif ($status == 404) {
147 0           Apertur::SDK::Error::NotFound->throw(message => $message);
148             }
149             elsif ($status == 429) {
150 0           my $retry_after = $res->header('Retry-After');
151 0 0         $retry_after = defined $retry_after ? int($retry_after) : undef;
152 0           Apertur::SDK::Error::RateLimit->throw(
153             message => $message,
154             retry_after => $retry_after,
155             );
156             }
157             elsif ($status == 400) {
158 0           Apertur::SDK::Error::Validation->throw(message => $message);
159             }
160             else {
161 0           Apertur::SDK::Error->throw(
162             status_code => $status,
163             code => $code,
164             message => $message,
165             );
166             }
167             }
168              
169             1;
170              
171             __END__