NAME

Error::Base - Simple structured errors with full backtrace

VERSION

This document describes Error::Base version v0.1.0

SYNOPSIS

use Error::Base;
Error::Base->crash('Sanity check failed');  # die() with backtrace

my $err     = Error::Base->new('Foo');      # construct object first
    yourcodehere(...);                  # ... do other stuff
$err->crash;                                # as object method

my $err     = Error::Base->new(
                'Foo error',                # odd arg is error text
                -quiet    => 1,             # no backtrace
                grink     => 'grunt',       # store somethings
                puppy     => 'dog',         # your keys, no leading dash 
            );
$err->crash;

$err->crank;                    # get cranky: warn() but don't die()
my $err = Error::Base->crank('Me!');        # also a constructor

eval{ Error::Base->crash( 'car', -foo => 'bar' ) }; 
my $err     = $@ if $@;         # catch and examine the object

my $err     = Error::Base->new(
                -base       => 'File handler error:',
                _openerr    => 'Couldn\t open $file for $op',
            );
$err->crash(
    -type
);

DESCRIPTION

    J'avais cru plus difficile de mourir. -- Louis XIV

Die early, die often. Make frequent sanity checks and die when a check fails. See neat dumps of the caller stack with each error. Construct a group of error messages in one object or write error text ad hoc. Trap an error object and examine the contents; or let it tell its sad tale and end it.

Error::Base usage can be simple or complex. For quick sanity checks, construct and throw a simple fatal error in one line. At the other extreme, you can override methods in your own error subclasses.

Error::Base is lightweight. It defines no global variables, uses no non-core modules (and few of those), exports no symbols, and is purely object-oriented. I hope you will be able to use it commonly instead of a simple die(). You are not required to subclass it.

METHODS

new()

my $err     = Error::Base->new('Foo');      # constructor
my $err     = Error::Base->new(             # with named args
                -base       => 'Bar error:',
                -quiet      => 1,
                -top        => 3,
                -prepend    => '@! Globalcorpcoapp:',
                -indent     => '@!                 ',
                foo         => bar,
            );
my $err     = Error::Base->new(             # okay to pass both
                    'bartender:'            # lone string...
                -base   => 'Bar error:',    # ... and named args
                -type   => 'last call',     # be more specific
                _beer   => 'out of beer',   # your private attribute(s)
            );

The constructor must be called as a class method; there is no mutator returning a new object based on an old one. You do have some freedom in how you call, though.

Called with an even number of args, they are all considered key/value pairs. Keys with leading dash ('-') are reserved for use by Error::Base; all others are free to use as you see fit. Error message text is constructed as a single string.

Called with an odd number of args, the first arg is shifted off and appended to the error message text. This shorthand may be offensive to some; in which case, don't do that. Instead, pass -base, -type, or both.

You may stash any arbitrary data inside the returned object (during construction or later) and do whatever you like with it. You might choose to supply additional optional texts for later access.

See "PARAMETERS".

crash()

Error::Base->crash('Sanity check failed');  # as class method
my $err = Error::Base->crash('Flat tire:'); # also a constructor
$err->crash;                                # as object method
$err->crash(        # all the same args are okay in crash() as in new()
            'bartender: '
        -base   => 'Bar error:',
    );
eval{ $err->crash }; 
my $err     = $@ if $@;         # catch and examine the object

crash() and other public methods may be called as class or object methods. If called as a class method, then new() is called internally. Call new() yourself first if you want to call crash() as an object method.

crash() is a very thin wrapper, easy to subclass. It differs from similar methods in that instead of returning its object, it die()-s with it. If uncaught, the error will stringify; if caught, the entire object is yours.

crank()

Error::Base->crank('More gruel!');          # as class method
$err->crank;                                # as object method
my $err = Error::Base->crank('Me!');        # also a constructor

This is exactly like crash() except that it warn()s instead of die()-ing. Therefore it can also usefully be used as a constructor of an object for later use.

crank() is also a very thin wrapper. You may subclass it; you may trap the entire object or let it stringify to STDERR.

cuss()

my $err = Error::Base->cuss('x%@#*!');      # also a constructor

Again, exactly like crash() or crank() except that it neither die()-s nor warn()s; it only returns the object.

The difference between new() and the other methods is that new() returns the constructed object containing only what was passed in as arguments. crash(), crank(), and cuss() perform a full stack backtrace (if not passed -quiet) and format the result for stringified display.

