With Exception::Class
, I can define exceptions as classes, and they're available everywhere once they've been loaded anywhere. But various places, including the docs for E::C itself, recommend using Throwable
nowadays.
Throwable
is a role, so I need to build the classes to compose it into. Throwable::Factory
helps with that, but I can't figure out how to make these classes available everywhere. It seems T::F builds subroutines that return opaque class names. I feel I'm missing the last piece of the puzzle but haven't been able to find any examples of T::F real-world usage.
CodePudding user response:
One idea could be to collect the exceptions in a separate module and import that into all modules that needs access to the exceptions. Unfortunately, it seems like it is difficult to export these for some reason. I tried the following (MyExceptions.pm
):
package MyExceptions;
use Throwable::Factory
GeneralException => undef,
RuntimeException => undef,
;
our @EXPORT = qw(GeneralException RuntimeException);
sub import2 {
no strict 'refs';
my $caller = caller;
my $pkg = __PACKAGE__;
for my $name (@EXPORT) {
my $imported = $caller . '::' . $name;
my $coderef = *{$pkg . "::" . $name};
*{ $imported } = \*{ $coderef };
}
}
sub import {
no strict 'refs';
my $caller = caller;
my $pkg = __PACKAGE__;
my @coderefs = (
["GeneralException", *MyExceptions::GeneralException],
["RuntimeException", *MyExceptions::RuntimeException]
);
for my $item (@coderefs) {
my ($name, $coderef) = @$item;
my $imported = $caller . '::' . $name;
*{ $imported } = \*{ $coderef };
}
}
1;
I was not able to make the import2()
exporter sub
in the above code work (it does not export the exception but something else (but what?)), so as a workaround I wrote the import()
sub which works.
CodePudding user response:
There seem to be 4 things I'm looking for.
- Easy syntax for declaring exception types.
- Exceptions that implement Throwable.
- Extra features in the exception objects, like tags, custom attributes, and simplification of passing attributes to the constructor.
- Instantiation of exceptions via classes (globally available) rather than functions (which have to be imported into every module).
Like Exception::Class
, Throwable::Factory
and Throwable::SugarFactory
offer a condensed syntax for declaring exception types, but it turns out I can live without that. Throwable::Factory
in fact has everything I want, except the exception functions have to be declared in the same file they're used in. They're kind of throwawayable-throwable exceptions. I don't want that.
Some of the extra features in Throwable::Factory
exceptions come from Throwable::Error, which is part of the Throwable
distribution. The rest are easy enough to steal. Throwable::Error is in fact a Moo
class, and so we have a winner.
I can put all my exception classes in a single file and load it via use
at the top of my app. The exception hierarchy inherits from Throwable::Error as a base class. Because these are Moo classes, it's trivial to add custom accessors to particular classes. And I can cut/paste the extra features I like from Throwable::Factory.
package MyApp::Exceptions ;
use strict ;
use warnings ;
use Throwable::Error ;
use Types::Standard qw( Str ) ;
use Moo ;
use namespace::clean ;
use feature qw(signatures) ;
no warnings qw(experimental::signatures) ;
extends 'Throwable::Error' ;
with 'Role::Identifiable::HasTags' ;
has description => (
is => 'ro',
isa => Str,
required => 1,
default => 'Generic exception',
) ;
# stack_trace() and message() inherited from Throwable::Error
sub error ($self) { $self->message }
sub package ($self) { $self->stack_trace->frame(0)->package }
sub file ($self) { $self->stack_trace->frame(0)->filename }
sub line ($self) { $self->stack_trace->frame(0)->line }
# sugar for ::HasTags
sub has_tags ( $self, @wanted ) {
$self->has_tag($_) || return 0 for @wanted ;
return 1 ;
}
# support shorthand instantiation eg Foo->throw($message, attr => $val);
around BUILDARGS => sub {
my ( $orig, $class, @args ) = @_ ;
return {} unless @args ;
return $class->$orig(@args) if @args == 1 ;
unshift @args, 'message' if @args % 2 ;
return $class->$orig( {@args} ) ;
} ;
# ----- enduser exception classes -----
package SystemError ;
use Types::Standard qw( Int ) ;
use Moo ;
extends 'MyApp::Exceptions' ;
has code => ( is => 'ro', isa => Int->where('$_ >= 0'), default => 1 ) ;
has ' description' => ( default => 'A system error' ) ;
package FileError ;
use Types::Standard qw( InstanceOf ) ;
use Moo ;
extends 'SystemError' ;
has ' code' => ( default => 2 ) ;
has ' description' => ( default => 'A file error' ) ;
has file => ( is => 'ro', required => 1, isa => InstanceOf['Path::Tiny'] ) ;
1 ;
As long as somewhere, I've said use MyApp::Exceptions;
, now everywhere I can say:
use Nice::Try ;
try {
something() or SystemError->throw("Problem trying to do something",
code => 7,
tags => [qw(something broke)],
) ;
}
catch ( SystemError $e where { $_->has_tags(qw(something broke)) }) {
fix_it($e) ;
}
catch ( SystemError $e where { $_->has_tag('something') }) {
repair_it($e) ;
}
catch ( FileError $e ) {
warn sprintf "Problem doing something() with file %s: %s",
$e->file->basename, $e->message ;
}
catch ( $e ) {
die "Give up! $e" ;
}