File Coverage

blib/lib/App/Framework/Base/Object/ErrorHandle.pm
Criterion Covered Total %
statement 57 149 38.2
branch 5 34 14.7
condition 1 9 11.1
subroutine 13 29 44.8
pod 21 21 100.0
total 97 242 40.0


line stmt bran cond sub pod time code
1             package App::Framework::Base::Object::ErrorHandle ;
2              
3             =head1 NAME
4              
5             App::Framework::Base::Object::ErrorHandle - Adds error handling to basic object
6              
7             =head1 SYNOPSIS
8              
9             use App::Framework::Base::Object::ErrorHandle ;
10              
11              
12             =head1 DESCRIPTION
13              
14             Any object derived from this class can throw an error and some registered error handler will catch (and handle) that error.
15              
16             Hierarchy of catch handlers is:
17              
18             catch_fn set for this object instance
19             any registered global catch function (last registered first)
20             default handler
21            
22             Global catch functions, when registered, are added to a stack so that the last one registered is called first.
23              
24             Each handler must return either 1=handled, or 0=not handled to tell this object whether to move on to the next handler.
25              
26             NOTE: The default handler may be over-ridden by any derived object.
27              
28             This object is set up such that when used as stand-alone objects (i.e. outside of an application framework), then errors are handled
29             with die(), warn() etc.
30              
31              
32             =head1 DIAGNOSTICS
33              
34             Setting the debug flag to level 1 prints out (to STDOUT) some debug messages, setting it to level 2 prints out more verbose messages.
35              
36             =head1 AUTHOR
37              
38             Steve Price C<< >>
39              
40             =head1 BUGS
41              
42             None that I know of!
43              
44             =head1 INTERFACE
45              
46             =over 4
47              
48             =cut
49              
50 29     29   157 use strict ;
  29         54  
  29         1198  
51 29     29   362 use Carp ;
  29         51  
  29         2035  
52              
53             our $VERSION = "1.004" ;
54              
55             #============================================================================================
56             # USES
57             #============================================================================================
58 29     29   19544 use App::Framework::Base::Object ;
  29         85  
  29         56187  
