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.
There seem to be 4 things I'm looking for.
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" ;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With