NAME

custom::failures - Minimalist, customized exception hierarchy generator

VERSION

version 0.004

SYNOPSIS

package MyApp::failure;

use custom::failures qw/io::file io::network/;

# customize failure methods…

DESCRIPTION

This module works like failures but lets you define a customized exception hierarchy if you need a custom namespace, additional attributes, or customized object behaviors.

Because failure classes have an @ISA chain and Perl by default uses depth-first-search to resolve method calls, you can override behavior anywhere in the custom hierarchy and it will take precedence over default failure behaviors.

There are two methods that might be useful to override:

  • message

  • throw

Both are described further, below.

USAGE

Defining a custom failure hierarchy

package MyApp::failure;

use custom::failures qw/foo::bar/;

This will define a failure class hierarchy under the calling package's namespace. The following diagram show the classes that will be created (arrows denote 'is-a' relationships):

MyApp::failure::foo::bar --> failure::foo::bar
       |                        |
       V                        V
MyApp::failure::foo      --> failure::foo
       |                        |
       V                        V
MyApp::failure           --> failure

Alternatively, if you want a different namespace for the hierarchy, do it this way:

use custom::failures 'MyApp::Error' => [ 'foo::bar' ];

That will create the following classes and relationships:

MyApp::Error::foo::bar --> failure::foo::bar
       |                        |
       V                        V
MyApp::Error::foo      --> failure::foo
       |                        |
       V                        V
MyApp::Error           --> failure

By having custom classes also inherit from a standard namespace, you can throw a custom error class that will still be caught in the standard namespace:

use Safe::Isa; # for $_isa
try {
    MyApp::failure::foo::bar->throw;
}
catch {
    if ( $_->$_isa( "failure::foo" ) ) {
        # handle it here
    }
};

Adding custom attributes

Failure classes are implemented with Class::Tiny, so adding attributes is trivially easy:

package MyApp::failure;

use custom::failures qw/foo::bar/;

use Class::Tiny qw/user/;

This adds a user attribute to MyApp::failure and all its subclasses so it can be set in the argument to throw:

MyApp::failure::foo->throw( { msg => "Ouch!", user => "me" } );

Be sure to load Class::Tiny after you load custom::failures so that your @ISA is already set up.

Overriding the message method

Overriding message lets you modify how the error string is produced. The message method takes a string (typically just the msg field) and returns a string. It should not produce or append stack trace information. That is done during object stringification.

Call SUPER::message if you want the standard error text prepended ("Caught $class: ...").

For example, if you want to use String::Flogger to render messages:

package MyApp::failure;

use custom::failures qw/foo::bar/;
use String::Flogger qw/flog/;

sub message {
    my ( $self, $msg ) = @_;
    return $self->SUPER::message( flog($msg) );
}

Then you can pass strings or array references or code references as the msg for throw:

MyApp::failure->throw( "just a string"               );
MyApp::failure->throw( [ "show some data %s", $ref ] );
MyApp::failure->throw( sub { call_expensive_sub() }  );

Because the message method is only called during stringification (unless you call it yourself), the failure class type can be checked before any expensive rendering is done.

Overriding the throw method

Overriding throw lets you modify the arguments you can provide or ensure that a trace is included. It can take whatever arguments you want and should call SUPER::throw with a hash reference to actually throw the error.

For example, to capture the filename associated with file errors:

package MyApp::failure;

use custom::failures qw/file/;

use Class::Tiny qw/filename/;

sub throw {
    my ( $class, $msg, $file ) = @_;
    my $args = {
        msg => $msg,
        filename => $file,
        trace => failures->croak_trace,
    };
    $self->SUPER::throw( $args );
}

sub message {
    # do something with 'msg' and 'filename'
}

Later you could use it like this:

MyApp::failure::file->throw( opening => $some_file );

Using BUILD

Class::Tiny supports BUILD, so you can also use that to do things with failure objects when thrown. This example logs exceptions as they are built:

use Log::Any qw/$log/;

sub BUILD {
    my ($self) = @_;
    $log->error( $self->message );
}

By using message instead of stringifying $self, we log the message but not the trace (if any).

AUTHOR

David Golden <dagolden@cpan.org>

COPYRIGHT AND LICENSE

This software is Copyright (c) 2013 by David Golden.

This is free software, licensed under:

The Apache License, Version 2.0, January 2004