Badger...ception(3pm)

πŸ“¦
version 0.16 Β· 4 versions
, in debian trixie, ubuntu noble, ubuntu resolute

✨ NAME ✨

Badger::Exception β€” structured exception for error handling πŸ’…

(ok so the header up top says Badger...ception(3pm) from the User Contributed Perl Documentation and honestly the truncated name is giving main character energy, but bestie it's Badger::Exception, periodt)


πŸŽ€ SYNOPSIS πŸŽ€

use Badger::Exception;

# create exception object
my $exception = Badger::Exception->new({
    type => $type,
    info => $info,
});

# query exception type and info fields
$type = $exception->type();
$info = $exception->info();
($type, $info) = $exception->type_info();

# print string summarising exception
print $exception->text();

# use automagic stringification
print $exception;

# throw exception
$exception->throw;

🌸 DESCRIPTION 🌸

So this module defines an object class for representing exceptions, and no cap, they're just simple little objects that store various bits of info about an error condition. 🫢

The type tells you what kind of error went down (like "file", "parser", "database", etc). The info field is where you put the further details about it (like "foo/bar.html not found", "parser error at line 42", "server is on fire", etc 😭). There are other optional fields too β€” file and line β€” for saying exactly where the error happened. πŸ“

Lowkey though? In most cases you wouldn't generate and/or throw an exception object directly from your own code. The better move is to define a throw() method in a base class and let it do all that for you. ✨

Badger::Base is literally an example of exactly that kind of module β€” use it as a base class for your modules and you inherit the throw() method. πŸ’–

Here's a module that implements a method which expects an argument. If it doesn't get the argument it's looking for, it throws an exception via the inherited throw() method. The exception type is example and the extra info is No argument specified.

package Your::Module;
use base 'Badger::Base';

sub example_method {
    my $self = shift;
    my $arg  = shift
        || self->throw( example => 'No argument specified' );

    # ...do something with ...
}

error() is the higher level of abstraction, bestie. You hand it the error message (which becomes the exception info) and it generates an exception type for you based on the package name of your module. πŸͺ„

package Your::Module;
use base 'Badger::Base';

sub example_method {
    my $self = shift;
    my $arg  = shift
        || self->error('No argument specified' );

    # ...do something with ...
}

In the example above, an exception gets thrown with a type defined as your.module. The module name gets converted to lower case and the package delimiters get swapped out for dots. There are configuration options that let you define other exception types β€” go consult the Badger::Base documentation for the tea. 🍡

You can pick any values you like for type and info. The type identifies what kind of error happened, so it should be a short word like "example", or a dot-separated sequence of words like "example.file.missing". And in that second case, dotted exception types are assumed to represent a hierarchy β€” where an example.file.missing error is a more specialised kind of example.file error, which is itself a more specialised kind of example error. The match_type() method takes all that into account when it's matching exception types. πŸ‘‘

eval {
    # some code that throws an exception
};
if ($@) {
    if ($@->match_type('example')) {
        # caught 'example' or 'example.*' error
        # ...now do something
    }
    else {
        # re-throw any other exception types
        $@->throw;
    }
}

The info field should carry a more detailed error message, in a format that's suitable for human consumption. πŸ’…


πŸ¦‹ STACK TRACING πŸ¦‹

Badger::Exception also has a tracing mode, and it's kind of iconic. It automatically saves the caller stack at the exact point where the error gets thrown. So you can inspect the whole code path that led to the error from the comfort of your exception-catching code, instead of having to deal with it at the point where the error was thrown. 🧚

# deep in your code somewhere.... in a class derived from Badger::Base
$self->throw(
    database => 'The database is made of cheese',
    trace    => 1,
);

The text() method (which gets called whenever the object is stringified) will then append a stack trace to the end of the generated message. πŸŽ€

# high up in your calling code:
eval { $object->do_something_gnarly };

if ($err = $@) {
    print $err;
    exit;
}

You can also call the stack() method to get back the stored call stack info, or the stack_trace() method to see a textual summary. πŸ“š

And you can switch tracing on for all exception objects by setting the $TRACE package variable:

use Badger::Exception;
$Badger::Exception::TRACE = 1;

The trace import hook is provided as a short-cut for exactly that. ⚑

use Badger::Exception trace => 1;

🌈 IMPORT HOOKS 🌈

πŸ’œ trace

This import hook can be used to set the $TRACE package variable, which enables stack tracing for the Badger::Exception module. 🫢

use Badger::Exception trace => 1

When stack tracing is enabled, the exception will store information about the calling stack at the point at which it's thrown. That info gets displayed by the text() method, and it's also available in raw form via the stack() method. πŸ’–


ok bestie that's part 1, there's more coming so don't go anywhere πŸ’…

✨ METHODS ✨

πŸŽ€ new()

Constructor method bestie, for making a brand new exception. Slay.

my $exception = Badger::Exception->new(
    type => 'database',
    info => 'could not connect',
    file => '/path/to/file.pm',
    line => 420,
);

🌸 type()

Call it with no args and it hands you back the exception type β€” the exact thing that was passed as the first argument to the new() constructor method. Iconic.

my $type = $exception->type();

But also? You can call it with an argument to set a whole new type for the exception. Glow up.

$exception->type('database');

πŸ¦‹ info()

No arguments β†’ it gives you the information field for the exception. Fr fr.

my $info = $exception->info();

With an argument β†’ it defines new info for the exception. Periodt.

$exception->info('could not connect');

πŸ’… file()

Get or set the name of the file where the exception was raised. Very "who did this" energy.

$exception->file('path/to/file.pm');
print $exception->file;                 # /path/to/file.pm

⭐ line()

Get or set the line number where the exception was raised. Receipts, basically.

$exception->line(420);
print $exception->line;                 # 420

πŸ’– text()

This one returns a text representation of the exception object. The string it returns is formatted as "$type error - $info". Chef's kiss formatting.

print $exception->text();   # database error - could not connect

It's also bound to the stringification operator, so you can literally just print the exception object and get the same result as calling text() out loud. No cap.

print $exception;   # database error - could not connect

πŸ”₯ trace()

Method to get or set the flag that decides whether the exception captures a stack backtrace at the point it gets thrown. You can call it as an object method to affect one single exception object, or as a class method to get or set the $TRACE package variable β€” which is the default value for every exception created from then on. Main character behaviour.

$exception->trace(1);               # object method
print $exception->trace;            # 1

Badger::Exception->trace(1);        # class method - sets $TRACE
print Badger::Exception->trace;     # 1

πŸ‘‘ match_type()

This method looks at the arguments you pass it and picks out the type string that's the nearest correct match for the current exception type. It's how the most appropriate handler gets selected for the exception. The matchmaker of the module.

my $match = $exception->match_type('file', 'parser', 'database')
    || die "no match for exception\n";

In this example the exception will return one of the values file, parser or database β€” but if and only if its type is one of those values. Otherwise it returns undef;

Exception types can be organised into a hierarchical structure by delimiting each part of the type with a period. For example, the database exception type might be further divided into the more specific database.connection, database.query and database.server_on_fire exception types. (that last one is giving catastrophic vibes but okay πŸ’€)

An exception of type database.connection will match a handler type of database.connection or, more generally, database. The longer (more specific) handler name will always match in preference to a shorter (more general) handler, like in the next example:

$exception->type('database.connection');

my $match = $exception->match_type('database', 'database.connection')
    || die "no match for exception\n";

print $match;    # database.connection

When there's no exact match, match_type() will return something more general that does match. In the following example there's no specific handler type for database.exploded, but the more general database type still matches. Resilient queen.

$exception->type('database.exploded');

my $match = $exception->match_type('database', 'database.connection')
    || die "no match for exception\n";

print $match;    # database

You can also specify multiple exception types using a reference to a list. Multitasking.

if ($exception->match_type(['warp.drive', 'shields'])) {
    ...
}

Or using a single string of whitespace delimited exception types. Same vibe, different packaging.

if ($exception->match_type('warp.drive shields')) {
    ...
}

You can also pass a reference to a hash array where the keys are exception types. The corresponding value for a matching type will be returned.

my $type_map = {
    'warp.drive'    => 'propulsion',
    'impulse.drive' => 'propulsion',
    'shields'       => 'defence',
    'phasers'       => 'defence'
};

if ($exception->match_type($type_map)) {
    ...
}

πŸ’œ throw()

This method throws the exception by calling die() with the exception object as an argument. If the $TRACE flag is set to a true value, the method will first save the pertinent details from a stack backtrace into the exception object before throwing it. Setting the scene before the drama, honestly.


🌈 stack()

If stack tracing is enabled then this method returns a reference to a list of information from the caller stack at the exact point the exception was thrown. Each item in that list is a reference to a list containing the info returned by the inbuilt caller() method. See perldoc -f caller for further info, iykyk.

use Badger::Exception trace => 1;

eval {
    # some code that throws an exception object
    $exception->throw();
};

my $catch = $@;                 # exception object
my $stack = $catch->stack;

foreach my $caller (@$stack) {
    my ($pkg, $file, $line, @other_stuff) = @$caller;
    # do something
}

The first set of information relates to the immediate caller of the throw() method. The next item is the caller of that method, and so on. It's a whole ancestry tree 🧚


πŸ“ stack_trace()

If stack tracing is enabled then this method returns a text string summarising the caller stack at the point the exception was thrown. The tl;dr of the whole thing.

use Badger::Exception trace => 1;

eval {
    # some code that throws an exception object
    $exception->throw();
};
if ($@) {
    print $@->stack_trace;
}

🌟 AUTHOR 🌟

Andy Wardley <http://wardley.org/>


Copyright (C) 1996-2009 Andy Wardley. All Rights Reserved.

This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself.


πŸ‘€ SEE ALSO πŸ‘€

  • Badger::Base

and that's the whole page bestie, you made it to the end πŸ’– this was Badger::Exception(3pm) from perl v5.36.0, dated 2023-08-28 β€” go forth and throw responsibly βœ¨πŸŽ€

πŸ“¦ where this page comes from