59              
60             #============================================================================================
61             # OBJECT HIERARCHY
62             #============================================================================================
63             our @ISA = qw(App::Framework::Base::Object) ;
64              
65             #============================================================================================
66             # GLOBALS
67             #============================================================================================
68              
69             my %FIELDS = (
70             'errors' => [], # List of errors for this object
71             'catch_fn' => undef, # Function called if error is thrown
72             ) ;
73              
74             # Keep track of all errors
75             my @all_errors = () ;
76              
77             # Error type priority
78             my %ERR_TYPES = (
79             'fatal' => 0x80,
80             'nonfatal' => 0x40,
81             'warning' => 0x08,
82             'note' => 0x04,
83             'none' => 0x00,
84            
85             ) ;
86              
87             # Error handler stack
88             my @GLOBAL_ERROR_HANDLERS = () ;
89              
90             # Some useful masks
91             my $ERR_TYPE_MASK = 0xF0 ;
92             my $ERR_TYPE_WARN = 0x08 ;
93             my $ERR_TYPE_NOTE = 0x04 ;
94              
95              
96             #============================================================================================
97             # CONSTRUCTOR
98             #============================================================================================
99              
100             =item B
101              
102             Create a new App::Framework::Base::Object::ErrorHandle.
103              
104             The %args are specified as they would be in the B method, for example:
105              
106             'mmap_handler' => $mmap_handler
107              
108             The full list of possible arguments are :
109              
110             'fields' => Either ARRAY list of valid field names, or HASH of field names with default values
111              
112             =cut
113              
114             sub new
115             {
116 160     160 1 1288 my ($obj, %args) = @_ ;
117              
118 160   33     1817 my $class = ref($obj) || $obj ;
119              
120             # Create object
121 160         3344 my $this = $class->SUPER::new(%args) ;
122            
123            
124 160         1031 return($this) ;
125             }
126              
127              
128              
129             #============================================================================================
130             # CLASS METHODS
131             #============================================================================================
132              
133             #-----------------------------------------------------------------------------
134              
135             =item B
136              
137             Initialises the App::Framework::Base::Object::ErrorHandle object class variables. Creates a class instance so that these
138             methods can also be called via the class (don't need a specific instance)
139              
140             =cut
141              
142             sub init_class
143             {
144 160     160 1 388 my $class = shift ;
145 160         880 my (%args) = @_ ;
146              
147 160 50       800 if (! keys %args)
148             {
149 0         0 %args = () ;
150             }
151            
152             # Add extra fields
153 160         963 foreach (keys %FIELDS)
154             {
155 320         1266 $args{'fields'}{$_} = $FIELDS{$_} ;
156             }
157 160         1629 $class->SUPER::init_class(%args) ;
158              
159             # Create a class instance object - allows these methods to be called via class
160 160         4233 $class->class_instance(%args) ;
161              
162             }
163              
164              
165             #-----------------------------------------------------------------------------
166              
167             =item B
168              
169             Add a new error to the Class list keeping track of all runtime errors
170              
171             =cut
172              
173             sub _global_error
174             {
175 1     1   2 my $class = shift ;
176 1         2 my ($error) = @_ ;
177            
178 1         3 push @all_errors, $error ;
179             }
180              
181             #-----------------------------------------------------------------------------
182              
183             =item B
184              
185             Add a new error to the Class list keeping track of all runtime errors
186              
187             %args hash contains:
188              
189             * type = fatal, nonfatal, warning, note
190             * message = text message
191             * errorcode = integer error code value
192              
193             =cut
194              
195             sub global_error
196             {
197 0     0 1 0 my $class = shift ;
198 0         0 my (%args) = @_ ;
199            
200             # Convert args into an error
201 0         0 my $error = _create_error('parent'=>$class, %args) ;
202              
203 0         0 $class->_global_error($error) ;
204             }
205              
206              
207             #-----------------------------------------------------------------------------
208              
209             =item B
210              
211             Returns a hash containing the information from the last error stored in the global list
212              
213             Hash contains:
214              
215             * type = fatal, nonfatal, warning, note
216             * message = text message
217             * errorcode = integer error code value
218              
219             If there are no errors, returns undef
220              
221             =cut
222              
223             sub global_last_error
224             {
225 0     0 1 0 my $class = shift ;
226 0         0 my (%args) = @_ ;
227              
228 0         0 my $error = _latest_worst_error(\@all_errors) ;
229            
230 0         0 return $error ;
231             }
232              
233             #-----------------------------------------------------------------------------
234              
235             =item B
236              
237             Returns the list of all errors
238              
239             =cut
240              
241             sub global_errors
242             {
243 0     0 1 0 my $class = shift ;
244            
245 0         0 return @all_errors ;
246             }
247              
248             #-----------------------------------------------------------------------------
249              
250             =item B
251              
252             Returns a hash containing the information from the last actual error (i.e. only 'fatal' or 'nonfatal' types) stored
253             in the global list
254              
255             Hash contains:
256              
257             * type = fatal, error, warning, note
258             * message = text message
259             * errorcode = integer error code value
260              
261             If there are no errors, returns undef
262              
263             =cut
264              
265             sub any_error
266             {
267 0     0 1 0 my $class = shift ;
268              
269 0         0 my $error = $class->global_last_error() ;
270            
271             # Ensure this is something worth reporting
272 0         0 return $class->is_error($error) ;
273             }
274              
275             #-----------------------------------------------------------------------------
276              
277             =item B
278              
279             Returns TRUE if the $error object type matches the mask
280              
281             =cut
282              
283             sub error_check
284             {
285 1     1 1 2 my $class = shift ;
286              
287 1         2 my ($error, $mask) = @_ ;
288            
289             # Ensure this is something worth reporting
290 1 50       4 if ($error)
291             {
292 1         4 my $type = $ERR_TYPES{$error->{'type'}} ;
293 1 50       5 unless ($type & $mask)
294             {
295 0         0 $error = undef ;
296             }
297             }
298            
299 1         5 return $error ;
300             }
301              
302              
303              
304             #-----------------------------------------------------------------------------
305              
306             =item B
307              
308             Returns TRUE if the $error object is either 'fatal' or 'nonfatal'
309              
310             =cut
311              
312             sub is_error
313             {
314 1     1 1 2 my $class = shift ;
315              
316 1         3 my ($error) = @_ ;
317 1         10 return $class->error_check($error, $ERR_TYPE_MASK) ;
318             }
319              
320             #-----------------------------------------------------------------------------
321              
322             =item B
323              
324             Returns TRUE if the $error object is 'warning'
325              
326             =cut
327              
328             sub is_warning
329             {
330 0     0 1 0 my $class = shift ;
331              
332 0         0 my ($error) = @_ ;
333 0         0 return $class->error_check($error, $ERR_TYPE_WARN) ;
334             }
335              
336             #-----------------------------------------------------------------------------
337              
338             =item B
339              
340             Returns TRUE if the $error object is 'note'
341              
342             =cut
343              
344             sub is_note
345             {
346 0     0 1 0 my $class = shift ;
347              
348 0         0 my ($error) = @_ ;
349 0         0 return $class->error_check($error, $ERR_TYPE_NOTE) ;
350             }
351              
352              
353             #-----------------------------------------------------------------------------
354              
355             =item B
356              
357             Split the error object into component parts and return them in an ARRAY:
358              
359             [0] = Message
360             [1] = Error code
361             [2] = Type
362             [3] = Parent
363              
364             =cut
365              
366             sub error_split
367             {
368 1     1 1 2 my $class = shift ;
369              
370 1         3 my ($error) = @_ ;
371 1         2 my @parts ;
372            
373 1 50       4 if ($error)
374             {
375 1         5 @parts = @$error{qw/message errorcode type parent/} ;
376             }
377            
378 1         6 return @parts ;
379             }
380              
381             #-----------------------------------------------------------------------------
382              
383             =item B
384              
385             Add a new global error handler on to the stack
386              
387             =cut
388              
389             sub register_global_handler
390             {
391 0     0 1 0 my $class = shift ;
392 0         0 my ($code_ref) = @_ ;
393            
394 0         0 push @GLOBAL_ERROR_HANDLERS, $code_ref ;
395             }
396              
397             #-----------------------------------------------------------------------------
398              
399             =item B
400              
401             Last ditch attempt to handle errors. Uses die(), warn() etc as appropriate.
402              
403             =cut
404              
405             sub default_error_handler
406             {
407 0     0 1 0 my $this = shift ;
408 0         0 my ($error) = @_ ;
409              
410 0         0 my $handled = 0 ;
411              
412             # If it's an error, stop
413 0 0       0 if ($this->is_error($error))
414             {
415 0         0 my ($msg, $exitcode) = $this->error_split($error) ;
416 0         0 die "Error: $msg\n" ;
417 0         0 $handled = 1 ;
418             }
419 0 0       0 if ($this->is_warning($error))
420             {
421 0         0 my ($msg, $exitcode) = $this->error_split($error) ;
422 0         0 warn "Warning: $msg\n" ;
423 0         0 $handled = 1 ;
424             }
425 0 0       0 if ($this->is_note($error))
426             {
427 0         0 my ($msg, $exitcode) = $this->error_split($error) ;
428 0         0 print "Note: $msg\n" ;
429 0         0 $handled = 1 ;
430             }
431              
432 0         0 return $handled ;
433             }
434              
435              
436             #============================================================================================
437             # OBJECT METHODS
438             #============================================================================================
439              
440              
441             #-----------------------------------------------------------------------------
442              
443             =item B<_throw_error($error)>
444              
445             Add a new error to this object instance, also adds the error to this Class list
446             keeping track of all runtime errors
447              
448             =cut
449              
450             sub _throw_error
451             {
452 1     1   2 my $this = shift ;
453 1         1 my ($error) = @_ ;
454            
455             # Add to this object's list
456 1         3 push @{$this->errors()}, $error ;
  1         32  
457              
458             # Add to global list
459 1         13 $this->_global_error($error) ;
460            
461             ## Handle the error
462 1         2 my $handled = 0 ;
463              
464             # See if we have a registered catch function
465 1         27 my $catch_fn = $this->catch_fn() ;
466 1 50       3 if ($catch_fn)
467             {
468 1         4 $handled = &$catch_fn($error) ;
469             }
470            
471             # if not handled, try global
472 0 0       0 if (!$handled)
473             {
474 0   0     0 for (my $i = scalar(@GLOBAL_ERROR_HANDLERS)-1; ($i>=0) && !$handled; --$i)
475             {
476 0         0 $catch_fn = $GLOBAL_ERROR_HANDLERS[$i] ;
477 0         0 $handled = &$catch_fn($error) ;
478             }
479             }
480              
481             # when all else fails, do it yourself
482 0 0       0 if (!$handled)
483             {
484 0         0 $handled = $this->default_error_handler($error) ;
485             }
486            
487             # If all REALLY fails, die!
488 0 0       0 if (!$handled)
489             {
490 0         0 my ($msg, $exitcode) = $this->error_split($error) ;
491 0         0 die "Unhandled Error: $msg ($exitcode)\n" ;
492             }
493              
494             }
495              
496             #-----------------------------------------------------------------------------
497              
498             =item B
499              
500             Throws an error for this object based on an error object associated with a different object
501            
502             =cut
503              
504             sub rethrow_error
505             {
506 0     0 1 0 my $this = shift ;
507 0         0 my ($error) = @_ ;
508            
509             # Create copy of error
510 0         0 my %err_copy = () ;
511 0         0 foreach (keys %$error)
512             {
513 0         0 $err_copy{$_} = $error->{$_} ;
514             }
515 0         0 $err_copy{'parent'} = $this ;
516            
517 0         0 $this->_throw_error(\%err_copy) ;
518            
519             }
520              
521              
522             #-----------------------------------------------------------------------------
523              
524             =item B
525              
526             Add a new error to this object instance, also adds the error to this Class list
527             keeping track of all runtime errors
528              
529             %args hash contains:
530              
531             * type = fatal, nonfatal, warning, note
532             * message = text message
533             * errorcode = integer error code value
534              
535             =cut
536              
537             sub throw_error
538             {
539 1     1 1 3 my $this = shift ;
540 1         10 my (%args) = @_ ;
541            
542             # Convert args into an error
543 1         8 my $error = _create_error('parent'=>$this, %args) ;
544              
545 1         16 $this->_throw_error($error) ;
546            
547             }
548              
549             #-----------------------------------------------------------------------------
550              
551             =item B
552              
553             Add a new error (type=fatal) to this object instance, also adds the error to this Class list
554             keeping track of all runtime errors
555              
556             =cut
557              
558             sub throw_fatal
559             {
560 1     1 1 3 my $this = shift ;
561 1         2 my ($message, $errorcode) = @_ ;
562            
563             # Convert args into an error
564 1         17 $this->throw_error('type'=>'fatal', 'message'=>$message, 'errorcode'=>$errorcode) ;
565            
566             }
567              
568              
569             #-----------------------------------------------------------------------------
570              
571             =item B
572              
573             Add a new error (type=nonfatal) to this object instance, also adds the error to this Class list
574             keeping track of all runtime errors
575              
576             =cut
577              
578             sub throw_nonfatal
579             {
580 0     0 1 0 my $this = shift ;
581 0         0 my ($message, $errorcode) = @_ ;
582            
583             # Convert args into an error
584 0         0 $this->throw_error('type'=>'nonfatal', 'message'=>$message, 'errorcode'=>$errorcode) ;
585            
586             }
587              
588             #-----------------------------------------------------------------------------
589              
590             =item B
591              
592             Add a new error (type=warning) to this object instance, also adds the error to this Class list
593             keeping track of all runtime errors
594              
595             =cut
596              
597             sub throw_warning
598             {
599 0     0 1 0 my $this = shift ;
600 0         0 my ($message, $errorcode) = @_ ;
601            
602             # Convert args into an error
603 0         0 $this->throw_error('type'=>'warning', 'message'=>$message, 'errorcode'=>$errorcode) ;
604            
605             }
606              
607             #-----------------------------------------------------------------------------
608              
609             =item B
610              
611             Add a new error (type=note) to this object instance, also adds the error to this Class list
612             keeping track of all runtime errors
613              
614             =cut
615              
616             sub throw_note
617             {
618 0     0 1 0 my $this = shift ;
619 0         0 my ($message, $errorcode) = @_ ;
620            
621             # Convert args into an error
622 0         0 $this->throw_error('type'=>'note', 'message'=>$message, 'errorcode'=>$errorcode) ;
623            
624             }
625              
626              
627              
628             #-----------------------------------------------------------------------------
629              
630             =item B
631              
632             Returns a hash containing the information from the last (worst case) error stored for this object
633             i.e. if a 'fatal' error is followed by some 'note's then the 'fatal' error is returned
634              
635             Hash contains:
636              
637             * type = fatal, error, warning, note
638             * message = text message
639             * errorcode = integer error code value
640              
641             If there are no errors, returns undef
642              
643             =cut
644              
645             sub last_error
646             {
647 0     0 1 0 my $this = shift ;
648 0         0 my (%args) = @_ ;
649              
650 0         0 my $errors_aref = $this->errors() ;
651              
652 0         0 my $error = _latest_worst_error($errors_aref) ;
653            
654 0         0 return $error ;
655             }
656              
657              
658             #-----------------------------------------------------------------------------
659              
660             =item B
661              
662             Returns a hash containing the information from the last actual error (i.e. only 'fatal' or 'nonfatal' types) stored for this object
663              
664             Hash contains:
665              
666             * type = fatal, error, warning, note
667             * message = text message
668             * errorcode = integer error code value
669              
670             If there are no errors, returns undef
671              
672             =cut
673              
674             sub error
675             {
676 0     0 1 0 my $this = shift ;
677 0         0 my (%args) = @_ ;
678              
679 0         0 my $error = $this->last_error() ;
680            
681             # Ensure this is something worth reporting
682 0 0       0 if ($error)
683             {
684 0         0 my $type = $ERR_TYPES{$error->{'type'}} ;
685 0 0       0 unless ($type & $ERR_TYPE_MASK)
686             {
687 0         0 $error = undef ;
688             }
689             }
690            
691 0         0 return $error ;
692             }
693              
694              
695              
696             # ============================================================================================
697             # PRIVATE FUNCTIONS
698             # ============================================================================================
699              
700             #-----------------------------------------------------------------------------
701              
702             =item B<_create_error()>
703              
704             Returns a hash containing the information from the last error stored for this object
705              
706             Hash contains:
707              
708             * type = fatal, error, warning, note
709             * message = text message
710             * errorcode = integer error code value
711              
712             If there are no errors, returns undef
713              
714             =cut
715              
716             sub _create_error
717             {
718 1     1   5 my (%args) = @_ ;
719              
720             # TODO: Convert errors into error objects then add to the list
721 1         6 my $error = {
722             'type' => $args{'type'},
723             'message' => $args{'message'},
724             'errorcode' => $args{'errorcode'},
725             'parent' => $args{'parent'},
726             } ;
727            
728 1         3 return $error ;
729             }
730              
731             #-----------------------------------------------------------------------------
732              
733             =item B<_cmp_error($err1, $err2)>
734              
735             Compares error types. If the type of $err1 is more srious than $err2 then returns positive;
736             if type $err1 is less serious than $err2 then returns negative; otherwise returns 0
737              
738             Order of seriousness:
739              
740             * fatal
741             * error
742             * warning
743             * note
744              
745             =cut
746              
747             sub _cmp_error
748             {
749 0     0     my ($err1, $err2) = @_ ;
750              
751             # TODO: Add checks for valid error object & type get
752              
753 0           my ($err1_pri, $err2_pri) = (0, 0) ;
754 0 0         $err1_pri = $ERR_TYPES{$err1->{'type'}} if exists($ERR_TYPES{$err1->{'type'}}) ;
755 0 0         $err2_pri = $ERR_TYPES{$err2->{'type'}} if exists($ERR_TYPES{$err2->{'type'}}) ;
756              
757 0           return $err1_pri <=> $err2_pri ;
758             }
759              
760             #-----------------------------------------------------------------------------
761              
762             =item B<_latest_worst_error($errors_aref)>
763              
764             Works through the specified errors list and returns the latest, worst error
765              
766             =cut
767              
768             sub _latest_worst_error
769             {
770 0     0     my ($errors_aref) = @_ ;
771              
772 0           my $error = undef ;
773 0           my $num_errors = scalar(@$errors_aref) ;
774 0 0         if ($num_errors)
775             {
776             # Run backwards looking for worst error
777 0           foreach my $ix (0..$num_errors-1)
778             {
779 0           my $error_num = $num_errors-1-$ix ;
780 0 0 0       if (!$error || _cmp_error($errors_aref->[$error_num], $error)>0 )
781             {
782 0           $error = $errors_aref->[$error_num] ;
783             }
784             }
785             }
786              
787 0           return $error ;
788             }
789              
790             # ============================================================================================
791             # END OF PACKAGE
792             1;
793              
794             __END__