You may find cuss() useful in testing your subclass or to see how your error will be thrown without the bother of actually catching crash().

init()

$err->init(@args);

Probably, it is not useful to call this object method directly. Perhaps you might subclass it or call it from within your subclass constructor. The calling conventions are exactly the same as for the other public methods.

init(), is called on a newly constructed object, as is conventional. If you call it a second time on an existing object, new @args will overwrite previous values. Internally, when called on an existing object, crash(), crank(), and cuss() each call init().

Therefore, the chief distinction between calling as class or object method is that if you call new() first then you can separate the definition of your error text from the actual throw.

PARAMETERS

All public methods accept the same arguments, with the same conventions. All parameter names begin with a leading dash ('-'); please choose other names for your private keys.

If the same parameter is set multiple times, the most recent argument completely overwrites the previous:

my $err     = Error::Base->new( -top    => 3, );
    # -top is now 3
$err->cuss(  -top    => 0, );
    # -top is now 0
$err->crank( -top    => 1, );
    # -top is now 1

You are cautioned that deleting keys may be unwise.

-base

scalar string

$err->crash;                        # emits 'Undefined error'
$err->crash( -base => 'Bar');       # emits 'Bar'

The value of -base is printed in the first line of the stringified error object after a call to crash(), crank(), or cuss().

-type

scalar string

$err->crash( 
        -type   => 'last call'
    );                              # emits 'last call'
$err->crash(
        -base   => 'Bar',
        -type   => 'last call',
    );                              # emits 'Bar last call'

This parameter is provided as a way to express a subtype of error.

-pronto

scalar string

$err->crash( 'Pronto!' );           # emits 'Pronto!'
$err->crash(
            'Pronto!',
        -base   => 'Bar',
        -type   => 'last call',
    );                              # emits 'Bar last call Pronto!'
$err->crash(
        -base   => 'Bar',
        -type   => 'last call',
        -pronto => 'Pronto!',
    );                              # same thing

As a convenience, if the number of arguments passed in is odd, then the first arg is shifted off and appnended to the error message. This is done to simplify writing one-off, one-line sanity checks:

open( my $in_fh, '<', $filename )
    or Error::Base->crash("Couldn't open $filename for reading.");

It is expected that each message argument be a single scalar. If you need to pass a multi-line string then please embed escaped newlines ('\n').

-key

This feature has been replaced by "LATE INTERPOLATION".

-quiet

scalar boolean default: undef

$err->crash( -quiet         => 1, );        # no backtrace

By default, you get a full stack backtrace. If you want none, set this parameter. Only -msg will be emitted.

-top

scalar unsigned integer default: 2

$err->crash( -top           => 0, );        # really full backtrace

By default, you get a full stack backtrace: "full" meaning, from the point of invocation. Some stack frames are added by the process of crash()-ing itself; by default, these are not seen. If you want more or fewer frames you may set this parameter.

Beware that future implementations may change the number of stack frames added internally by Error::Base; and also you may see a different number of frames if you subclass, depending on how you do that. The safe way:

my $err     = Error::Base->new('Foo');      # construct object
$err->{ -top => ($err->{-top})++ };         # drop the first frame
$err->crash();

This is ugly and you may get a convenience method in future.

-prepend

scalar string default: undef

-indent

scalar string default: first char of -prepend, padded with spaces to length

-prepend_all

scalar string default: undef

my $err     = Error::Base->new(
                -prepend    => '#! Globalcorpcoapp:',
            );
$err->crash ('Boy Howdy!');
    # emits '@! Globalcorpcoapp: Boy Howdy!
    #        @                   in main::fubar at line 42    [test.pl]'

Any string passed to -prepend will be prepended to the first line only of the formatted error message. If -indent is defined then that will be prepended to all following lines. If -indent is undefined then it will be formed from the first character only of -prepend, padded with spaces to the length of -prepend. -prepend_all will be prepended to all lines.

This is a highly useful feature that improves readability in the middle of a dense dump. So in future releases, the default may be changed to form -prepend in some way for you if not defined. If you are certain you want no prepending or indentation, pass the empty string, q{}.

-$"

scalar string default: q{ }

my $err     = Error::Base->new(
                -base   => 'Bar',
                -type   => 'last call',
            );
$err->crash(
            'Pronto!',
    );                              # emits 'Bar last call Pronto!'
