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