File Coverage

blib/lib/Error/Hierarchy/Util.pm
Criterion Covered Total %
statement 34 97 35.0
branch 2 36 5.5
condition 0 22 0.0
subroutine 11 22 50.0
pod 14 14 100.0
total 61 191 31.9


line stmt bran cond sub pod time code
1 2     2   780 use 5.008;
  2         7  
  2         86  
2 2     2   12 use strict;
  2         4  
  2         64  
3 2     2   10 use warnings;
  2         4  
  2         100  
4              
5             package Error::Hierarchy::Util;
6             BEGIN {
7 2     2   53 $Error::Hierarchy::Util::VERSION = '1.103530';
8             }
9             # ABSTRACT: Assertions and other tools
10 2     2   1988 use Data::Miscellany 'is_defined';
  2         2862  
  2         154  
11 2     2   677 use Error::Hierarchy::Mixin; # to get UNIVERSAL::throw()
  2         5  
  2         50  
12 2     2   9 use Exporter qw(import);
  2         4  
  2         2555  
13             our %EXPORT_TAGS = (
14             ref => [
15             qw{
16             assert_arrayref assert_nonempty_arrayref
17             assert_hashref assert_nonempty_hashref
18             }
19             ],
20             misc => [
21             qw{
22             assert_class assert_defined assert_read_only assert_is_integer
23             assert_getopt assert_enum assert_named_args load_class
24             }
25             ],
26             );
27             our @EXPORT_OK = @{ $EXPORT_TAGS{all} = [ map { @$_ } values %EXPORT_TAGS ] };
28              
29             sub assert_class ($$) {
30 0     0 1 0 my ($obj, $class) = @_;
31 0 0 0     0 return if ref $obj && $obj->isa($class);
32 0         0 local $Error::Depth = $Error::Depth + 2;
33 0         0 throw Error::Hierarchy::Internal::Class(
34             class_expected => $class,
35             class_got => ref($obj),
36             );
37             }
38              
39             sub assert_read_only {
40 0 0   0 1 0 return unless @_;
41 0         0 local $Error::Depth = $Error::Depth + 2;
42 0         0 my $sub = (caller(1))[3];
43 0         0 throw Error::Hierarchy::Internal::ReadOnlyAttribute(attribute => $sub,);
44             }
45              
46             # In assert_condition we use
47             #
48             # local $Error::Depth = $Error::Depth + 3;
49             #
50             # because:
51             #
52             # +1 to make assert_condition invisible to caller
53             #
54             # +1 to make assert_defined and friends invisible to caller
55             #
56             # +1 to make the one who called assert_* invisible to caller, since we
57             # want to report the location where the method that checks its args using
58             # assert_* was called from.
59             sub assert_condition ($$$) {
60 2     2 1 6 my ($condition, $exception_class, $custom_message) = @_;
61 2 50       6 return if $condition;
62              
63             # get the name of the first sub an assert_* sub was called with the unmet
64             # assertion
65 2         2 my ($level, $sub);
66 2         3 do {
67 4         39 $sub = (caller(++$level))[3];
68             } until $sub !~ /^.*::assert_/;
69              
70             # XXX: shouldn't we use $level here instead of 3?
71 2         5 local $Error::Depth = $Error::Depth + 3;
72 2         20 $exception_class->throw(custom_message => "[$sub] $custom_message");
73             }
74              
75             sub assert_defined ($$) {
76 1     1 1 603 my ($val, $custom_message) = @_;
77              
78             # If it's a value object, it might have been autogenerated; see
79             # value_object accessor generator, in which case it might not have a value
80             # yet, but $val would be defined - it's the empty value object.
81             # Performance optimization: Because this function is called so often, we
82             # don't call assert_condition() unless it is necessary.
83 1 50       7 return if is_defined($val);
84 1         11 assert_condition(0, 'Error::Hierarchy::Internal::ValueUndefined',
85             $custom_message);
86             }
87              
88             sub assert_arrayref ($$) {
89 0     0 1 0 my ($val, $custom_message) = @_;
90 0   0     0 assert_condition(
91             (defined($val) && ref($val) eq 'ARRAY'),
92             'Error::Hierarchy::Internal::NoArrayRef',
93             $custom_message
94             );
95             }
96              
97             sub assert_nonempty_arrayref ($$) {
98 0     0 1 0 my ($val, $custom_message) = @_;
99 0   0     0 assert_condition(
100             (defined($val) && ref($val) eq 'ARRAY' && scalar @$val),
101             'Error::Hierarchy::Internal::EmptyArrayRef',
102             $custom_message
103             );
104             }
105              
106             sub assert_hashref ($$) {
107 0     0 1 0 my ($val, $custom_message) = @_;
108 0   0     0 assert_condition(
109             (defined($val) && ref($val) eq 'HASH'),
110             'Error::Hierarchy::Internal::NoHashRef',
111             $custom_message
112             );
113             }
114              
115             sub assert_nonempty_hashref ($$) {
116 0     0 1 0 my ($val, $custom_message) = @_;
117 0   0     0 assert_condition(
118             (defined($val) && ref($val) eq 'HASH' && scalar keys %$val),
119             'Error::Hierarchy::Internal::EmptyHashRef',
120             $custom_message
121             );
122             }
123              
124             sub assert_is_integer ($) {
125 1     1 1 567 my $val = shift;
126 1         7 assert_condition(
127             ($val =~ /^[1-9]$/),
128             'Error::Hierarchy::Internal::CustomMessage',
129             'expected an integer value from 1 to 9'
130             );
131             }
132              
133             # In Data::Conveyor, this function is called by service methods to verify
134             # options passed to it. If the value given is true, we just return. If it is
135             # false, we throw a special "help exception". When the shell service interface
136             # calls a service method, it catches this help exception and prints
137             # manpage-like help information for that method.
138             sub assert_getopt ($$) {
139 0     0 1   my ($val, $custom_message) = @_;
140 0 0         return if $val;
141 0           Data::Conveyor::Exception::ServiceMethodHelp->throw(
142             custom_message => $custom_message);
143             }
144              
145             sub assert_named_args {
146 0     0 1   my ($args, @args_spec) = @_;
147 0           my (%supported_args, @required_args);
148 0           for (@args_spec) {
149 0           /(^\+)?(.*)/;
150 0   0       my $required = defined $1 && $1 eq '+';
151 0           $supported_args{$2}++;
152 0 0         push @required_args => $2 if $required;
153             }
154 0           my @unsupported_args = grep { !$supported_args{$_} } keys %$args;
  0            
155 0           my @missing_required_args = grep { !defined $args->{$_} } @required_args;
  0            
156 0 0 0       return if @unsupported_args == 0 && @missing_required_args == 0;
157 0           my $sub = (caller(1))[3];
158 0           my $message = "$sub() called with illegal named arguments:\n";
159 0 0         if (@missing_required_args) {
160 0           local $" = ', ';
161 0           $message .= " missing required arguments: @missing_required_args\n";
162             }
163 0 0         if (@unsupported_args) {
164 0           local $" = ', ';
165 0           $message .= " unsupported arguments: @unsupported_args\n";
166             }
167 0           Error::Hierarchy::Internal::CustomMessage->throw(custom_message => $message);
168             }
169              
170             sub assert_enum {
171 0     0 1   my ($val, $enum_arrayref, $custom_message) = @_;
172 0           for my $valid_value (@$enum_arrayref) {
173 0 0         return if $val eq $valid_value;
174             }
175             throw Error::Hierarchy::Internal::CustomMessage(
176 0           custom_message => "$custom_message: invalid value [$val]");
177             }
178              
179             # support for "virtual" classes that do not exist as files.
180             # this is of no use for payload reinstantiation in a new
181             # process, as Storable calls require() before touching any
182             # accessor. it does allow a few things, though:
183             # load_class XYZ, 1 for example, or calling static methods
184             # directly, such as XYZ->DEFAULTS.
185             sub loader_callback {
186 0 0   0 1   shift if $_[0] eq __PACKAGE__;
187 0           our $loader_callback;
188 0 0         if (my $callback = shift) {
189 0 0         throw Error::Hierarchy::Internal::CustomMessage(
190             custom_message => "argument must be a coderef")
191             unless ref $callback eq 'CODE';
192 0           $loader_callback = $callback;
193             }
194 0           $loader_callback;
195             }
196              
197             sub load_class ($$) {
198 0     0 1   my ($class, $verbose) = @_;
199 0           assert_defined $class, 'called without class argument.';
200              
201             # An attempt at optimization: This sub is called very often. By relying on
202             # every class defining a $VERSION, we can shortcut costly processing.
203             {
204 2     2   13 no strict 'refs';
  2         4  
  2         372  
  0            
205 0 0         return if ${"$class\::VERSION"};
  0            
206             }
207              
208             # report errors from perspective of caller
209 0           local $Error::Depth = $Error::Depth + 1;
210 0           eval "require $class";
211 0 0 0       if (defined($@) && $@ ne '') {
212              
213             # allow for dynamic class generation
214 0 0         if (my $code = __PACKAGE__->loader_callback) {
215 0 0         return $class if $code->($class);
216             }
217              
218             # this error is so severe we want to print it during test mode
219 0 0         print $@ if $verbose;
220 0           throw Error::Hierarchy::Internal::CustomMessage(custom_message =>
221             sprintf("Couldn't load package [%s]: %s", $class, $@),);
222             }
223 0           $class;
224             }
225             1;
226              
227              
228             __END__