File Coverage

blib/lib/Gerrit/REST.pm
Criterion Covered Total %
statement 27 94 28.7
branch 0 48 0.0
condition 0 3 0.0
subroutine 9 16 56.2
pod 4 4 100.0
total 40 165 24.2


line stmt bran cond sub pod time code
1             package Gerrit::REST;
2             {
3             $Gerrit::REST::VERSION = '0.011';
4             }
5             # ABSTRACT: Thin wrapper around Gerrit's REST API
6              
7 1     1   53208 use 5.010;
  1         4  
  1         102  
8 1     1   1078 use utf8;
  1         11  
  1         7  
9 1     1   38 use strict;
  1         7  
  1         33  
10 1     1   4 use warnings;
  1         2  
  1         30  
11              
12 1     1   5 use Carp;
  1         2  
  1         91  
13 1     1   1022 use URI;
  1         9263  
  1         36  
14 1     1   1302 use JSON;
  1         17784  
  1         8  
15 1     1   1144 use Data::Util qw/:check/;
  1         1234  
  1         6249  
16 1     1   944 use REST::Client;
  1         113451  
  1         1510  
17              
18             sub new {
19 0     0 1   my ($class, $URL, $username, $password, $rest_client_config) = @_;
20              
21 0 0         $URL = URI->new($URL) if is_string($URL);
22 0 0         is_instance($URL, 'URI')
23             or croak __PACKAGE__ . "::new: URL argument must be a string or a URI object.\n";
24              
25             # If no password is set we try to lookup the credentials in the .netrc file
26 0 0         if (! defined $password) {
27 0 0         eval {require Net::Netrc}
  0            
28             or croak "Can't require Net::Netrc module. Please, specify the USERNAME and PASSWORD.\n";
29 0 0         if (my $machine = Net::Netrc->lookup($URL->host, $username)) { # $username may be undef
30 0           $username = $machine->login;
31 0           $password = $machine->password;
32             } else {
33 0           croak "No credentials found in the .netrc file.\n";
34             }
35             }
36              
37 0 0         is_string($username)
38             or croak __PACKAGE__ . "::new: USERNAME argument must be a string.\n";
39              
40 0 0         is_string($password)
41             or croak __PACKAGE__ . "::new: PASSWORD argument must be a string.\n";
42              
43 0 0         $rest_client_config = {} unless defined $rest_client_config;
44 0 0         is_hash_ref($rest_client_config)
45             or croak __PACKAGE__ . "::new: REST_CLIENT_CONFIG argument must be a hash-ref.\n";
46              
47 0           my $rest = REST::Client->new($rest_client_config);
48              
49             # Set default base URL
50 0           $rest->setHost($URL);
51              
52             # Follow redirects/authentication by default
53 0           $rest->setFollow(1);
54              
55             # Request compact JSON by default
56 0           $rest->addHeader('Accept' => 'application/json');
57              
58             # Configure UserAgent name and password authentication
59 0           for my $ua ($rest->getUseragent) {
60 0           $ua->agent(__PACKAGE__);
61 0           $ua->credentials($URL->host_port, 'Gerrit Code Review', $username, $password);
62             }
63              
64 0           return bless {
65             rest => $rest,
66             json => JSON->new->utf8->allow_nonref,
67             } => $class;
68             }
69              
70             sub _error {
71 0     0     my ($self, $content, $type, $code) = @_;
72              
73 0 0         $type = 'text/plain' unless $type;
74 0 0         $code = 500 unless $code;
75              
76 0           my $msg = __PACKAGE__ . " Error[$code";
77              
78 0 0         if (eval {require HTTP::Status}) {
  0            
79 0 0         if (my $status = HTTP::Status::status_message($code)) {
80 0           $msg .= " - $status";
81             }
82             }
83              
84 0           $msg .= "]:\n";
85              
86 0 0 0       if ($type =~ m:text/plain:i) {
  0 0          
87 0           $msg .= $content;
88             } elsif ($type =~ m:text/html:i && eval {require HTML::TreeBuilder}) {
89 0           $msg .= HTML::TreeBuilder->new_from_content($content)->as_text;
90             } else {
91 0           $msg .= "";
92             };
93 0           $msg =~ s/\n*$/\n/s; # end message with a single newline
94 0           return $msg;
95             }
96              
97             sub _content {
98 0     0     my ($self) = @_;
99              
100 0           my $rest = $self->{rest};
101 0           my $code = $rest->responseCode();
102 0           my $type = $rest->responseHeader('Content-Type');
103 0           my $content = $rest->responseContent();
104              
105 0 0         $code =~ /^2/
106             or croak $self->_error($content, $type, $code);
107              
108 0 0         if (! defined $type) {
    0          
    0          
109 0           return;
110             } elsif ($type =~ m:^application/json:i) {
111 0 0         if (substr($content, 0, 4) eq ")]}'") {
112 0           return $self->{json}->decode(substr($content, 5));
113             } else {
114 0           croak $self->_error("Missing \")]}'\" prefix for JSON content:\n\n$content");
115             }
116             } elsif ($type =~ m:^text/plain:i) {
117 0           return $content;
118             } else {
119 0           croak $self->_error("I don't understand content with Content-Type '$type'");
120             }
121             }
122              
123             sub GET {
124 0     0 1   my ($self, $resource) = @_;
125              
126 0 0         eval { $self->{rest}->GET("/a$resource") }
  0            
127             or croak $self->_error("Error in GET(/a$resource): $@");
128              
129 0           return $self->_content();
130             }
131              
132             sub DELETE {
133 0     0     my ($self, $resource) = @_;
134              
135 0 0         eval { $self->{rest}->DELETE("/a$resource") }
  0            
136             or croak $self->_error("Error in DELETE(/a$resource): $@");
137              
138 0           return $self->_content();
139             }
140              
141             sub PUT {
142 0     0 1   my ($self, $resource, $value) = @_;
143              
144 0 0         eval { $self->{rest}->PUT(
  0            
145             "/a$resource",
146             $self->{json}->encode($value),
147             {'Content-Type' => 'application/json;charset=UTF-8'},
148             ) }
149             or croak $self->_error("Error in PUT(/a$resource, ...): $@");
150              
151 0           return $self->_content();
152             }
153              
154             sub POST {
155 0     0 1   my ($self, $resource, $value) = @_;
156              
157 0 0         eval { $self->{rest}->POST(
  0            
158             "/a$resource",
159             $self->{json}->encode($value),
160             {'Content-Type' => 'application/json;charset=UTF-8'},
161             ) }
162             or croak $self->_error("Error in POST(/a$resource, ...): $@");
163              
164 0           return $self->_content();
165             }
166              
167             1;
168              
169             __END__