File Coverage

blib/lib/SRS/EPP/Response/Error.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             # vim: filetype=perl:noexpandtab:ts=3:sw=3
2             #
3             # Copyright (C) 2009 NZ Registry Services
4             #
5             # This program is free software: you can redistribute it and/or modify
6             # it under the terms of the Artistic License 2.0 or later. You should
7             # have received a copy of the Artistic License the file COPYING.txt.
8             # If not, see <http://www.perlfoundation.org/artistic_license_2_0>
9 2     2   4109 use strict;
  2         5  
  2         79  
10 2     2   10 use warnings;
  2         4  
  2         69  
11              
12 2     2   48 use 5.010;
  2         8  
  2         128  
13              
14             package SRS::EPP::Response::Error;
15             {
16             $SRS::EPP::Response::Error::VERSION = '0.22';
17             }
18 2     2   926 use Moose;
  2         510213  
  2         22  
19 2     2   16573 use MooseX::StrictConstructor;
  0            
  0            
20             use Data::Dumper;
21              
22             with 'MooseX::Log::Log4perl::Easy';
23              
24             use SRS::EPP::Response::Error::Map qw(map_srs_error map_srs_error_code);
25              
26             use XML::LibXML;
27             use PRANG::Graph::Context;
28             use XML::SRS::Error;
29              
30             extends 'SRS::EPP::Response';
31              
32             has 'exception' =>
33             is => 'ro',
34             ;
35              
36             has 'bad_node' =>
37             is => "rw",
38             isa => "XML::LibXML::Node",
39             ;
40              
41             has '+server_id' =>
42             required => 1,
43             ;
44              
45             has '+code' =>
46             lazy => 1,
47             default => \&derive_error_code,
48             ;
49              
50             sub derive_error_code {
51             my $self = shift;
52             my $exception = @_ ? shift : $self->exception;
53             if ( ref $exception and ref $exception eq "ARRAY" ) {
54             return $self->derive_error_code($exception->[0]);
55             }
56             given ($exception) {
57             when (!blessed $_) {
58             return 2400;
59             }
60             when ($_->isa("XML::LibXML::Error")) {
61             return 2001;
62             }
63             when ($_->isa("XML::SRS::Error")) {
64             return map_srs_error_code($exception);
65             }
66             when ($_->isa("PRANG::Graph::Context::Error")) {
67             return 2004;
68             }
69             }
70             }
71              
72             has '+extra' =>
73             lazy => 1,
74             default => \&derive_extra,
75             ;
76              
77             sub derive_extra {
78             my $self = shift;
79             my $exception = @_ ? shift : $self->exception;
80             given ($exception) {
81             when (! defined $_) {
82             return "";
83             }
84             when (!ref $_) {
85             return m{^(.+?)(?:at .+? line \d+)?\.?$} ? $1 : $_;
86             }
87             when (ref($_) eq "ARRAY") {
88             return join "; ", map {
89             $self->derive_extra($_);
90             } @$exception;
91             }
92             when (!blessed $_) {
93             return "";
94             }
95             when ($_->isa("XML::LibXML::Error")) {
96             return "Input XML not valid (or xmlns error)";
97             }
98             when ($_->isa("XML::SRS::Error")) {
99             return $exception->desc;
100             }
101             when ($_->isa("PRANG::Graph::Context::Error")) {
102             return "Input violates XML Schema";
103             }
104             default {
105             return "";
106             }
107             }
108             }
109              
110             has 'mapped_errors' =>
111             is => "ro",
112             isa => "ArrayRef[XML::EPP::Error]",
113             lazy => 1,
114             default => sub {
115             my $self = shift;
116             my $exceptions_a = $self->exception;
117             unless (
118             ref $exceptions_a
119             and
120             ref $exceptions_a eq "ARRAY"
121             )
122             {
123             $exceptions_a = [$exceptions_a];
124             }
125             [ map { map_exception($_) } @$exceptions_a ];
126             };
127              
128             sub map_exception {
129             my $except = shift;
130             given ($except) {
131             when (ref $_ eq 'ARRAY') {
132             return map { map_exception($_) } @$except;
133             }
134             when (!blessed($_)) {
135             my $errorstr = ref $_ ? Dumper $_ : $_;
136             my @lines = split /\n/, $errorstr;
137             my $reason = parse_moose_error($lines[0], 1);
138             return XML::EPP::Error->new(
139             value => 'Unknown',
140             reason => $reason||'(none)',
141             );
142             }
143             when ($_->isa("PRANG::Graph::Context::Error")) {
144             use YAML;
145             my $xpath = $except->xpath;
146              
147             my $message = $except->message;
148              
149             my @lines = split /\n/, $message;
150              
151             my $reason = "XML Schema validation error at $xpath";
152              
153             $reason .= '; ' . parse_moose_error($lines[0]);
154              
155             return XML::EPP::Error->new(
156             value => $except->node || '',
157             reason => $reason || '',
158             );
159             }
160             when ($_->isa("XML::LibXML::Error")) {
161             my @errors;
162             while ($except) {
163             my $error = XML::EPP::Error->new(
164             value => $except->context || "(n/a)",
165             reason => $except->message || '',
166             );
167             push @errors, $error;
168              
169             # though called '_prev', this function
170             # is documented.
171             $except = $except->_prev;
172             }
173             return @errors;
174             }
175             when ($_->isa("XML::EPP::Error")) {
176             return $except;
177             }
178             when ($_->isa("XML::SRS::Error")) {
179             return map_srs_error($except);
180             }
181             }
182             }
183              
184             around 'build_response' => sub {
185             my $orig = shift;
186             my $self = shift;
187              
188             my $message = $self->$orig(@_);
189             my $result = $message->message->result;
190              
191             my $bad_node = $self->bad_node;
192             my $errors_a = $self->exception
193             ? $self->mapped_errors
194             : [];
195              
196             $result->[0]->add_error($_) for grep {defined} @$errors_a;
197             return $message;
198             };
199              
200             # TODO: Moose supports structured errors, although might need an
201             # extension or a newer version
202             # This would be much easier if we used those
203             sub parse_moose_error {
204             my $string = shift;
205             my $dont_return_catchall = shift // 0;
206              
207             my $error = '';
208              
209             if (
210             $string =~ m{
211             Validation \s failed \s for \s
212             '.*::(\w+Type)'
213             \s (?:failed \s )?with \s value \s
214             (.*) \s at
215             }x
216             )
217             {
218             $error = "'$2' does not meet schema requirements for $1";
219             }
220             elsif (
221             $string =~ m{
222             Attribute \s \((.+?)\) \s does \s not \s
223             pass \s the \s type \s constraint \s
224             because: \s Validation \s failed \s for \s
225             '.+?' \s (?:failed \s )?
226             with \s value \s (.+?) \s at
227             }x
228             )
229             {
230             my ($label, $value) = ($1, $2);
231             unless ($value =~ m{^(?:ARRAY|HASH)}) {
232             $error = "Invalid value $value ($label)";
233             }
234             }
235             elsif ($string =~ m{Attribute \((.+?)\) is required}) {
236             $error = "Missing required value ($1)";
237             }
238             elsif (! $dont_return_catchall) {
239             # Catch-all
240             $string =~ m{^(.+?)(?:at .+? line \d+)?\.?$};
241             $error = $1;
242             }
243              
244             return $error;
245             }
246              
247             no Moose;
248             __PACKAGE__->meta->make_immutable;
249              
250             1;
251              
252             __END__
253              
254             =head1 NAME
255              
256             SRS::EPP::Response::Error - EPP exception/error response class
257              
258             =head1 SYNOPSIS
259              
260             #... in a SRS::EPP::Command subclass' ->process() handler...
261             return SRS::EPP::Response::Error->new
262             (
263             id => "XXXX",
264             extra => "...",
265             );
266              
267             =head1 DESCRIPTION
268              
269             This module handles generating errors; the information these can hold
270             is specified in RFC3730 / RFC4930.
271              
272             =head1 SEE ALSO
273              
274             L<SRS::EPP::Response>, L<SRS::EPP::Command>
275              
276             =cut
277              
278             # Local Variables:
279             # mode:cperl
280             # indent-tabs-mode: t
281             # cperl-continued-statement-offset: 8
282             # cperl-brace-offset: 0
283             # cperl-close-paren-offset: 0
284             # cperl-continued-brace-offset: 0
285             # cperl-continued-statement-offset: 8
286             # cperl-extra-newline-before-brace: nil
287             # cperl-indent-level: 8
288             # cperl-indent-parens-as-block: t
289             # cperl-indent-wrt-brace: nil
290             # cperl-label-offset: -8
291             # cperl-merge-trailing-else: t
292             # End: