File Coverage

blib/lib/Class/ReluctantORM/Exception.pm
Criterion Covered Total %
statement 12 57 21.0
branch 0 20 0.0
condition 0 2 0.0
subroutine 4 8 50.0
pod 1 3 33.3
total 17 90 18.8


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Class::ReluctantORM::Exception - OO Exceptions
4              
5             =head1 SYNOPSIS
6              
7             use Class::ReluctantORM::Exception;
8              
9             # In API code:
10              
11             # dies locally
12             Class::ReluctantORM::Exception::Params::Missing->throw(param => 'id');
13              
14             # dies from caller's perspective
15             Class::ReluctantORM::Exception::Params::Missing->croak(param => 'id');
16              
17             # dies from caller's caller's perspective
18             Class::ReluctantORM::Exception::Params::Missing->croak(param => 'id', frames => 2);
19              
20             # To catch:
21             eval { something_dangerous(); }
22             if (my $e = Class::ReluctantORM::Exception::Params::Missing->caught()) {
23             my $p = $e->param(); # Find out what was missing
24             print $e; # Stringifies nicely
25             } else {
26             die $@; # Pass on unrecognized exceptions
27             }
28              
29             # Special handler included for working with OmniTI::DB connections....
30             my $dbh = NatGeo::DB::Magazine->new();
31             $dbh->set_handle_error(Class::ReluctantORM::Exception->make_db_error_handler());
32              
33              
34             =head1 DESCRIPTION
35              
36             Uses Exception::Class to define a wide variety of exceptions.
37              
38             =head1 STRINGIFICATION
39              
40             Any extra fields defined for a particular exception
41             class will be included in the stringification of
42             the exception, like this:
43              
44             Error message
45             field1 => value1
46             field2 => value2
47              
48             =head1 EXCEPTIONS
49              
50             =over
51              
52             =item Class::ReluctantORM::Exception::Param
53              
54             Exceptions related to parameter passing. Expect fields 'param'.
55              
56             =over
57              
58             =item Class::ReluctantORM::Exception::Param::Missing
59              
60             =item Class::ReluctantORM::Exception::Param::Empty
61              
62             An array or hash ref turned out to be present but empty.
63              
64             =item Class::ReluctantORM::Exception::Param::ExpectedHash
65              
66             Thrown when the method uses named parameters, but an odd number of parameters were provided. param field is not used.
67              
68             =item Class::ReluctantORM::Exception::Param::ExpectedHashRef
69              
70             =item Class::ReluctantORM::Exception::Param::ExpectedArrayRef
71              
72             =item Class::ReluctantORM::Exception::Param::ExpectedHashref
73              
74             =item Class::ReluctantORM::Exception::Param::MutuallyExclusive
75              
76             Used when two parameters cannot both be present. Use fields 'param' and 'other_param'.
77              
78             =item Class::ReluctantORM::Exception::Param::Duplicate
79              
80             Thrown when the same named parameter is used more than once.
81              
82             =back
83              
84             =back
85              
86              
87             =head1 AUTHOR
88              
89             Clinton Wolfe
90              
91             =cut
92              
93             package Class::ReluctantORM::Exception;
94 42     42   693 use strict;
  42         83  
  42         2414  
95             our $DEBUG ||= 0;
96 42     42   49874 use Data::Dumper;
  42         535356  
  42         4125  
97             our $TRACE ||= 0; # Set to true to enable traces on croak();
98              
99 42     42   406 use base 'Exception::Class::Base';
  42         90  
  42         46230  
100              
101             our %FIELD_RENDERERS =
102             (
103             fetch_locations => sub {
104             my $traces = shift || [];
105             unless ($traces) { return '' }
106             my $str = "\tFetched from " . (scalar @$traces) . " location(s):\n";
107             foreach my $trace (@$traces) {
108             $str .= "\t\tLocation Trace:\n";
109             foreach my $frame (@$trace) {
110             # TODO - TB2CRO - OmniTI-ism
111             # Special hook for Mungo support
112             if ($frame->{package} =~ /Mungo::FilePage/) {
113             my $file = $main::Response->{Mungo}->demangle_name($frame->{package} . '::__content');
114             $file =~ s{^Mungo::FilePage\(}{};
115             $file =~ s{\)$}{};
116             $str .= "\t\t\tfile: " . $file . " line:" . $frame->{line} . "\n";
117             } else {
118             $str .= "\t\t\t" . $frame->{file} . " line:" . $frame->{line} . "\n";
119             }
120             }
121             }
122             return $str;
123             },
124             table => sub {
125             my $table = shift;
126             return '' unless $table;
127             return $table->display_name();
128             },
129             join => sub {
130             my $j = shift;
131             return '' unless $j;
132             return $j->pretty_print(prefix => "\t ");
133             },
134             query_location => sub {
135             my $frames = shift || [];
136             unless ($frames) { return '' }
137             my $str = "\tOriginal query location:\n";
138             foreach my $frame (@$frames) {
139             $str .= "\t " . $frame->{file} . " line " . $frame->{line} . "\n";
140             }
141             return $str;
142             },
143             );
144              
145             sub full_message {
146 0     0 1   my $self = shift;
147 0           my @field_names = $self->Fields();
148 0           my $msg = $self->message . "\n"; # Automatic newline?
149 0   0       $msg .= ($self->description . "\n") || '';
150 0           foreach my $field (@field_names) {
151 0           my $val = $self->$field;
152 0 0         if (exists $FIELD_RENDERERS{$field}) {
153 0           $msg .= $FIELD_RENDERERS{$field}->($val);
154             } else {
155 0 0         $msg .= "\t$field => " . (defined($val) ? $val : 'undef') . "\n";
156             }
157             }
158              
159             # Include filename and file
160             #print STDERR "In full_message, have frame count " . $self->trace->frame_count . "\n";
161 0           my $frame = $self->trace->frame(0);
162 0           $msg .= " at " . $frame->filename . " line " . $frame->line . "\n";
163              
164 0           return $msg;
165             }
166              
167             sub croak {
168 0     0 0   my $class = shift;
169 0           my %args;
170              
171             # Behave like throw: if one arg, it's the message.
172 0 0         if (@_ == 1) {
173 0           %args = (message => shift());
174             } else {
175 0           %args = @_;
176             }
177              
178 0 0         my $frame_count = defined($args{frames}) ? $args{frames} : 1;
179 0           delete $args{frames};
180              
181 0           my $self = $class->new(%args);
182              
183 0           my @frames = $self->{trace}->frames;
184             #print STDERR "In croak, have orginal frame count " . (scalar @frames) . "\n";
185              
186             # If requested frame skip is greater than actual frame count, force to be one less than actual frame count.
187 0 0         if ($frame_count >= @frames) {
188 0           $frame_count = @frames - 1;
189             }
190              
191 0           my @dropped_frames = splice @frames, 0, $frame_count; # Delete $frame_count frames from the top (nearest) end of the stack.
192 0           $self->{trace}->{frames} = \@frames;
193             #print STDERR "In croak, have final frame count " . (scalar @frames) . "\n";
194              
195 0 0         if ($DEBUG > 1) {
196 0           print STDERR __PACKAGE__ . ':' . __FILE__ . " - Have dropped frames:\n";
197 0           for (@dropped_frames) {
198 0           print "\t" . $_->filename . ':' . $_->line . "\n";
199             }
200             }
201              
202 0 0         if ($TRACE) {
203 0           $self->show_trace(1);
204             }
205              
206 0           die $self;
207             }
208              
209             # Returns a coderef suitable for setting the DBI HandleError attribute.
210             sub make_db_error_handler {
211 0     0 0   my $class = shift;
212             my $code = sub {
213 0     0     my $errstr = shift;
214 0           my $dbh = shift;
215 0           my $bind_msg = '(in PREPARE stage)';
216 0 0         if ($dbh->{ParamValues}) {
217 0           my %binds = %{$dbh->{ParamValues}};
  0            
218 0 0         if ($DEBUG) { print STDERR __PACKAGE__ . ':' . __LINE__ . "In DB error handler, have binds:\n" . Dumper(\%binds); }
  0            
219 0 0         $bind_msg = join ', ', map { $_ . ':' . (defined($binds{$_}) ? $binds{$_} : 'undef') } keys %binds;
  0            
220             }
221             Class::ReluctantORM::Exception::SQL::ExecutionError->croak(
222 0           frames => 1,
223             error => $errstr,
224             statement => $dbh->{Statement},
225             bind_values => $bind_msg,
226             );
227             }
228 0           }
229              
230              
231             package main;
232              
233             # Note: this should follow the definition of Class::ReluctantORM::Exception
234             use Exception::Class
235             (
236              
237             # Coding
238 42         2970 'Class::ReluctantORM::Exception::NotImplemented' =>
239             {
240             isa => 'Class::ReluctantORM::Exception',
241             description => 'The code that is being attempted to execute has not yet been written.',
242             },
243             'Class::ReluctantORM::Exception::CannotLoadClass' =>
244             {
245             isa => 'Class::ReluctantORM::Exception',
246             description => 'The requested class cannot be loaded.',
247             fields => [qw(class)],
248             },
249              
250              
251              
252             # Param handling
253             'Class::ReluctantORM::Exception::Param' =>
254             {
255             isa => 'Class::ReluctantORM::Exception',
256             description => 'A general parameter error',
257             fields => [ qw(param value) ],
258             },
259              
260             'Class::ReluctantORM::Exception::Param::Missing' =>
261             {
262             isa => 'Class::ReluctantORM::Exception::Param',
263             description => 'A required parameter is missing. The required parameter is listed in the param field.',
264             },
265              
266             'Class::ReluctantORM::Exception::Param::BadValue' =>
267             {
268             isa => 'Class::ReluctantORM::Exception::Param',
269             description => 'A parameter has an invalid value.',
270             fields => [ qw(expected) ],
271             },
272              
273             'Class::ReluctantORM::Exception::Param::ExpectedHash' =>
274             {
275             isa => 'Class::ReluctantORM::Exception::Param',
276             description => 'The method or subroutine expected to be called with named paramters, but an odd number of parameters were passed.',
277             },
278              
279             'Class::ReluctantORM::Exception::Param::ExpectedArrayRef' =>
280             {
281             isa => 'Class::ReluctantORM::Exception::Param',
282             description => 'A parameter was expected to be an array ref, but was not. The parameter is listed in the param field.',
283             },
284              
285             'Class::ReluctantORM::Exception::Param::ExpectedHashRef' =>
286             {
287             isa => 'Class::ReluctantORM::Exception::Param',
288             description => 'A parameter was expected to be an hash ref, but was not. The parameter is listed in the param field.',
289             },
290              
291             'Class::ReluctantORM::Exception::Param::WrongType' =>
292             {
293             isa => 'Class::ReluctantORM::Exception::Param',
294             description => 'A parameter is of the wrong type. ',
295             fields => [ qw(param expected) ],
296             },
297              
298             'Class::ReluctantORM::Exception::Param::MutuallyExclusive' =>
299             {
300             isa => 'Class::ReluctantORM::Exception::Param',
301             description => 'You may only provide or or the other of a pair of parameters, but you provided both. The parameters are listed in the param_set field.',
302             fields => [ qw(param_set) ],
303             },
304              
305             'Class::ReluctantORM::Exception::Param::Duplicate' =>
306             {
307             isa => 'Class::ReluctantORM::Exception::Param',
308             description => 'You specified the same parameter more than once in a list, when unique values are required.',
309             },
310             'Class::ReluctantORM::Exception::Param::Spurious' =>
311             {
312             isa => 'Class::ReluctantORM::Exception::Param',
313             description => 'You provided extra, unrecognized parameters.',
314             },
315              
316              
317              
318             # Other function calling problems
319             'Class::ReluctantORM::Exception::Call' =>
320             {
321             isa => 'Class::ReluctantORM::Exception',
322             description => 'A general error in function/method calling style',
323             },
324              
325             'Class::ReluctantORM::Exception::Call::NotMutator' =>
326             {
327             isa => 'Class::ReluctantORM::Exception::Call',
328             description => 'You may not use this function to set a value.',
329             fields => [ qw(attribute) ],
330             },
331              
332             'Class::ReluctantORM::Exception::Call::ExpectationFailure' =>
333             {
334             isa => 'Class::ReluctantORM::Exception::Call',
335             description => 'A precondition failed for this operation. Hilarity ensues.',
336             },
337              
338             'Class::ReluctantORM::Exception::Call::NotPermitted' =>
339             {
340             isa => 'Class::ReluctantORM::Exception::Call',
341             description => 'You may not call this method.',
342             },
343              
344             'Class::ReluctantORM::Exception::Call::NotPermitted::ClassMethodOnly' =>
345             {
346             isa => 'Class::ReluctantORM::Exception::Call::NotPermitted',
347             description => 'You may not call this method as an instance method. You must call it as a class method.',
348             fields => [ qw(method) ],
349             },
350              
351             'Class::ReluctantORM::Exception::Call::NotPermitted::InstanceMethodOnly' =>
352             {
353             isa => 'Class::ReluctantORM::Exception::Call::NotPermitted',
354             description => 'You may not call this method as a class method. You must call it as an instance method.',
355             fields => [ qw(method) ],
356             },
357              
358             'Class::ReluctantORM::Exception::Call::Deprecated' =>
359             {
360             isa => 'Class::ReluctantORM::Exception::Call',
361             description => 'You may not call this method, because it is no longer supported.',
362             },
363              
364             'Class::ReluctantORM::Exception::Call::PureVirtual' =>
365             {
366             isa => 'Class::ReluctantORM::Exception::Call',
367             description => 'This method may not be called directly, because a subclass is supposed to provide its own implementation.',
368             },
369              
370             'Class::ReluctantORM::Exception::Call::NoSuchMethod' =>
371             {
372             isa => 'Class::ReluctantORM::Exception::Call',
373             description => 'You tried to use a method that does not exist. This usually means the module does not know how to AUTOLOAD the requested method.',
374             },
375              
376              
377             # Database problems
378             'Class::ReluctantORM::Exception::Data' =>
379             {
380             isa => 'Class::ReluctantORM::Exception',
381             description => 'A general data-related error.',
382             fields => [ ],
383             },
384              
385             'Class::ReluctantORM::Exception::Data::NotFound' =>
386             {
387             isa => 'Class::ReluctantORM::Exception::Data',
388             description => 'Required data was not found.',
389             fields => [ qw(primary_key criteria) ],
390             },
391              
392             'Class::ReluctantORM::Exception::Data::AlreadyInserted' =>
393             {
394             isa => 'Class::ReluctantORM::Exception::Data',
395             description => 'A data object is marked as already existing in the database, but you just tried to insert it again.',
396             fields => [ qw(primary_key) ],
397             },
398             'Class::ReluctantORM::Exception::Data::DependsOnInsert' =>
399             {
400             isa => 'Class::ReluctantORM::Exception::Data',
401             description => 'This object depends on another data object, which must be inserted before you can perform this operation.',
402             },
403              
404             'Class::ReluctantORM::Exception::Data::UpdateWithoutInsert' =>
405             {
406             isa => 'Class::ReluctantORM::Exception::Data',
407             description => 'A data object is marked as not yet existing in the database, but you just tried to do an UPDATE on it.',
408             },
409              
410             'Class::ReluctantORM::Exception::Data::DeleteWithoutInsert' =>
411             {
412             isa => 'Class::ReluctantORM::Exception::Data',
413             description => 'A data object is marked as not yet existing in the database, but you just tried to do a DELETE on it.',
414             },
415              
416             'Class::ReluctantORM::Exception::Data::NeedMoreKeys' =>
417             {
418             isa => 'Class::ReluctantORM::Exception::Data',
419             description => 'An operation depends on having multiple primary or foreign keys, but you did not provide enough keys.',
420             },
421              
422              
423             'Class::ReluctantORM::Exception::Data::UnsupportedCascade' =>
424             {
425             isa => 'Class::ReluctantORM::Exception::Data',
426             description => 'An insert or update would require performing a cascading insert or update, which is not supported.',
427             },
428              
429             'Class::ReluctantORM::Exception::Data::UniquenessViolation' =>
430             {
431             isa => 'Class::ReluctantORM::Exception::Data',
432             description => 'A proposed operation would violate a uniqueness constraint, either DB-enforced or ReluctantORM-based.',
433             },
434              
435             'Class::ReluctantORM::Exception::Data::FetchRequired' =>
436             {
437             isa => 'Class::ReluctantORM::Exception::Data',
438             description => 'You tried to access related data, but the related data has not been fetched yet. This ORM does not support implicit lazy loading (that is what makes it Reluctant). Please adjust the fetch call to include the related data. If Origin Tracking is enabled, the location of the fetch call(s) will be listed.',
439             # Special renderer for fetch_locations in message()
440             fields => [ qw(call_instead called fetch_locations) ],
441             },
442              
443              
444             # SQL problems
445             'Class::ReluctantORM::Exception::SQL' =>
446             {
447             isa => 'Class::ReluctantORM::Exception',
448             description => 'An error related to parsing, manipulation, or execution of SQL objects.',
449             fields => [ qw(sql) ],
450             },
451              
452             'Class::ReluctantORM::Exception::SQL::AbortedByMonitor' =>
453             {
454             isa => 'Class::ReluctantORM::Exception::SQL',
455             description => 'The query exceeded the fatal_limit threshold of a monitor, and was aborted.',
456             fields => [ qw(monitor observed limit query_location) ],
457             },
458              
459              
460             'Class::ReluctantORM::Exception::SQL::NotInflatable' =>
461             {
462             isa => 'Class::ReluctantORM::Exception::SQL',
463             description => 'The SQL object has insufficient metadata to inflate',
464             fields => [ qw() ],
465             },
466              
467             'Class::ReluctantORM::Exception::SQL::NotInflatable::MissingColumn' =>
468             {
469             isa => 'Class::ReluctantORM::Exception::SQL::NotInflatable',
470             description => 'An essential column is missing in the output columns, which is needed to inflate',
471             fields => [ qw(table column) ],
472             },
473              
474             'Class::ReluctantORM::Exception::SQL::NotInflatable::ExtraTable' =>
475             {
476             isa => 'Class::ReluctantORM::Exception::SQL::NotInflatable',
477             description => 'Could not figure out what to do with a table in the query.',
478             fields => [ qw(table) ],
479             },
480              
481             'Class::ReluctantORM::Exception::SQL::NotInflatable::VagueJoin' =>
482             {
483             isa => 'Class::ReluctantORM::Exception::SQL::NotInflatable',
484             description => 'Could not figure out what to do with a join in the query.',
485             fields => [ qw(join) ],
486             },
487              
488             'Class::ReluctantORM::Exception::SQL::TooComplex' =>
489             {
490             isa => 'Class::ReluctantORM::Exception::SQL',
491             description => 'The statement or SQL object was too complicated to interpret.',
492             fields => [ qw() ],
493             },
494              
495             'Class::ReluctantORM::Exception::SQL::ParseError' =>
496             {
497             isa => 'Class::ReluctantORM::Exception::SQL',
498             description => 'The SQL statement contained a syntax error or other parsing problem.',
499             fields => [ qw(sql) ],
500             },
501              
502             'Class::ReluctantORM::Exception::SQL::AmbiguousReference' =>
503             {
504             isa => 'Class::ReluctantORM::Exception::SQL',
505             description => 'A table or column reference could not be disambiguated.',
506             fields => [ qw(statement referent) ],
507             },
508              
509             'Class::ReluctantORM::Exception::SQL::ExecuteWithoutPrepare' =>
510             {
511             isa => 'Class::ReluctantORM::Exception::SQL',
512             description => 'You must call $driver->prepare($sql) before calling $sql->execute()',
513             fields => [ qw() ],
514             },
515              
516             'Class::ReluctantORM::Exception::SQL::FinishWithoutPrepare' =>
517             {
518             isa => 'Class::ReluctantORM::Exception::SQL',
519             description => 'You must call $driver->prepare($sql) before calling $sql->finish()',
520             fields => [ qw() ],
521             },
522              
523             'Class::ReluctantORM::Exception::SQL::ExecutionError' =>
524             {
525             isa => 'Class::ReluctantORM::Exception::SQL',
526             description => 'An error occured within the database.',
527             fields => [ qw(statement bind_values) ],
528             },
529              
530              
531 42     42   500287 );
  42         86711  
532              
533              
534             1;