File Coverage

blib/lib/Net/Amazon/S3/Response.pm
Criterion Covered Total %
statement 70 75 93.3
branch 22 30 73.3
condition 8 10 80.0
subroutine 26 29 89.6
pod 12 15 80.0
total 138 159 86.7


line stmt bran cond sub pod time code
1             package Net::Amazon::S3::Response;
2             # Abstract: Behaviour common to most S3 responses.
3             $Net::Amazon::S3::Response::VERSION = '0.98';
4 96     96   841 use Moose;
  96         260  
  96         914  
5              
6 96     96   674027 use Carp ();
  96         266  
  96         2704  
7 96     96   75950 use XML::LibXML;
  96         3202813  
  96         800  
8 96     96   17630 use XML::LibXML::XPathContext;
  96         258  
  96         2396  
9              
10 96     96   607 use Net::Amazon::S3::Constants;
  96         253  
  96         2208  
11              
12 96     96   583 use namespace::clean;
  96         255  
  96         1098  
13              
14             has http_response => (
15             is => 'ro',
16             required => 1,
17             handles => [
18             qw[ code ],
19             qw[ message ],
20             qw[ is_success ],
21             qw[ is_redirect ],
22             qw[ status_line ],
23             qw[ content ],
24             qw[ decoded_content ],
25             qw[ header ],
26             qw[ headers ],
27             qw[ header_field_names ],
28             ],
29             );
30              
31             has xml_document => (
32             is => 'ro',
33             init_arg => undef,
34             lazy => 1,
35             builder => '_build_xml_document',
36             );
37              
38             has xpath_context => (
39             is => 'ro',
40             init_arg => undef,
41             lazy => 1,
42             builder => '_build_xpath_context',
43             );
44              
45             has error_code => (
46             is => 'ro',
47             init_arg => undef,
48             lazy => 1,
49             builder => '_build_error_code',
50             );
51              
52             has error_message => (
53             is => 'ro',
54             init_arg => undef,
55             lazy => 1,
56             builder => '_build_error_message',
57             );
58              
59             has error_resource => (
60             is => 'ro',
61             init_arg => undef,
62             lazy => 1,
63             builder => '_build_error_resource',
64             );
65              
66             has error_request_id => (
67             is => 'ro',
68             init_arg => undef,
69             lazy => 1,
70             builder => '_build_error_request_id',
71             );
72              
73             has _data => (
74             is => 'ro',
75             init_arg => undef,
76             lazy => 1,
77             builder => '_build_data',
78             );
79              
80             sub _parse_data;
81              
82             sub connection {
83 0     0 1 0 return $_[0]->header ('Connection');
84             }
85              
86             sub content_length {
87 2   50 2 1 64 return $_[0]->http_response->content_length || 0;
88             }
89              
90             sub content_type {
91 201     201 1 5958 return $_[0]->http_response->content_type;
92             }
93              
94             sub date {
95 0     0 0 0 return $_[0]->header ('Date');
96             }
97              
98             sub etag {
99 4     4 1 10137 return $_[0]->_decode_etag ($_[0]->header ('ETag'));
100             }
101              
102             sub server {
103 1     1 1 181 return $_[0]->header ('Server');
104             }
105              
106             sub delete_marker {
107 1     1 1 230 return $_[0]->header (Net::Amazon::S3::Constants::HEADER_DELETE_MARKER);
108             }
109              
110             sub id_2 {
111 1     1 1 227 return $_[0]->header (Net::Amazon::S3::Constants::HEADER_ID_2);
112             }
113              
114             sub request_id {
115 2     2 1 229 return $_[0]->header (Net::Amazon::S3::Constants::HEADER_REQUEST_ID);
116             }
117              
118             sub version_id {
119 1     1 1 225 return $_[0]->header (Net::Amazon::S3::Constants::HEADER_VERSION_ID);
120             }
121              
122             sub is_xml_content {
123 199     199 1 18009 my ($self) = @_;
124              
125 199   100     1120 return $self->content_type =~ m:[/+]xml\b: && $self->decoded_content;
126             }
127              
128             sub is_error {
129 419     419 1 12054 my ($self) = @_;
130              
131 419 100       12793 return 1 if $self->http_response->is_error;
132 88 50       1323 return 1 if $self->findvalue ('/Error');
133 88         5217 return;
134             }
135              
136             sub is_internal_response {
137 3     3 1 2209 my ($self) = @_;
138              
139 3         14 my $header = $self->header ('Client-Warning');
140 3   66     302 return !! ($header && $header eq 'Internal response');
141             }
142              
143             sub findvalue {
144 230     230 0 741 my ($self, @path) = @_;
145              
146 230 100       6916 return '' unless $self->xpath_context;
147 181         5138 $self->xpath_context->findvalue (@path);
148             }
149              
150             sub findnodes {
151 0     0 0 0 my ($self, @path) = @_;
152              
153 0 0       0 return unless $self->xpath_context;
154 0         0 $self->xpath_context->findnodes (@path);
155             }
156              
157             sub _build_data {
158 14     14   37 my ($self) = @_;
159              
160 14 50       59 return $self->is_success
161             ? $self->_parse_data
162             : undef
163             ;
164             }
165             sub _build_error_code {
166 107     107   278 my ($self) = @_;
167              
168             return
169 107 50       318 unless $self->is_error;
170              
171 107 100       3922 return $self->http_response->code
172             unless $self->xpath_context;
173              
174 74         354 return $self->findvalue ('/Error/Code');
175             }
176              
177             sub _build_error_message {
178 97     97   298 my ($self) = @_;
179              
180             return
181 97 50       372 unless $self->is_error;
182              
183 97 100       3806 return $self->http_response->message
184             unless $self->xpath_context;
185              
186 66         225 return $self->findvalue ('/Error/Message');
187             }
188              
189             sub _build_error_resource {
190 2     2   6 my ($self) = @_;
191              
192             return
193 2 50       6 unless $self->is_error;
194              
195 2 100       77 return "${\ $self->http_response->request->uri }"
  1         30  
196             unless $self->xpath_context;
197              
198 1         4 return $self->findvalue ('/Error/Resource');
199             }
200              
201             sub _build_error_request_id {
202 2     2   7 my ($self) = @_;
203              
204             return
205 2 50       5 unless $self->is_error;
206              
207 2 100       74 return $self->request_id
208             unless $self->xpath_context;
209              
210 1         5 return $self->findvalue ('/Error/RequestId');
211             }
212              
213             sub _build_xml_document {
214 194     194   500 my ($self) = @_;
215              
216 194 100       812 return unless $self->is_xml_content;
217              
218             # TODO: A 200 OK response can contain valid or invalid XML
219 112         114247 return XML::LibXML->new->parse_string ($self->http_response->decoded_content);
220             }
221              
222             sub _build_xpath_context {
223 194     194   527 my ($self) = @_;
224              
225 194         6241 my $doc = $self->xml_document;
226 194 100       4969 return unless $doc;
227              
228 112         5020 my $xpc = XML::LibXML::XPathContext->new ($doc);
229              
230 112   100     2318 my $s3_ns = $doc->documentElement->lookupNamespaceURI
231             || 'http://s3.amazonaws.com/doc/2006-03-01/';
232 112         669 $xpc->registerNs (s3 => $s3_ns);
233              
234 112         8077 return $xpc;
235             }
236              
237             sub _decode_etag {
238 4     4   280 my ($self, $etag) = @_;
239              
240 4         24 $etag =~ s/ (?:^") | (?:"$) //gx;
241 4         18 return $etag;
242             }
243              
244             1;
245              
246             __END__
247              
248             =pod
249              
250             =encoding utf-8
251              
252             =head1 NAME
253              
254             Net::Amazon::S3::Response
255              
256             =head1 VERSION
257              
258             version 0.98
259              
260             =head1 SYNOPSIS
261              
262             package Command::Response;
263             extends 'Net::Amazon::S3::Response';
264              
265             ...
266             my $response = Command::Response->new (
267             http_response => $http_response,
268             );
269              
270             =head1 DESCRIPTION
271              
272             Response handler base class providing functionality common to most S3 responses.
273              
274             =head1 EXTENDING
275              
276             L<Net::Amazon::S3::Response> provides methods to cache response data.
277              
278             =over
279              
280             =item _data
281              
282             Read-only accessor initialized by C<_build_data>
283              
284             =item _build_data
285              
286             Data builder, by default calls C<_parse_data> if response is success and provides
287             valid XML document.
288              
289             =item _parse_data
290              
291             Abstract (undefined in parent) method to be implemented by children.
292              
293             =back
294              
295             =head1 METHODS
296              
297             =head2 Constructor
298              
299             Constructor accepts only one (required) parameter - C<http_response>.
300             It should act like L<HTTP::Response>.
301              
302             =head2 Response classification methods
303              
304             =over
305              
306             =item is_success
307              
308             True if response is a success response, false otherwise.
309              
310             Successful response may contain invalid XML.
311              
312             =item is_redirect
313              
314             True if response is a redirect.
315              
316             =item is_error
317              
318             True if response is an error response, false otherwise.
319              
320             Response is considered to be an error either when response code is an HTTP
321             error (4xx or 5xx) or response content is an error XML document.
322              
323             See also L<"S3 Error Response"|https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html>
324             for more details.
325              
326             =item is_internal_response
327              
328             True if response is generated by user agent itself (eg: Cannot connect)
329              
330             =item is_xml_content
331              
332             True if response data is a valid XML document
333              
334             =back
335              
336             =head2 Error handling
337              
338             Apart error classifition L<Net::Amazon::S3::Response> provides also common
339             error data accessors.
340              
341             Error data are available only in case of error response.
342              
343             =over
344              
345             =item error_code
346              
347             Either content of C<Error/Code> XML element or HTTP response code.
348              
349             =item error_message
350              
351             Either content of C<Error/Message> XML element or HTTP response message.
352              
353             =item error_request_id
354              
355             Content of C<Error/RequestId> XML element if available, C<x-amz-request-id> header
356             if available, empty list otherwise.
357              
358             =item error_resource
359              
360             Content of c<Error/Resource> if available, request uri otherwise.
361              
362             =back
363              
364             =head2 Common Response Headers
365              
366             See L<"S3 Common Response Headers"|https://docs.aws.amazon.com/AmazonS3/latest/API/RESTCommonResponseHeaders.html>
367             for more details.
368              
369             =over
370              
371             =item content_length
372              
373             =item content_type
374              
375             =item connection
376              
377             =item etag
378              
379             ETag with trimmed leading/trailing quotes.
380              
381             =item server
382              
383             =item delete_marker
384              
385             =item request_id
386              
387             =item id_2
388              
389             =item version_id
390              
391             =back
392              
393             =head2 XML Document parsing
394              
395             =over
396              
397             =item xml_document
398              
399             Lazy built instance of L<XML::LibXML>.
400              
401             Available only if response is XML response and contains valid XML document.
402              
403             =item xpath_context
404              
405             Lazy built instance of L<XML::LibXML::XPathContext>.
406              
407             Available only if response is XML response and contains valid XML document
408              
409             =back
410              
411             =head2 HTTP Response methods
412              
413             Further methods delegated to C<http_response>.
414             Refer L<HTTP::Response> for description.
415              
416             =over
417              
418             =item code
419              
420             =item message
421              
422             =item status_line
423              
424             =item content
425              
426             =item decoded_content
427              
428             =item header
429              
430             =item headers
431              
432             =item header_field_names
433              
434             =back
435              
436             =head1 AUTHOR
437              
438             Branislav Zahradník <barney@cpan.org>
439              
440             =head1 COPYRIGHT AND LICENSE
441              
442             This module is part of L<Net::Amazon::S3>.
443              
444             =head1 AUTHOR
445              
446             Branislav Zahradník <barney@cpan.org>
447              
448             =head1 COPYRIGHT AND LICENSE
449              
450             This software is copyright (c) 2021 by Amazon Digital Services, Leon Brocard, Brad Fitzpatrick, Pedro Figueiredo, Rusty Conover, Branislav Zahradník.
451              
452             This is free software; you can redistribute it and/or modify it under
453             the same terms as the Perl 5 programming language system itself.
454              
455             =cut