File Coverage

blib/lib/WebService/Client.pm
Criterion Covered Total %
statement 64 109 58.7
branch 13 38 34.2
condition 5 13 38.4
subroutine 16 21 76.1
pod 7 8 87.5
total 105 189 55.5


line stmt bran cond sub pod time code
1             package WebService::Client;
2 1     1   9893 use Moo::Role;
  1         2  
  1         6  
3              
4             our $VERSION = '1.0000'; # VERSION
5              
6 1     1   365 use Carp qw(croak);
  1         2  
  1         66  
7 1     1   7 use HTTP::Request;
  1         2  
  1         30  
8 1     1   540 use HTTP::Request::Common qw(DELETE GET POST PUT);
  1         2314  
  1         69  
9 1     1   7 use JSON::MaybeXS qw();
  1         2  
  1         17  
10 1     1   4 use LWP::UserAgent;
  1         2  
  1         26  
11 1     1   444 use WebService::Client::Response;
  1         4  
  1         1829  
12              
13             has base_url => (
14             is => 'rw',
15             default => sub { '' },
16             );
17              
18             has ua => (
19             is => 'ro',
20             lazy => 1,
21             default => sub { LWP::UserAgent->new(timeout => shift->timeout) },
22             );
23              
24             has timeout => (
25             is => 'ro',
26             default => sub { 10 },
27             );
28              
29             has retries => (
30             is => 'ro',
31             default => sub { 0 },
32             isa => sub {
33             my $r = shift;
34             die 'retries must be a nonnegative integer'
35             unless defined $r and $r =~ /^\d+$/;
36             },
37             );
38              
39             has logger => ( is => 'ro' );
40              
41             has log_method => (
42             is => 'ro',
43             default => sub { 'DEBUG' },
44             );
45              
46             has content_type => (
47             is => 'rw',
48             default => sub { 'application/json' },
49             );
50              
51             has deserializer => (
52             is => 'ro',
53             lazy => 1,
54             default => sub {
55             my $self = shift;
56             my $json = $self->json;
57             sub {
58             my ($res, %args) = @_;
59             return $json->decode($res->content);
60             }
61             },
62             );
63              
64             has serializer => (
65             is => 'ro',
66             lazy => 1,
67             default => sub {
68             my $self = shift;
69             my $json = $self->json;
70             sub {
71             my ($data, %args) = @_;
72             # TODO: remove the next line after clients are updated to inject
73             # custom serializers that will handle this logic
74             return $data unless _content_type($args{headers}) =~ /json/;
75             return $json->encode($data);
76             }
77             }
78             );
79              
80             has json => (
81             is => 'ro',
82             lazy => 1,
83             default => sub { JSON::MaybeXS->new() },
84             );
85              
86             has mode => (
87             is => 'ro',
88             default => sub { '' },
89             );
90              
91             sub get {
92 1     1 1 1129 my ($self, $path, $params, %args) = @_;
93 1   50     7 $params ||= {};
94 1         3 my $headers = $self->_headers(\%args);
95 1         4 my $url = $self->_url($path);
96 1         2 my $q = '';
97 1 50       3 if (%$params) {
98 0         0 my @items;
99 0         0 while (my ($key, $value) = each %$params) {
100 0 0       0 if ('ARRAY' eq ref $value) {
101 0         0 push @items, map "$key\[]=$_", @$value;
102             }
103             else {
104 0         0 push @items, "$key=$value";
105             }
106             }
107 0 0       0 if (@items) {
108 0         0 $q = '?' . join '&', @items;
109             }
110             }
111 1         14 my $req = GET "$url$q", %$headers;
112 1         8216 return $self->req($req, %args);
113             }
114              
115             sub post {
116 0     0 1 0 my ($self, $path, $data, %args) = @_;
117 0         0 my $headers = $self->_headers(\%args);
118 0         0 my $url = $self->_url($path);
119 0         0 my $req = POST $url, %$headers, $self->_content($data, %args);
120 0         0 return $self->req($req, %args);
121             }
122              
123             sub put {
124 0     0 1 0 my ($self, $path, $data, %args) = @_;
125 0         0 my $headers = $self->_headers(\%args);
126 0         0 my $url = $self->_url($path);
127 0         0 my $req = PUT $url, %$headers, $self->_content($data, %args);
128 0         0 return $self->req($req, %args);
129             }
130              
131             sub patch {
132 0     0 1 0 my ($self, $path, $data, %args) = @_;
133 0         0 my $headers = $self->_headers(\%args);
134 0         0 my $url = $self->_url($path);
135 0         0 my %content = $self->_content($data, %args);
136             my $req = HTTP::Request->new(
137             'PATCH', $url, [%$headers], $content{content}
138 0         0 );
139 0         0 return $self->req($req, %args);
140             }
141              
142             sub delete {
143 0     0 1 0 my ($self, $path, %args) = @_;
144 0         0 my $headers = $self->_headers(\%args);
145 0         0 my $url = $self->_url($path);
146 0         0 my $req = DELETE $url, %$headers;
147 0         0 return $self->req($req, %args);
148             }
149              
150             sub req {
151 1     1 1 3 my ($self, $req, %args) = @_;
152 1         4 $self->_log_request($req);
153 1         24 my $res = $self->ua->request($req);
154 1         1384 $self->_log_response($res);
155              
156 1         3 my $retries = $self->retries;
157 1   33     3 while ($res->code =~ /^5/ and $retries--) {
158 0         0 sleep 1;
159 0         0 $res = $self->ua->request($req);
160 0         0 $self->_log_response($res);
161             }
162              
163 1         16 $self->prepare_response($res);
164              
165 1 50       5 if ($self->mode) {
166 0         0 return WebService::Client::Response->new(
167             res => $res,
168             json => $self->json,
169             );
170             }
171              
172 1 50 33     4 return if $req->method eq 'GET' and $res->code =~ /404|410/;
173 1 50       90 die $res unless $res->is_success;
174 1 50       16 return 1 unless $res->content;
175 1         36 my $des = $self->deserializer;
176 1 50       18 $des = $args{deserializer} if exists $args{deserializer};
177 1 50       4 if ($des) {
178 1 50       4 die 'deserializer must be a coderef or undef'
179             unless 'CODE' eq ref $des;
180 1         5 return $des->($res, %args);
181             }
182             else {
183 0         0 return $res->content;
184             }
185             }
186              
187             sub log {
188 2     2 1 341 my ($self, $msg) = @_;
189 2 50       10 return unless $self->logger;
190 0         0 my $log_method = $self->log_method;
191 0         0 $self->logger->$log_method($msg);
192             }
193              
194             sub prepare_response {
195 1     1 0 2 my ($self, $res) = @_;
196 1         12 Moo::Role->apply_roles_to_object($res, 'HTTP::Response::Stringable');
197 1         1688 return;
198             }
199              
200             sub _url {
201 1     1   2 my ($self, $path) = @_;
202 1 50       3 croak 'The path is missing' unless defined $path;
203 1 50       8 return $path =~ /^http/ ? $path : $self->base_url . $path;
204             }
205              
206             sub _headers {
207 1     1   2 my ($self, $args) = @_;
208 1   50     6 my $headers = $args->{headers} ||= {};
209 1 50       4 croak 'The headers param must be a hashref' unless 'HASH' eq ref $headers;
210 1 50       3 $headers->{content_type} = $self->content_type
211             unless _content_type($headers);
212 1         3 return $headers;
213             }
214              
215             sub _log_request {
216 1     1   2 my ($self, $req) = @_;
217 1         11 $self->log(ref($self) . " REQUEST:\n" . $req->as_string);
218             }
219              
220             sub _log_response {
221 1     1   2 my ($self, $res) = @_;
222 1         6 $self->log(ref($self) . " RESPONSE:\n" . $res->as_string);
223             }
224              
225             sub _content_type {
226 1     1   2 my ($headers) = @_;
227             return $headers->{'Content-Type'}
228             || $headers->{'content-type'}
229 1   33     30 || $headers->{content_type};
230             }
231              
232             sub _content {
233 0     0     my ($self, $data, %args) = @_;
234 0           my @content;
235 0 0         if (defined $data) {
236 0           my $ser = $self->serializer;
237 0 0         $ser = $args{serializer} if exists $args{serializer};
238 0 0         if ($ser) {
239 0 0         die 'serializer must be a coderef or undef'
240             unless 'CODE' eq ref $ser;
241 0           $data = $ser->($data, %args);
242             }
243 0           @content = ( content => $data );
244             }
245 0           return @content;
246             }
247              
248             # ABSTRACT: A base role for quickly and easily creating web service clients
249              
250              
251             1;
252              
253             __END__