File Coverage

blib/lib/Net/OpenStack/Client/Request.pm
Criterion Covered Total %
statement 91 96 94.7
branch 29 32 90.6
condition 13 15 86.6
subroutine 13 13 100.0
pod 7 7 100.0
total 153 163 93.8


line stmt bran cond sub pod time code
1             package Net::OpenStack::Client::Request;
2             $Net::OpenStack::Client::Request::VERSION = '0.1.4';
3 11     11   601 use strict;
  11         26  
  11         276  
4 11     11   54 use warnings;
  11         23  
  11         309  
5              
6 11     11   51 use base qw(Exporter);
  11         18  
  11         1146  
7 11     11   4845 use Readonly;
  11         37836  
  11         1112  
8              
9             Readonly our @SUPPORTED_METHODS => qw(DELETE GET PATCH POST PUT);
10             Readonly our @METHODS_REQUIRE_OPTIONS => qw(PATCH POST PUT);
11              
12             our @EXPORT = qw(mkrequest);
13             our @EXPORT_OK = qw(parse_endpoint @SUPPORTED_METHODS @METHODS_REQUIRE_OPTIONS $HDR_X_AUTH_TOKEN);
14              
15 11     11   7289 use overload bool => '_boolean';
  11         6194  
  11         61  
16              
17             Readonly our $HDR_ACCEPT => 'Accept';
18             Readonly our $HDR_ACCEPT_ENCODING => 'Accept-Encoding';
19             Readonly our $HDR_CONTENT_TYPE => 'Content-Type';
20             Readonly our $HDR_X_AUTH_TOKEN => 'X-Auth-Token';
21             Readonly our $HDR_X_SUBJECT_TOKEN => 'X-Subject-Token';
22              
23              
24             Readonly my %DEFAULT_HEADERS => {
25             $HDR_ACCEPT => 'application/json, text/plain',
26             $HDR_ACCEPT_ENCODING => 'identity, gzip, deflate, compress',
27             $HDR_CONTENT_TYPE => 'application/json',
28             };
29              
30              
31             =head1 NAME
32              
33             Net::OpenStack::Client::Request is an request class for Net::OpenStack.
34              
35             Boolean logic is overloaded using C<_boolean> method (as inverse of C).
36              
37             =head2 Public functions
38              
39             =over
40              
41             =item mkrequest
42              
43             A C factory
44              
45             =cut
46              
47             sub mkrequest
48             {
49 69     69 1 611 return Net::OpenStack::Client::Request->new(@_);
50             }
51              
52             =item parse_endpoint
53              
54             Parse C and look for templates and parameters.
55              
56             Return (possibly modified) endpoint, arrayref of template names
57             and arrayref of parameter names.
58              
59             If C is passed, report an error and return; else die on failure.
60              
61             =cut
62              
63             sub parse_endpoint
64             {
65 65     65 1 149 my ($origendpoint, $logger) = @_;
66              
67             # strip parameters
68 65         399 my ($endpoint, $paramtxt) = $origendpoint =~ /^([^?]+)(?:\?([^?]+))?$/;
69              
70             # List of key names that have to be passed
71 65         117 my (@templates, @params);
72 65         231 foreach my $template ($endpoint =~ m#\{([^/]+)}#g) {
73             # only add once; order is not that relevant
74 52 100       149 push(@templates, $template) if (!grep {$_ eq $template} @templates);
  28         88  
75             };
76              
77 65   100     295 foreach my $kv (split(/&/, $paramtxt || '')) {
78 68 50       209 if ($kv =~ m/^([^=]+)=/) {
79 68         216 push(@params, $1);
80             } else {
81             # invalid
82 0         0 my $msg = "invalid parameter kv $kv for origendpoint $origendpoint";
83 0 0       0 if ($logger) {
84 0         0 $logger->error($msg);
85 0         0 return;
86             } else {
87 0         0 die $msg;
88             }
89             }
90             }
91              
92 65         243 return $endpoint, \@templates, \@params;
93             }
94              
95              
96             =pod
97              
98             =back
99              
100             =head2 Public methods
101              
102             =over
103              
104             =item new
105              
106             Create new request instance from options for command C
107             and REST HTTP C.
108              
109             The C is the URL to use (can be templated with C)
110              
111             Options
112              
113             =over
114              
115             =item tpls: template names and values
116              
117             =item opts: optional arguments
118              
119             =item error: an error (no default)
120              
121             =item service: service name
122              
123             =item version: service version
124              
125             =item result: result path for the response
126              
127             =back
128              
129             =cut
130              
131             sub new
132             {
133 71     71 1 6988 my ($this, $endpoint, $method, %opts) = @_;
134 71   33     296 my $class = ref($this) || $this;
135             my $self = {
136             endpoint => $endpoint,
137              
138             tpls => $opts{tpls} || {},
139             params => $opts{params} || {},
140             opts => $opts{opts} || {},
141             paths => $opts{paths} || {},
142             raw => $opts{raw},
143              
144             rest => $opts{rest} || {}, # options for rest
145              
146             error => $opts{error}, # no default
147              
148             service => $opts{service},
149             version => $opts{version},
150             result => $opts{result},
151 71   100     793 };
      100        
      100        
      100        
      100        
152              
153 71 100       246 if (grep {$method eq $_} @SUPPORTED_METHODS) {
  355         2182  
154 70         137 $self->{method} = $method;
155             } else {
156 1         4 $self->{error} = "Unsupported method $method";
157             }
158              
159 71         211 bless $self, $class;
160              
161 71         484 return $self;
162             };
163              
164             =item endpoint
165              
166             Parses the endpoint attribute, look for any templates, and replace them with values
167             from C attribute hashref.
168             Any parameters defined in the endpoint are removed, and only those that
169             are present in the C attribute are readded with the values from the attribute.
170              
171             The data can contain more keys than what is required
172             for templating, those keys and their values will be ignored.
173              
174             This does not modify the endpoint attribute.
175              
176             Return templated endpoint on success or undef on failure.
177              
178             If host is defined, try to make a full URL
179              
180             =over
181              
182             =item if you provide only fqdn, make a https:///v
183              
184             =item if you provide URL, check for version suffix, return /
185              
186             =back
187              
188             =cut
189              
190              
191             sub endpoint
192             {
193 62     62 1 3256 my ($self, $host) = @_;
194              
195             # reset error attribute
196 62         103 $self->{error} = undef;
197              
198 62         146 my ($endpoint, $templates, $params) = parse_endpoint($self->{endpoint});
199 62         137 foreach my $template (@$templates) {
200 41         94 my $pattern = '\{' . $template . '\}';
201 41 100       104 if (exists($self->{tpls}->{$template})) {
202 40         499 $endpoint =~ s#$pattern#$self->{tpls}->{$template}#g;
203             } else {
204 1         4 $self->{error} = "Missing template $template data to replace endpoint $self->{endpoint}";
205 1         7 return;
206             }
207             }
208              
209 61         113 my @fparams;
210 61         142 foreach my $param (sort @$params) {
211 63 100       145 if (exists($self->{params}->{$param})) {
212 26         72 push(@fparams, "$param=".$self->{params}->{$param});
213             }
214             }
215              
216 61 100       137 if (@fparams) {
217 22         97 $endpoint =~ s/\/+$//;
218 22         79 $endpoint .= '?'.join('&', @fparams);
219             }
220              
221 61 100       128 if ($host) {
222 54         77 my $url = $host;
223              
224 54         185 my $version_suffix = "v$self->{version}";
225 54         176 $version_suffix =~ s/^v+/v/;
226              
227 54 100       279 if ($host !~ m/^http/) {
    100          
228 1         4 $url = "https://$url/$version_suffix";
229             } elsif ($host !~ m#/v[\d.]+/?$#) {
230 1         3 $url .= "/$version_suffix";
231             }
232              
233 54         136 $url =~ s#/+$##;
234 54         109 $endpoint =~ s#^/+##;
235 54         125 $endpoint = "$url/$endpoint";
236             }
237              
238 61         218 return $endpoint;
239             }
240              
241             =item opts_data
242              
243             Generate hashref from options, to be used for JSON encoding.
244             If C attribute is defined, ignore all options and return it.
245              
246             Returns empty hasref, even if no options existed.
247              
248             =cut
249              
250             sub opts_data
251             {
252 29     29 1 442 my ($self) = @_;
253              
254 29         48 my $root;
255              
256 29 100       64 if ($self->{raw}) {
257             # ignore all options passed
258 11         20 $root = $self->{raw};
259             } else {
260 18         36 $root = {};
261 18         34 foreach my $key (sort keys %{$self->{opts}}) {
  18         72  
262 26         39 my @paths = @{$self->{paths}->{$key}};
  26         58  
263 26         40 my $lastpath = pop(@paths);
264 26         33 my $here = $root;
265 26         47 foreach my $path (@paths) {
266             # build tree
267 74 100       151 $here->{$path} = {} if !exists($here->{$path});
268 74         161 $here = $here->{$path};
269             }
270             # no intermediate variable with value
271 26         60 $here->{$lastpath} = $self->{opts}->{$key};
272             }
273             }
274              
275 29         68 return $root;
276             }
277              
278             =item headers
279              
280             Return headers for the request.
281              
282             Supported options:
283              
284             =over
285              
286             =item token: authentication token stored in X-Auth-Token
287              
288             =item headers: hashref with headers to add that take precedence over the defaults.
289             Headers with an undef value will be removed.
290              
291             =back
292              
293             =cut
294              
295             sub headers
296             {
297 56     56 1 124 my ($self, %opts) = @_;
298              
299 56         154 my $headers = {%DEFAULT_HEADERS};
300              
301 56 100       1494 while (my ($hdr, $value) = each %{$opts{headers} || {}}) {
  58         292  
302 2 100       6 if (defined($value)) {
303 1         3 $headers->{$hdr} = $value;
304             } else {
305 1         4 delete $headers->{$hdr};
306             }
307             }
308              
309 56 100       161 $headers->{$HDR_X_AUTH_TOKEN} = $opts{token} if defined $opts{token};
310              
311 56         136 return $headers;
312             }
313              
314              
315             =item is_error
316              
317             Test if this is an error or not (based on error attribute).
318              
319             =cut
320              
321             sub is_error
322             {
323 14     14 1 8897 my $self = shift;
324 14 100       64 return $self->{error} ? 1 : 0;
325             }
326              
327             # Overloaded boolean, inverse of is_error
328             sub _boolean
329             {
330 11     11   2411 my $self = shift;
331 11         25 return ! $self->is_error();
332             }
333              
334             =pod
335              
336             =back
337              
338             =cut
339              
340             1;