File Coverage

blib/lib/RestAPI.pm
Criterion Covered Total %
statement 80 89 89.8
branch 24 32 75.0
condition 5 9 55.5
subroutine 14 14 100.0
pod 1 2 50.0
total 124 146 84.9


line stmt bran cond sub pod time code
1             package RestAPI;
2 7     7   106104 use v5.14;
  7         32  
3             our $VERSION = "0.09";
4 7     7   3905 use Moo;
  7         56037  
  7         32  
5 7     7   10734 no warnings 'experimental';
  7         16  
  7         277  
6 7     7   4123 use Types::Standard qw( Any HashRef Bool Str Int );
  7         708241  
  7         105  
7 7     7   13795 use namespace::autoclean;
  7         39123  
  7         38  
8 7     7   6300 use XML::Simple qw( XMLin XMLout );
  7         66458  
  7         48  
9 7     7   5631 use JSON::XS ();
  7         27203  
  7         174  
10 7     7   1539 use LWP::UserAgent ();
  7         93211  
  7         185  
11 7     7   3778 use Time::HiRes qw( gettimeofday tv_interval );
  7         9678  
  7         45  
12              
13             # Basic construction params
14             has 'server' => ( is => 'rw', isa => Str );
15             has 'port' => ( is => 'rw', isa => Int );
16             has 'ssl_opts' => ( is => 'rw', isa => HashRef );
17             has 'basicAuth' => ( is => 'rw', isa => Bool);
18             has ['realm', 'username', 'password', 'scheme'] => ( is => 'rw' );
19             has 'timeout' => ( is => 'rw', isa => Int );
20              
21             # Added construction params
22             has 'headers' => ( is => 'rw', isa => HashRef, default => sub { {} } );
23             has 'query' => ( is => 'rw', isa => Str );
24             has 'path' => ( is => 'rw', isa => Str, trigger => \&_set_request );
25             has 'q_params' => ( is => 'rw', isa => HashRef, default => sub {{}}, trigger => \&_set_q_params );
26             has 'http_verb' => ( is => 'rw', isa => Str, default => 'GET' );
27             has 'payload' => ( is => 'rw', isa => Any, trigger => \&_set_payload );
28             has 'encoding' => ( is => 'rw', isa => Str );
29              
30             # other objects
31             has 'req' => ( is => 'ro', writer => '_set_req' );
32             has 'req_params' => ( is => 'ro', writer => '_set_req_params');
33             has 'ua' => ( is => 'rw', writer => '_set_ua' );
34             has 'jsonObj' => ( is => 'ro', default => sub {
35             return JSON::XS->new
36             ->utf8
37             ->allow_nonref
38             ->convert_blessed;
39             } );
40             has 'raw' => ( is => 'ro', writer => '_set_raw' );
41             has 'response' => ( is => 'ro', writer => '_set_response' );
42             has 'metrics' => ( is => 'ro', isa => HashRef, default => sub { {} } );
43              
44             # encodes the payload if not encoded already
45             sub _set_payload {
46 4     4   9501 my $self = shift;
47 4 100       76 if ( ref $self->payload ) {
48 2         23 my $str;
49 2         37 for ( $self->encoding ) {
50 2         23 when ( m|xml| ) {
51 0         0 $str = XMLout( $self->payload );
52             }
53 2         7 when ( m|json| ) {
54 2         38 $str = $self->jsonObj->encode( $self->payload );
55             }
56             }
57 2         63 $self->payload( $str );
58             }
59             }
60              
61             sub BUILD {
62 7     7 0 382 my $self = shift;
63 7         152 $self->_set_ua( LWP::UserAgent->new(
64             ssl_opts => $self->ssl_opts,
65             timeout => $self->timeout,
66             agent => 'RestAPI/0.0.8',
67             ));
68              
69 7 100 100     18844 $self->server( "$self->{server}:$self->{port}" ) if ( $self->{server} && $self->{port} );
70              
71 7 50       242 if ( $self->basicAuth ) {
72 0         0 $self->ua->credentials(
73             $self->server,
74             $self->realm,
75             $self->username,
76             $self->password
77             );
78             }
79              
80 7 100       126 if ( $self->scheme ) {
81 6         127 $self->server($self->scheme . '://' . $self->server);
82             }
83             }
84              
85             sub _set_q_params {
86 2     2   4765 my $self = shift;
87 2 100       3 return unless keys %{$self->q_params};
  2         39  
88 1         9 my $q_params;
89 1         2 while ( my ( $k, $v ) = each %{$self->q_params} ) {
  2         31  
90 1         12 $q_params .= '&'."$k=$v";
91             }
92 1         27 $self->_set_req_params( substr( $q_params, 1, length($q_params) - 1 ) );
93             }
94              
95             sub _set_request {
96 10     10   247 my $self = shift;
97              
98 10         25 my $url;
99 10 100       121 $url = $self->server if ( $self->{server} );
100              
101 10 100       219 if ( $self->query ) {
102 1 50 33     26 $self->{query} = '/'.$self->{query} if ( $url && $self->{query} !~ m|^/|);
103 1         18 $url .= $self->query;
104             }
105              
106 10 100       242 if ( $self->path ) {
107 9 100       116 $self->{path} = '/'.$self->{path} unless ( $self->{path} =~ m|^/| );
108 9         148 $url .= $self->path;
109             }
110              
111 10 50       105 $url .= '?'.$self->req_params if ($self->req_params);
112              
113 10         79 my $h = HTTP::Headers->new;
114 10 100       249 $h->content_type($self->encoding) if ( $self->encoding );
115              
116 10         236 while ( my ( $k, $v ) = each( %{$self->headers} ) ) {
  10         172  
117 0         0 $h->header( $k, $v );
118             }
119              
120 10         337 $self->_set_req( HTTP::Request->new( $self->http_verb, $url, $h, $self->payload ) );
121             }
122              
123             #===============================================================================
124              
125             =head2 do - executes the REST request or dies trying...
126              
127             =head3 INPUT
128              
129             none
130              
131             =head3 OUTPUT
132              
133             The response data object or the raw response if undecoded.
134              
135             =cut
136              
137             #===============================================================================
138             sub do {
139 4     4 1 22020 my $self = shift;
140              
141 4         20 $self->_set_request();
142              
143 4         621 my %headers;
144 4         36 my $t0 = [gettimeofday];
145 4         52 $self->_set_response( $self->ua->request( $self->req ) );
146 4         6651 $self->{metrics}->{'response_time'} = tv_interval( $t0, [gettimeofday] );
147              
148 4 50       122 die "Error: ".$self->response->status_line
149             unless ( $self->response->is_success );
150              
151 4         94 %headers = $self->response->flatten();
152 4         536 $self->_set_raw( $self->response->decoded_content );
153 4 50 33     811 if ( exists $headers{'Content-Transfer-Encoding'} &&
154             $headers{'Content-Transfer-Encoding'} eq 'binary' ) {
155 0         0 return $self->raw;
156             }
157            
158 4 50       22 my $r_encoding = $self->response->header("Content_Type")
159             or return $self->raw;
160              
161 4         174 my $outObj;
162 4         13 for ( $r_encoding ) {
163 4         13 when ( m|application/xml| ) {
164 0 0       0 if ( $self->raw =~ /^<\?xml/ ) {
165 0         0 $outObj = XMLin( $self->raw );
166             } else {
167 0         0 $outObj = $self->raw;
168             }
169             }
170 4         15 when ( m|application/json| ) {
171 4         57 $outObj = $self->jsonObj->decode( $self->raw );
172             }
173 0         0 when ( m|text| ) {
174 0         0 $outObj = $self->raw;
175             }
176             }
177 4         27 return $outObj;
178             }
179              
180             __PACKAGE__->meta->make_immutable;
181              
182             __END__
183              
184             #===============================================================================
185              
186             =head1 NAME
187              
188             RestAPI - a base module to interact with a REST API interface
189              
190             =head1 VERSION
191              
192             Version 0.09
193              
194              
195             =head1 SYNOPSIS
196              
197             use RestAPI;
198              
199             # a REST GET request
200             my $client = RestAPI->new(
201             basicAuth => 1,
202             realm => "Some Realm",
203             ssl_opts => { verify_hostname => 0 },
204             username => "foo",
205             password => "bar",
206             timeout => 10, # in secs
207             scheme => 'https', # if missing it is assumed comprised in the server or in the query
208             server => '...',
209             query => '...', # (maybe fixed) request part
210             path => '...', # added alongside the request
211             q_params => { foo => bar },
212             headers => { k => 'v' },
213             http_verb => 'GET', # any http verb...
214             encoding => 'application/xml' # or whatever...
215             );
216              
217             # a REST POST request
218             my $client = RestAPI->new(
219             basicAuth => 1,
220             realm => "Some Realm",
221             username => "foo",
222             password => "bar",
223             scheme => 'https',
224             timeout => 10, # in secs
225             server => '...',
226             query => '...',
227             path => '...',
228             q_params => { foo => bar },
229             http_verb => 'POST',
230             payload => '...',
231             encoding => 'application/xml'
232             );
233              
234             # a REST UPDATE request
235             my $client = RestAPI->new(
236             basicAuth => 1,
237             realm => "Some Realm",
238             username => "foo",
239             password => "bar",
240             scheme => 'https',
241             timeout => 10, # in secs
242             server => '...',
243             query => '...',
244             path => '...',
245             q_params => { foo => bar },
246             http_verb => 'PUT',
247             payload => '...',
248             encoding => 'application/xml'
249             );
250              
251             # a REST DELETE request
252             my $client = RestAPI->new(
253             basicAuth => 1,
254             realm => "Some Realm",
255             username => "foo",
256             password => "bar",
257             scheme => 'https',
258             timeout => 10, # in secs
259             server => '...',
260             query => '...',
261             path => '...',
262             q_params => { foo => bar },
263             http_verb => 'DELETE',
264             encoding => 'application/xml'
265             );
266              
267             try {
268             my $response_data = $client->do();
269              
270             # $self->response is the HTTP::Response object
271             # you get back from your request...
272             my %response_headers = $client->response->flatten();
273             } catch {
274             die "Error performing request, status line: $!\n";
275             }
276              
277             my $raw_response = $client->raw(); # the raw response.
278              
279             =head1 EXPORT
280              
281             None
282              
283             =head1 AUTHOR
284              
285             Marco Masetti, C<< <marco.masetti at sky.uk> >>
286              
287             =head1 SUPPORT
288              
289             You can find documentation for this module with the perldoc command.
290              
291             perldoc RestAPI
292              
293              
294             =head1 LICENSE AND COPYRIGHT
295              
296             Copyright 2017 Marco Masetti.
297              
298             This program is free software; you can redistribute it and/or modify it
299             under the terms of Perl itself.
300              
301             =cut
302              
303             #===============================================================================