File Coverage

blib/lib/Err.pm
Criterion Covered Total %
statement 50 55 90.9
branch 17 20 85.0
condition n/a
subroutine 13 14 92.8
pod 4 4 100.0
total 84 93 90.3


line stmt bran cond sub pod time code
1             package Err;
2 2     2   80614 use base qw(Exporter);
  2         8  
  2         231  
3              
4 2     2   12 use strict;
  2         3  
  2         68  
5 2     2   9 use warnings;
  2         7  
  2         62  
6              
7 2     2   11 use Carp qw(croak);
  2         3  
  2         121  
8 2     2   11 use Scalar::Util qw(blessed);
  2         11  
  2         226  
9              
10             # load Exception::Class and declare the Err::Exception baseclass
11 2     2   1854 use Exception::Class ("Err::Exception");
  2         19650  
  2         13  
12              
13             our $VERSION = "0.02";
14             our @EXPORT_OK;
15              
16             my %defaults; # default arguments
17              
18             ########################################################################
19              
20             sub _class_from_code {
21 40     40   58 my $code = shift;
22 40 100       103 return "Exception::Class::Base" unless length $code;
23 35 50       64 return "Err::Exception" if $code eq ".";
24 35         54 $code =~ s/\A\./Err::Exception::/x;
25 35         89 $code =~ s/[.']/::/gx;
26 35         99 return $code;
27             }
28              
29             ########################################################################
30              
31             sub is_err($) {
32 9 100   9 1 2771 return unless blessed $_;
33 5         11 return $_->isa(_class_from_code(shift));
34             }
35             push @EXPORT_OK, "is_err";
36              
37             sub ex_is_err($) {
38 9 100   9 1 831 return unless blessed $@;
39 5         11 return $@->isa(_class_from_code(shift));
40             }
41             push @EXPORT_OK, "ex_is_err";
42              
43             ########################################################################
44              
45             sub throw_err($$@) { ## no critic (RequireFinalReturn)
46 13     13 1 4673 my $err_class = _class_from_code(shift);
47 13         22 my $message = shift;
48              
49             # pre-populate our arguments from the defaults
50 13 100       11 my %args = (%{ $defaults{ $err_class } || {} }, @_);
  13         65  
51              
52             # throw the exception
53 13         69 $err_class->throw( message => $message, %args );
54             }
55             push @EXPORT_OK, "throw_err";
56              
57             ########################################################################
58              
59             sub declare_err($%) {
60 15     15 1 6115 my $err_class = _class_from_code(shift);
61 15         31 my %args = @_;
62              
63             # set the parent
64 15         16 my $parent;
65 15 100       36 $parent = _class_from_code(delete $args{isa}) if defined $args{isa};
66 15 100       29 unless (defined $parent) {
67 13         14 $parent = $err_class;
68              
69             # attempt to strip off the last ::whatever, but if we can't
70             # (presumably because the class name only had one part) just
71             # default to Err::Exception
72 13 100       51 unless($parent =~ s/::[^:]+\z//x) {
73 5         10 $parent = "Err::Exception";
74             }
75             }
76              
77             # set the description
78 15         20 my $description = delete $args{description};
79              
80             # everything else is fields. Remember them.
81 15         37 $defaults{ $err_class } = \%args;
82              
83             # declare the exception with Exception::Class
84 15         89 Exception::Class->import(
85             $err_class => {
86             isa => $parent,
87             (defined $description ? (description => $description) : ()),
88 15 100       30 fields => [keys %{ $defaults{ $err_class } }],
89             }
90             );
91              
92 15         10009 return;
93             }
94             push @EXPORT_OK, "declare_err";
95              
96             ########################################################################
97             # code below here is to support compile time checking (technically
98             # "CHECK" time checking) that exceptions have been properly declared when
99             # they're referenced in is_err and throw_err.
100             #
101             # None of it will be checked if B::CallChecker is not installed (but the
102             # code will still function okay)
103             ########################################################################
104              
105 2     2   840 if (eval "use B::CallChecker; 1") {
  0         0  
  0         0  
106              
107             ########################################################################
108             # routine to work out if a code has previously been declared or not
109             ########################################################################
110              
111             my %classes_we_have_declared;
112              
113             sub _is_code_declared {
114 0     0     my $err_class = _class_from_code(shift);
115              
116             # has this been declared with a declare_err call?
117 0 0         return 1 if $classes_we_have_declared{ $err_class };
118              
119             # no? well, there's still a chance that someone has
120             # declared it manually! We should check isa (note, we can't
121             # *just* check isa because it's entirely possible that someone
122             # has correctly used a declare_err to declare this code
123             # but that declare_err statement hasn't been executed yet
124             # (because code_is_declared is being called at CHECK time)
125             # in which case the code will have been registered but the
126             # @ISA will not have been setup at that point)
127 0           return $err_class->isa("Exception::Class::Base");
128             }
129              
130             ########################################################################
131             # wrapping throw_err, is_err, ex_is_err
132             ########################################################################
133              
134             foreach my $subname (qw(throw_err is_err ex_is_err)) {
135             my $uboat = do {
136 2     2   1599 no strict 'refs';
  2         4  
  2         763  
137             \&{ $subname };
138             };
139              
140             # remember what the normal checking routine would do
141             my ($original_check, $data) = B::CallChecker::cv_get_call_checker($uboat);
142              
143             # install our own checker that doesn't actually do prototype
144             # checking but instead interrogates the first argument to see
145             # if it's a valid exception code
146             B::CallChecker::cv_set_call_checker($uboat, sub {
147              
148             # extract the first argument
149             my $const = $_[0]->first->first->sibling;
150              
151             # ignore it if it's not a constant. We can't check this
152             # at compile time, so skip the check
153             unless ($const->name eq "const") {
154             croak "Improper use of $subname. The first argument to $subname was not a constant string";
155             }
156              
157             # get the value of the constant
158             my $code = ${ $const->sv->object_2svref };
159              
160             # throw a compile time error if the code
161             # hasn't been declared yet at compile time
162             unless (_is_code_declared($code)) {
163             croak "Undeclared exception code $code used in $subname (you must declare exception classes before referencing them)";
164             }
165              
166             # return the results of making the normal check
167             return $original_check->(@_);
168             },$data);
169             }
170              
171             ########################################################################
172             # declare_err wrapping
173             ########################################################################
174              
175             my $uboat = \&declare_err;
176              
177             # remember what the normal checking routine would do
178             my ($original_check, $data) = B::CallChecker::cv_get_call_checker($uboat);
179              
180             # install our own checker that doesn't actually do prototype
181             # checking but instead interrogates the first argument to
182             # get the check
183             B::CallChecker::cv_set_call_checker($uboat, sub {
184              
185             # extract the first argument
186             my $const = $_[0]->first->first->sibling;
187              
188             # ignore it if it's not a constant. We can't check this
189             # at compile time, so skip the check
190             unless ($const->name eq "const") {
191             croak "Improper use of declare_err. The first argument to declare_err was not a constant string";
192             }
193              
194             # get the value of the constant
195             my $code = ${ $const->sv->object_2svref };
196             $classes_we_have_declared{ _class_from_code($code) } = 1;
197              
198             # return the results of making the normal check
199             return $original_check->(@_);
200             },$data);
201             }
202              
203             ########################################################################
204              
205             1;
206              
207             __END__