File Coverage

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


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.99';
4 99     99   798 use Moose;
  99         256  
  99         864  
5              
6 99     99   679159 use Carp ();
  99         308  
  99         2425  
7 99     99   74252 use XML::LibXML;
  99         3223368  
  99         781  
8 99     99   17136 use XML::LibXML::XPathContext;
  99         269  
  99         2436  
9              
10 99     99   620 use Net::Amazon::S3::Constants;
  99         258  
  99         2226  
11              
12 99     99   630 use namespace::clean;
  99         241  
  99         1055  
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 3   100 3 1 121 return $_[0]->http_response->content_length || 0;
88             }
89              
90             sub content_type {
91 209     209 1 6191 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 9302 return $_[0]->_decode_etag ($_[0]->header ('ETag'));
100             }
101              
102             sub server {
103 1     1 1 178 return $_[0]->header ('Server');
104             }
105              
106             sub delete_marker {
107 1     1 1 227 return $_[0]->header (Net::Amazon::S3::Constants::HEADER_DELETE_MARKER);
108             }
109              
110             sub id_2 {
111 1     1 1 241 return $_[0]->header (Net::Amazon::S3::Constants::HEADER_ID_2);
112             }
113              
114             sub request_id {
115 2     2 1 227 return $_[0]->header (Net::Amazon::S3::Constants::HEADER_REQUEST_ID);
116             }
117              
118             sub version_id {
119 1     1 1 222 return $_[0]->header (Net::Amazon::S3::Constants::HEADER_VERSION_ID);
120             }
121              
122             sub is_xml_content {
123 206     206 1 16560 my ($self) = @_;
124              
125 206   100     970 return $self->content_type =~ m:[/+]xml\b: && $self->decoded_content;
126             }
127              
128             sub is_error {
129 434     434 1 10400 my ($self) = @_;
130              
131 434 100       13190 return 1 if $self->http_response->is_error;
132 91 50       1395 return 1 if $self->findvalue ('/Error');
133 91         5383 return;
134             }
135              
136             sub is_internal_response {
137 3     3 1 1794 my ($self) = @_;
138              
139 3         16 my $header = $self->header ('Client-Warning');
140 3   66     312 return !! ($header && $header eq 'Internal response');
141             }
142              
143             sub findvalue {
144 239     239 0 757 my ($self, @path) = @_;
145              
146 239 100       7124 return '' unless $self->xpath_context;
147 187         5300 $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   38 my ($self) = @_;
159              
160 14 50       62 return $self->is_success
161             ? $self->_parse_data
162             : undef
163             ;
164             }
165             sub _build_error_code {
166 111     111   325 my ($self) = @_;
167              
168             return
169 111 50       360 unless $self->is_error;
170              
171 111 100       4121 return $self->http_response->code
172             unless $self->xpath_context;
173              
174 77         377 return $self->findvalue ('/Error/Code');
175             }
176              
177             sub _build_error_message {
178 101     101   281 my ($self) = @_;
179              
180             return
181 101 50       442 unless $self->is_error;
182              
183 101 100       3790 return $self->http_response->message
184             unless $self->xpath_context;
185              
186 69         340 return $self->findvalue ('/Error/Message');
187             }
188              
189             sub _build_error_resource {
190 2     2   6 my ($self) = @_;
191              
192             return
193 2 50       5 unless $self->is_error;
194              
195 2 100       108 return "${\ $self->http_response->request->uri }"
  1         31  
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   5 my ($self) = @_;
203              
204             return
205 2 50       6 unless $self->is_error;
206              
207 2 100       73 return $self->request_id
208             unless $self->xpath_context;
209              
210 1         3 return $self->findvalue ('/Error/RequestId');
211             }
212              
213             sub _build_xml_document {
214 201     201   536 my ($self) = @_;
215              
216 201 100       864 return unless $self->is_xml_content;
217              
218             # TODO: A 200 OK response can contain valid or invalid XML
219 115         113050 return XML::LibXML->new->parse_string ($self->http_response->decoded_content);
220             }
221              
222             sub _build_xpath_context {
223 201     201   542 my ($self) = @_;
224              
225 201         6239 my $doc = $self->xml_document;
226 201 100       5055 return unless $doc;
227              
228 115         4816 my $xpc = XML::LibXML::XPathContext->new ($doc);
229              
230 115   100     2217 my $s3_ns = $doc->documentElement->lookupNamespaceURI
231             || 'http://s3.amazonaws.com/doc/2006-03-01/';
232 115         647 $xpc->registerNs (s3 => $s3_ns);
233              
234 115         8085 return $xpc;
235             }
236              
237             sub _decode_etag {
238 4     4   291 my ($self, $etag) = @_;
239              
240 4         25 $etag =~ s/ (?:^") | (?:"$) //gx;
241 4         28 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 - Behaviour common to most S3 responses.
255              
256             =head1 VERSION
257              
258             version 0.99
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