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   52921 use warnings;
  4         27  
  4         112  
6 4     4   18 use strict;
  4         7  
  4         128  
7              
8             our $VERSION = '0.602'; # VERSION
9              
10 4     4   962 use HTTP::AnyUA::Util qw(www_form_urlencode);
  4         8596  
  4         272  
11 4     4   1102 use HTTP::AnyUA;
  4         58817  
  4         109  
12 4     4   1033 use Module::Load qw(load);
  4         3162  
  4         22  
13 4     4   228 use Scalar::Util qw(weaken);
  4         8  
  4         210  
14 4     4   1274 use Types::Standard qw(Bool Object Str);
  4         222186  
  4         36  
15 4     4   4487 use WebService::BitbucketServer::Response;
  4         16  
  4         130  
16 4     4   1132 use WebService::BitbucketServer::Spec qw(api_info documentation_url);
  4         10  
  4         209  
17              
18 4     4   24 use Moo;
  4         8  
  4         20  
19 4     4   1047 use namespace::clean;
  4         7  
  4         14  
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   2328 no strict 'refs'; ## no critic ProhibitNoStrict
  4         7  
  4         4427  
95             *{__PACKAGE__."::${method}"} = sub {
96 1     1   1349 my $self = shift;
97 1 50       4 return $self->{$method} if defined $self->{$method};
98 1         5 load $package;
99 1         13 my $api = $package->new(context => $self);
100 1         11 $self->{$method} = $api;
101 1         4 weaken($self->{$method});
102 1         4 return $api;
103             };
104             };
105              
106              
107             sub url {
108 4     4 1 7 my $self = shift;
109 4         15 my $base = $self->base_url;
110 4         80 my $path = $self->path;
111 4         120 $base =~ s!/+$!!;
112 4         8 $path =~ s!^/+!!;
113 4         17 return "$base/$path";
114             }
115              
116              
117             sub call {
118 4     4 1 2903 my $self = shift;
119 4 50 33     32 (@_ == 1 && ref($_[0]) eq 'HASH') || @_ % 2 == 0
      33        
120             or _usage(q{$api->call(method => $method, url => $url, %options)});
121 4 50       18 my $args = @_ == 1 ? shift : {@_};
122              
123 4 50       13 $args->{url} or _croak("url is required\n");
124              
125 4   50     15 my $method = $args->{method} || 'GET';
126 4         13 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         21 $self->_call_add_authorization($args, \%options);
132              
133             # request body
134 4         9 my $data = $args->{data};
135 4   50     19 my $data_type = $args->{data_type} || 'application/json';
136 4 50       12 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         71 return WebService::BitbucketServer::Response->new(
158             context => $self,
159             request_args => $args,
160             raw => $resp,
161             json => $self->json,
162             );
163 4         17 };
164              
165 4         111 my $resp = $self->any_ua->request($method, $url, \%options);
166              
167 4 50       314 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         61 return $handle_response->($resp);
175             }
176             }
177              
178             # add the authorization header to request options
179             sub _call_add_authorization {
180 4     4   8 my $self = shift;
181 4         6 my $args = shift;
182 4         8 my $opts = shift;
183              
184 4 50 33     28 if ($self->username && $self->password) {
185 4         10 my $url = $self->base_url;
186 4 50 33     67 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         126 my $payload = $self->username . ':' . $self->password;
192 4         770 require MIME::Base64;
193 4         1325 my $auth_token = MIME::Base64::encode_base64($payload, '');
194 4         17 $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__