File Coverage

blib/lib/WebService/BitbucketServer.pm
Criterion Covered Total %
statement 78 144 54.1
branch 9 50 18.0
condition 6 28 21.4
subroutine 17 23 73.9
pod 3 3 100.0
total 113 248 45.5


line stmt bran cond sub pod time code
1             package WebService::BitbucketServer;
2             # ABSTRACT: Bindings for Bitbucket Server REST APIs
3              
4              
5 4     4   50033 use warnings;
  4         27  
  4         109  
6 4     4   17 use strict;
  4         7  
  4         131  
7              
8             our $VERSION = '0.603'; # VERSION
9              
10 4     4   962 use HTTP::AnyUA::Util qw(www_form_urlencode);
  4         8966  
  4         226  
11 4     4   1197 use HTTP::AnyUA;
  4         59102  
  4         111  
12 4     4   1070 use Module::Load qw(load);
  4         3165  
  4         23  
13 4     4   209 use Scalar::Util qw(weaken);
  4         7  
  4         208  
14 4     4   1341 use Types::Standard qw(Bool Object Str);
  4         219467  
  4         33  
15 4     4   4518 use WebService::BitbucketServer::Response;
  4         14  
  4         132  
16 4     4   1256 use WebService::BitbucketServer::Spec qw(api_info documentation_url);
  4         12  
  4         228  
17              
18 4     4   56 use Moo;
  4         8  
  4         21  
19 4     4   1087 use namespace::clean;
  4         9  
  4         12  
20              
21 0     0   0 sub _croak { require Carp; Carp::croak(@_) }
  0         0  
