File Coverage

blib/lib/Nexus/REST.pm
Criterion Covered Total %
statement 33 114 28.9
branch 0 56 0.0
condition 0 14 0.0
subroutine 11 19 57.8
pod 4 4 100.0
total 48 207 23.1


line stmt bran cond sub pod time code
1             package Nexus::REST;
2             {
3             $Nexus::REST::VERSION = '0.001';
4             }
5             # ABSTRACT: Thin wrapper around Nexus's REST API
6              
7 1     1   26779 use 5.010;
  1         3  
  1         46  
8 1     1   1177 use utf8;
  1         9  
  1         5  
9 1     1   31 use strict;
  1         7  
  1         39  
10 1     1   5 use warnings;
  1         2  
  1         30  
11              
12 1     1   6 use Carp;
  1         1  
  1         97  
13 1     1   4172 use URI;
  1         9186  
  1         33  
14 1     1   1118 use MIME::Base64;
  1         813  
  1         70  
15 1     1   6 use URI::Escape;
  1         2  
  1         57  
16 1     1   1062 use JSON;
  1         15596  
  1         7  
17 1     1   976 use Data::Util qw/:check/;
  1         974  
  1         214  
18 1     1   818 use REST::Client;
  1         104860  
  1         1769  
19              
20             sub new {
21 0     0 1   my ($class, $URL, $username, $password, $rest_client_config) = @_;
22              
23 0 0         $URL = URI->new($URL) if is_string($URL);
24 0 0         is_instance($URL, 'URI')
25             or croak __PACKAGE__ . "::new: URL argument must be a string or a URI object.\n";
26              
27             # Append the service suffix if not already specified
28 0 0         unless ($URL->path =~ m@/service/local$@) {
29 0           $URL->path($URL->path . '/service/local');
30             }
31              
32             # If no password is set we try to lookup the credentials in the .netrc file
33 0 0         if (! defined $password) {
34 0 0         eval {require Net::Netrc}
  0            
35             or croak "Can't require Net::Netrc module. Please, specify the USERNAME and PASSWORD.\n";
36 0 0         if (my $machine = Net::Netrc->lookup($URL->host, $username)) { # $username may be undef
37 0           $username = $machine->login;
38 0           $password = $machine->password;
39             } else {
40 0           croak "No credentials found in the .netrc file.\n";
41             }
42             }
43              
44 0 0         is_string($username)
45             or croak __PACKAGE__ . "::new: USERNAME argument must be a string.\n";
46              
47 0 0         is_string($password)
48             or croak __PACKAGE__ . "::new: PASSWORD argument must be a string.\n";
49              
50 0 0         $rest_client_config = {} unless defined $rest_client_config;
51 0 0         is_hash_ref($rest_client_config)
52             or croak __PACKAGE__ . "::new: REST_CLIENT_CONFIG argument must be a hash-ref.\n";
53              
54 0           my $rest = REST::Client->new($rest_client_config);
55              
56             # Set default base URL
57 0           $rest->setHost($URL);
58              
59             # Follow redirects/authentication by default
60 0           $rest->setFollow(1);
61              
62             # Since Nexus doesn't send an authentication chalenge, we may
63             # simply force the sending of the authentication header.
64 0           $rest->addHeader(Authorization => 'Basic ' . encode_base64("$username:$password"));
65              
66 0           $rest->addHeader(accept => 'application/json');
67              
68             # Configure UserAgent name
69 0           $rest->getUseragent->agent(__PACKAGE__);
70              
71 0           return bless {
72             rest => $rest,
73             json => JSON->new->utf8->allow_nonref,
74             } => $class;
75             }
76              
77             sub _error {
78 0     0     my ($self, $content, $type, $code) = @_;
79              
80 0 0         $type = 'text/plain' unless $type;
81 0 0         $code = 500 unless $code;
82              
83 0           my $msg = __PACKAGE__ . " Error[$code";
84              
85 0 0         if (eval {require HTTP::Status}) {
  0            
86 0 0         if (my $status = HTTP::Status::status_message($code)) {
87 0           $msg .= " - $status";
88             }
89             }
90              
91 0           $msg .= "]:\n";
92              
93 0 0 0       if ($type =~ m:text/plain:i) {
    0          
    0          
94 0           $msg .= $content;
95 0           } elsif ($type =~ m:application/json:) {
96 0           my $error = $self->{json}->decode($content);
97 0 0 0       if (ref $error eq 'HASH' && exists $error->{errorMessages}) {
98 0           foreach my $message (@{$error->{errorMessages}}) {
  0            
99 0           $msg .= "- $message\n";
100             }
101             } else {
102 0           $msg .= $content;
103             }
104             } elsif ($type =~ m:text/html:i && eval {require HTML::TreeBuilder}) {
105 0           $msg .= HTML::TreeBuilder->new_from_content($content)->as_text;
106             } else {
107 0           $msg .= "";
108             };
109 0           $msg =~ s/\n*$/\n/s; # end message with a single newline
110 0           return $msg;
111             }
112              
113             sub _content {
114 0     0     my ($self) = @_;
115              
116 0           my $rest = $self->{rest};
117 0           my $code = $rest->responseCode();
118 0           my $type = $rest->responseHeader('Content-Type');
119 0           my $content = $rest->responseContent();
120              
121 0 0         $code =~ /^2/
122             or croak $self->_error($content, $type, $code);
123              
124 0 0         return unless $content;
125              
126 0 0         if (! defined $type) {
    0          
    0          
127 0           croak $self->_error("Cannot convert response content with no Content-Type specified.");
128             } elsif ($type =~ m:^application/json:i) {
129 0           return $self->{json}->decode($content);
130             } elsif ($type =~ m:^text/plain:i) {
131 0           return $content;
132             } else {
133 0           croak $self->_error("I don't understand content with Content-Type '$type'.");
134             }
135             }
136              
137             sub _build_query {
138 0     0     my ($self, $query) = @_;
139              
140 0 0         is_hash_ref($query) or croak $self->_error("The QUERY argument must be a hash-ref.");
141              
142 0           return '?'. join('&', map {$_ . '=' . uri_escape($query->{$_})} keys %$query);
  0            
143             }
144              
145             sub GET {
146 0     0 1   my ($self, $path, $query) = @_;
147              
148 0 0         $path .= $self->_build_query($query) if $query;
149              
150 0           $self->{rest}->GET($path);
151              
152 0           return $self->_content();
153             }
154              
155             sub DELETE {
156 0     0     my ($self, $path, $query) = @_;
157              
158 0 0         $path .= $self->_build_query($query) if $query;
159              
160 0           $self->{rest}->DELETE($path);
161              
162 0           return $self->_content();
163             }
164              
165             sub PUT {
166 0     0 1   my ($self, $path, $query, $value, $headers) = @_;
167              
168 0 0         $path .= $self->_build_query($query) if $query;
169              
170 0   0       $headers //= {};
171 0   0       $headers->{'Content-Type'} //= 'application/json;charset=UTF-8';
172              
173 0           $self->{rest}->PUT($path, $self->{json}->encode($value), $headers);
174              
175 0           return $self->_content();
176             }
177              
178             sub POST {
179 0     0 1   my ($self, $path, $query, $value, $headers) = @_;
180              
181 0 0         $path .= $self->_build_query($query) if $query;
182              
183 0   0       $headers //= {};
184 0   0       $headers->{'Content-Type'} //= 'application/json;charset=UTF-8';
185              
186 0           $self->{rest}->POST($path, $self->{json}->encode($value), $headers);
187              
188 0           return $self->_content();
189             }
190              
191             1;
192              
193             __END__