$err->crash(
            'Pronto!',
        '-$"'   => '=',
    );                              # emits 'Bar=last call=Pronto!'

If you interpolate an array into a double-quoted literal string, perl will join the elements with $". Similarly, if you late interpolate an array into an error message part, Error::Base will join the elements with the value of $self->{'-$"'}. This does not have any effect on the Perlish $". Similarly, $" is ignored when Error::Base stirs the pot.

Also, message parts themselves are joined with $self->{'-$"'}. The default is a single space. This helps to avoid the unsightly appearance of words stuck together because you did not include enough space in your args. Empty elements are spliced out to avoid multiple consecutive spaces.

Note that '-$"' is a perfectly acceptable hash key but it must be quoted, lest trains derail in Vermont. The fat comma does not help.

LATE INTERPOLATION

Recall that all methods, on init(), pass through all arguments as key/value pairs in the error object. Except for those parameters reserved by the class API (by convention of leading dash), these are preserved unaltered.

my $err     = Error::Base->new(
                -base   => 'Panic:',
                -type   => 'lost my $foo.',
            );
$err->crash(
            'Help!',
        '$foo'  => 'hat',
    );      # emits 'Panic: lost my hat. Help!'

my $err     = Error::Base->new(
                -base   => 'Sing:',
                '@favs' => [qw/ schnitzel with noodles /],
            );
$err->crash(
        -type   => 'My favorite things are @favs.',
    );      # emits 'Sing: My favorite things are schnitzel with noodles.'

If we want to emit an error including information only available within a given scope we can interpolate it then and there with a double-quoted literal:

open( my $in_fh, '<', $filename )
    or Error::Base->crash("Couldn't open $filename for reading.");

This doesn't work if we want to declare lengthy error text well ahead of time:

my $err     = Error::Base->new(
                -base   => 'Up, Up and Away:',
                -type   => "FCC wardrobe malfunction of $jackson",
            );
sub call_ethel {
    my $jackson     = 'Janet';
    $err->crank;
};                  # won't work; $jackson out of scope for -type

What we need is late interpolation, which Error::Base provides.

When we have the desired value in scope, we simply pass it as the value to a key matching the placeholder $jackson:

my $err     = Error::Base->new(
                -base   => 'Up, Up and Away:',
                -type   => 'FCC wardrobe malfunction of $jackson',
            );
sub call_ethel {
    my $jackson     = 'Janet';
    $err->crank( '$jackson' => \$jackson );
};                  # 'Up, Up and Away: FCC wardrobe malfunction of Janet'

Note that the string passed to new() as the value of -type is now single quoted, which avoids a futile attempt to interpolate immediately. Also, a reference to the variable $jackson is passed as the value of the key '$jackson'. The key is quoted to avoid it being parsed as a variable.

my $err     = Error::Base->new(
                    'right here in $cities[$i].',
                -base   => 'Our $children{'who'} gonna have',
                -type   => q/$self->{'_what'}/,
            );
$err->crash(
        _what       => 'trouble:'
        '%children' => { who => 'children\'s children' },
        '@cities'   => [ 'Metropolis', 'River City', 'Gotham City' ],
        '$i'        => 1,
    );          # you're the Music Man