22 0     0   0 sub _usage { _croak("Usage: @_\n") }
23              
24 0 0   0   0 sub _debug_log { print STDERR join(' ', @_), "\n" if $ENV{PERL_WEBSERVICE_BITBUCKETSERVER_DEBUG} }
25              
26              
27             has base_url => (
28             is => 'ro',
29             isa => Str,
30             required => 1,
31             );
32              
33              
34             has path => (
35             is => 'lazy',
36             isa => Str,
37             default => 'rest',
38             );
39              
40              
41             has [qw(username password)] => (
42             is => 'ro',
43             isa => Str,
44             );
45              
46              
47             has ua => (
48             is => 'lazy',
49             default => sub {
50             load HTTP::Tiny;
51             HTTP::Tiny->new(
52             agent => "perl-webservice-bitbucketserver/$VERSION",
53             );
54             },
55             );
56              
57              
58             has any_ua => (
59             is => 'lazy',
60             isa => Object,
61             default => sub {
62             my $self = shift;
63             HTTP::AnyUA->new(ua => $self->ua);
64             },
65             );
66              
67              
68             has json => (
69             is => 'lazy',
70             isa => Object,
71             default => sub {
72             load JSON;
73             JSON->new->utf8(1);
74             },
75             );
76              
77              
78             has no_security_warning => (
79             is => 'rwp',
80             isa => Bool,
81             lazy => 1,
82             default => sub { $ENV{PERL_WEBSERVICE_BITBUCKETSERVER_NO_SECURITY_WARNING} || 0 },
83             );
84              
85              
86             my %api_accessors;
87             while (my ($namespace, $api) = each %WebService::BitbucketServer::Spec::API) {
88             my $method = $api->{id};
89             my $package = __PACKAGE__ . '::' . $api->{package};
90              
91             next if $api_accessors{$method};
92             $api_accessors{$method} = 1;
93              
94 4     4   2259 no strict 'refs'; ## no critic ProhibitNoStrict
  4         7  
  4         4825  
95             *{__PACKAGE__."::${method}"} = sub {
96 1     1   1406 my $self = shift;
97 1 50       5 return $self->{$method} if defined $self->{$method};
98 1         6 load $package;
99 1         13 my $api = $package->new(context => $self);
100 1         11 $self->{$method} = $api;
101 1         5 weaken($self->{$method});
102 1         5 return $api;
103             };
104             };
105              
106              
107             sub url {
108 4     4 1 9 my $self = shift;
109 4         16 my $base = $self->base_url;
110 4         83 my $path = $self->path;
111 4         124 $base =~ s!/+$!!;
112 4         9 $path =~ s!^/+!!;
113 4         18 return "$base/$path";
114             }
115              
116              
117             sub call {
118 4     4 1 2996 my $self = shift;
119 4 50 33     37 (@_ == 1 && ref($_[0]) eq 'HASH') || @_ % 2 == 0
      33        
120             or _usage(q{$api->call(method => $method, url => $url, %options)});
121 4 50       19 my $args = @_ == 1 ? shift : {@_};
122              
123 4 50       13 $args->{url} or _croak("url is required\n");
124              
125 4   50     16 my $method = $args->{method} || 'GET';
126 4         12 my $url = join('/', $self->url, $args->{url});
127              
128 4         7 my %options;
129 4         12 $options{headers}{Accept} = '*/*;q=0.2,application/json'; # prefer json response
130              
131 4         23 $self->_call_add_authorization($args, \%options);
132              
133             # request body
134 4         7 my $data = $args->{data};
135 4   50     18 my $data_type = $args->{data_type} || 'application/json';
136 4 50       14 if ($data) {
137 0 0 0     0 if ($method eq 'GET' || $method eq 'HEAD') {
138 0 0       0 my $params = ref($data) ? www_form_urlencode($data) : $data;
139 0 0       0 my $sep = $url =~ /\?/ ? '&' : '?';
140 0         0 $url .= "${sep}${params}";
141             }
142             else {
143 0 0 0     0 if ($data_type eq 'application/json' && ref($data)) {
144 0         0 $data = $self->json->encode($data);
145             }
146 0         0 $options{content} = $data;
147 0         0 $options{headers}{'content-type'} = $data_type;
148 0         0 $options{headers}{'content-length'} = length $data;
149             }
150             }
151              
152             my $handle_response = sub {
153 4     4   9 my $resp = shift;
154              
155 4 50       27 return $resp if $args->{raw};
156              
157 4         63 return WebService::BitbucketServer::Response->new(
158             context => $self,
159             request_args => $args,
160             raw => $resp,
161             json => $self->json,
162             );
163 4         18 };
164              
165 4         102 my $resp = $self->any_ua->request($method, $url, \%options);
166              
167 4 50       256 if ($self->any_ua->response_is_future) {
168 0         0 return $resp->transform(
169             done => $handle_response,
170             fail => $handle_response,
171             );
172             }
173             else {
174 4         55 return $handle_response->($resp);
175             }
176             }
177              
178             # add the authorization header to request options
179             sub _call_add_authorization {
180 4     4   10 my $self = shift;
181 4         7 my $args = shift;
182 4         6 my $opts = shift;
183              
184 4 50 33     37 if ($self->username && $self->password) {
185 4         12 my $url = $self->base_url;
186 4 50 33     68 if (!$self->no_security_warning && $url !~ /^https/) {
187 0         0 warn "Bitbucket Server authorization is being transferred unencrypted to $url !!!\n";
188 0         0 $self->_set_no_security_warning(0);
189             }
190              
191 4         123 my $payload = $self->username . ':' . $self->password;
192 4         867 require MIME::Base64;
193 4         1441 my $auth_token = MIME::Base64::encode_base64($payload, '');
194 4         18 $opts->{headers}{'authorization'} = "Basic $auth_token";
195             }
196             }
197              
198              
199             sub write_api_packages {
200 0     0 1   my $self = shift;
201 0 0 0       (@_ == 1 && ref($_[0]) eq 'HASH') || @_ % 2 == 0
      0        
202             or _usage(q{$api->write_api_packages(%args)});
203 0 0         my $args = @_ == 1 ? shift : {@_};
204              
205 0 0         $self = __PACKAGE__->new(base_url => '') unless ref $self;
206              
207 0           require WebService::BitbucketServer::WADL;
208              
209             my $handle_response = sub {
210 0     0     my $resp = shift;
211              
212 0 0         if (!$resp->{success}) {
213 0           warn "Failed to fetch $resp->{url} - $resp->{status} $resp->{reason}\n";
214 0           return;
215             }
216              
217 0           $self->_debug_log('Fetched WADL', $resp->{url});
218              
219 0           my $wadl = WebService::BitbucketServer::WADL::parse_wadl($resp->{content});
220 0           my ($package_code, $package) = WebService::BitbucketServer::WADL::generate_package($wadl, %$args, base => __PACKAGE__);
221              
222 0           require File::Path;
223 0           require File::Spec;
224              
225 0 0         my @pm = ($args->{dir} ? $args->{dir} : (), _mod_to_pm($package));
226 0           my $pm = File::Spec->catfile(@pm);
227 0           my $dir = File::Spec->catdir(@pm[0 .. (scalar @pm - 2)]);
228              
229 0           File::Path::make_path($dir);
230              
231             # write the pm
232 0 0         open(my $fh, '>', $pm) or die "open failed ($pm): $!";
233 0           print $fh $package_code;
234 0           close($fh);
235              
236 0           my $submap = WebService::BitbucketServer::WADL::generate_submap($wadl, %$args);
237              
238 0           my $api_info = api_info($wadl);
239 0 0         if (!$api_info) {
240 0           warn "Missing API info: $resp->{url}\n";
241 0           return;
242             }
243              
244 0           my $filename = "submap_$api_info->{id}.pl";
245              
246 0           my $filepath = File::Spec->catfile(qw{shares spec}, $filename);
247 0           $dir = File::Spec->catdir(qw{shares spec});
248              
249 0           File::Path::make_path($dir);
250              
251             # write the subroutine map
252 0 0         open($fh, '>', $filepath) or die "open failed ($filepath): $!";
253 0           print $fh $submap;
254 0           close($fh);
255 0           };
256              
257 0           my @responses;
258             my %requested;
259              
260 0           for my $namespace (keys %WebService::BitbucketServer::Spec::API) {
261 0           my $url = documentation_url($namespace, 'wadl', $args->{version});
262              
263 0 0         next if $requested{$url};
264 0           $requested{$url} = 1;
265              
266 0           my $resp = $self->any_ua->get($url);
267 0 0         if ($self->any_ua->response_is_future) {
268 0           push @responses, $resp->transform(
269             done => $handle_response,
270             fail => $handle_response,
271             );
272             }
273             else {
274 0           push @responses, $handle_response->($resp);
275             }
276             }
277              
278 0 0         if ($self->any_ua->response_is_future) {
279 0           return Future->wait_all(@responses);
280             }
281             else {
282 0           return \@responses;
283             }
284             }
285              
286             sub _mod_to_pm {
287 0     0     my $mod = shift;
288 0           my @parts = split(/::/, $mod);
289 0           $parts[-1] = "$parts[-1].pm";
290 0           return @parts;
291             }
292              
293             1;
294              
295             __END__