You may use scalar or array placeholders, signifying them with the usual sigils. Although you pass a reference, use the appropriate $, @ or % sigil to lead the corresponding key. As a convenience, you may pass simple scalars directly. (It's syntactically ugly to pass a reference to a literal scalar.) Any value that is not a reference will be late-interpolated directly; anything else will be deferenced (once).

This is Perlish interpolation, only delayed. You can interpolate escape sequences and anything else you would in a double-quoted string. You can pass a reference to a package variable; but do so against a simple key such as '$aryref'.

As a further convenience, you may interpolate a value from the error object itself. In the previous example, -type is defined as '$self->{_what}' (please note the single quotes). And also, _what is defined as 'trouble:'. When late-interpolated, -type expands to 'trouble:'. Note that Error::Base has no idea what you have called your error object (perhaps '$err'); use the placeholder '$self' at all times.

Don't forget to store your value against the appropriate key! This implementation of this feature does not peek into your pad. You may not receive an 'uninitialized' warning if a value is missing. However, no late interpolation will be attempted if no keys are stored, prefixed with $, @ or %. The literal sigil will be printed. So if you don't like this feature, don't use it.

RESULTS

Soon, I'll write accessor methods for all of these. For now, rough it.

-msg

scalar string default: 'Undefined error'

The error message, expanded, without -prepend or backtrace. An empty message is not allowed; if none is provided by any means, 'Undefined error' emits.

-lines

array of strings

The formatted error message, fully expanded, including backtrace.

-frames

array of hashrefs

The raw stack frames used to compose the backtrace.

SUBCLASSING

use base 'Error::Base';
sub init{
    my $self    = shift;
    _munge_my_args(@_);
    $self->SUPER::init(@_);
    return $self;
};

While useful standing alone, Error::Base is written to be subclassed, if you so desire. Perhaps the most useful method to subclass may be init(). You might also subclass crash(), crank(), or cuss() if you want to do something first:

use base 'Error::Base';
sub crash{
    my $self    = _fuss(@_);
    $self->a_kiss_before_dying();
    die $self;
};

The author hopes that most users will not be driven to subclassing but if you do so, successfully or not, please be so kind as to notify.

SEE ALSO

Many error-related modules are available on CPAN. Some do bizarre things.

Error is self-deprecated in its own POD as "black magic"; which recommends Exception::Class instead.

Exception installs a $SIG{__DIE__} handler that converts text passed to die into an exception object. It permits environment variables and setting global state; and implements a try syntax. This module may be closest in spirit to Error::Base. For some reason, I can't persuade cpan to find it.

Carp is well-known and indeed, does a full backtrace with confess(). The better-known carp() may be a bit too clever and in any case, the dump is not formatted to my taste. The module is full of global variable settings. It's not object-oriented and an error object can't easily be pre-created.

The pack leader seems to be Exception::Class. Error::Base differs most strongly in that it has a shorter learning curve (since it does much less); confines itself to error message emission (catching errors is another job); and does a full stack backtrace dump by default. Less code may also be required for simple tasks.

To really catch errors, I like Test::Trap ('block eval on steroids'). It has a few shortcomings but is extremely powerful. I don't see why its use should be confined to testing.

The line between emitting a message and catching it is blurred in many related modules. I did not want a jack-in-the-box object that phoned home if it was thrown under a full moon. The only clever part of an Error::Base object is that it stringifies.

It may be true to say that many error modules seem to expect to be caught. I usually expect my errors to cause all execution to come to a fatal, non-recoverable crash. Oh, yes; I agree it's sometimes needful to catch such errors, especially during testing. But if you're regularly throwing and catching, the term 'exception' may be appropriate but perhaps not 'error'.

INSTALLATION

This module is installed using Module::Build.

DIAGNOSTICS

This module emits error messages for you; it is hoped you won't encounter any from within itself. If you do see one of these errors, kindly report to RT so maintainer can take action. Thank you for helping.

Error::Base internal error: excessive backtrace:

Attempted to capture too many frames of backtrace. You probably mis-set -top, rational values of which are perhaps 0..9.

Error::Base internal error: unpaired args:

You do not have to pass paired arguments to most public methods. Perhaps you passed an odd number of args to a private method.

Error::Base internal error: undefined local list separator:

init() sets $self->{'-$"'} = q{ } by default; you may also set it to another value. If you want your message substrings tightly joined, set $self->{'-$"'} = q{}; don't undefine it.

Error::Base internal error: bad reftype:

You attempted to late-interpolate a reference other than to a scalar, array, or hash. Don't pass such references as values to any key with the wrong sigil.

CONFIGURATION AND ENVIRONMENT

Error::Base requires no configuration files or environment variables.

DEPENDENCIES

There are no non-core dependencies.

version 0.94 # Perl extension for Version Objects

overload # Overload Perl operations

Scalar::Util # General-utility scalar subroutines

This module should work with any version of perl 5.8.8 and up.

INCOMPATIBILITIES

None known.

BUGS AND LIMITATIONS

This is a very early release. Reports will be warmly welcomed.

Please report any bugs or feature requests to bug-error-base@rt.cpan.org, or through the web interface at http://rt.cpan.org.

THANKS

Grateful acknowledgement deserved by AMBRUS for coherent API suggestions. Any failure to grasp them is mine.

AUTHOR

Xiong Changnian <xiong@cpan.org>

LICENSE

Copyright (C) 2011 Xiong Changnian <xiong@cpan.org>

This library and its contents are released under Artistic License 2.0:

http://www.opensource.org/licenses/artistic-license-2.0.php