NAME

Exception::Stringy - a Perl Exceptions module where exceptions are not objects but simple strings.

VERSION

version 0.13

SYNOPSIS

use Exception::Stringy (
    'MyException',
 
    'YetAnotherException' => {
        isa         => 'AnotherException',
    },
 
    'ExceptionWithFields' => {
        isa    => 'YetAnotherException',
        fields => [ 'grandiosity', 'quixotic' ],
        alias  => 'throw_fields',
    },
);

## with Try::Tiny

use Try::Tiny;
 
try {
    # throw an exception
    MyException->throw('I feel funny.');

    # or use an aliase
    throw_fields 'Error message', grandiosity => 1;

    # you can build exception step by step
    my $e = ExceptionWithFields->new("The error message");
    $e->$xfield(quixotic => "some_value");
    $e->$xthrow();

}
catch {
    if ( $_->$xisa('Exception::Stringy') ) {
        warn $_->$xerror, "\n";
    }

    if ( $_->$xisa('ExceptionWithFields') ) {
        if ( $_->$xfield('quixotic') ) {
            handle_quixotic_exception();
        }
        else {
            handle_non_quixotic_exception();
        }
    }
    else {
        $_->$xrethrow;
    }
};
 
# without Try::Tiny
 
eval {
    # ...
    MyException->throw('I feel funny.');
    1;
} or do {
    my $e = $@;
    # .. same as above with $e instead of $_
}

DESCRIPTION

This module allows you to declare exceptions, and provides a simple interface to declare, throw, and interact with them. It can be seen as a light version of Exception::Class, except that there is a catch: exceptions are not objects, they are normal strings, with a pattern that contains properties.

This modules has no external dependancies. It requires Perl 5.8.9 or above.

WHY WOULD YOU WANT SUCH THING ?

Having exceptions be objects is sometimes very annoying. What if some code is calling you, and isn't expecting objects exceptions ? Sometimes string overloading doesn't work. Sometimes, external code tamper with your exception. Consider:

use Exception::Class ('MyException');
use Scalar::Util qw( blessed );
use Try::Tiny;

$SIG{__DIE__} = sub { die "FATAL: $_[0]" };

try {
  MyException->throw("foo");
} catch {
  die "this is not a Exception::Class" unless blessed $_ && $_->isa('Exception::Class');
  if ($_->isa('MyException')) { ... }
};

In this example, the exception thrown is a Exception::Class instance, but it gets forced to a string by the signal handler. When in the catch block, it's not an object anymore, it's a regular string, and the code fails to see that it's was once 'MyException'.

Using Exception::Stringy, exceptions are regular strings, that embed in themselves a small pattern to contain their properties. They can be stringified, concatenated, and tampered with in any way, as long as the pattern isn't removed (it can be moved inside the string though).

As a result, exceptions are more robust, while still retaining all features you'd expect from similar modules like Exception::Class

use Exception::Stringy ('MyException');
use Try::Tiny;

$SIG{__DIE__} = sub { die "FATAL: $_[0]" };

try {
  MyException->throw("foo");
} catch {
  die "this is not a Exception::Stringy" unless $_->$xisa('Exception::Stringy');
  if ($_->$xisa('MyException')) { ... }
};

BASIC USAGE

Registering exception classes

Defining exception classes is done when use'ing Exception::Stringy:

use Exception::Stringy (
  'MyException',
  'ExceptionWithFields' => {
        isa    => 'MyException',
        fields => [ qw(field1 field2) ],
        alias  => 'throw_fields',
  },
);

In the previous code, MyException is a simple exception, with no field, and it simply inherits from Exception::Stringy (all exceptions inherits from it). ExceptionWithFields inherits from MyException, has two fields defined, and throw_fields can be used as a shortcut to throw it.

Here are the details about what can be in the exception definitions:

class name

The keys of the definition's hash are reggular class name string, with an exception: they cannot start with a underscore ( _ ), keys starting with an underscore are reserved for options specification (see "ADVANCED OPTIONS");

isa

Expects a name (Str). If set, the exception will inherit from the given name.

fields

Expects a list of field names (ArrayRef). If set, the exceptions will be able to set/get these fields. Fields values should be short scalars (no references).

alias

Expects a function name (Str). If set, the user will be able to use this name as a class method, as a shortcut. From the example above, throw_fields-(...)> will be equivalent to ExceptionWithFields-throw(...)>

override

Expects a boolean (defaults to false). If set to true, then an already registered exception can be updated.

throwing exceptions

ExceptionWithFields->throw("error message", grandiosity => 42);

catching and checking exceptions

eval { ... 1; } or do {
  my $e = $@;
  if ($e->$xisa('Exception::Stringy')) {
    if ($e->$xisa('ExceptionWithFields')) {
      ...
    } elsif ($e->$xisa('YetAnotherException')) {
      ...
    }
  } else {
    # this works on anything, even objects or bare strings
    e->$xrethrow;
  }
};

CLASS METHODS

raise, throw

# both are exactly the same
ExceptionWithFields->throw("error message", grandiosity => 42);
ExceptionWithFields->raise("error message", grandiosity => 42);

Creates a string exception from the given class, with the error message and fields, then throws the exception. The exception is thrown using croak() from the Carp module.

The error message is always the first argument. If ommited, it'll default to empty string. Optional fields are provided as flat key / value pairs.

new

my $e = ExceptionWithFields->new("error message", grandiosity => 42);

Takes the same arguments as throw() but doesn't throw the exception. Instead, the exception is returned.

registered_fields

my @fields = ExceptionWithFields->registered_fields;

Returns the possible fields that an exception of the given class can have.

registered_exception_classes

my @class_names = Exception::Stringy->registered_exception_classes;

Returns the exceptions classes that have been registered.

METHODS

The syntax is a bit strange, but that's because exceptions are bare strings, and not blessed references, so we have to use a trick to have the arrow syntax working.

By default, the methods are in the x package (mnemonic: eXception) but you can change that by specifying an other _package_prefix (see "ADVANCED OPTIONS" below)

$xthrow(), $xrethrow(), $xraise()

$exception->$xthrow();
$exception->$xrethrow();
$exception->$xraise();

Throws the exception.

$xclass()

my $class = $exception->$xclass();

Returns the exception class name.

$xisa()

if ($exception->$xisa('ExceptionClass')) {... }

Returns true if the class of the given exception -isa()> the class given in parameter.

$xfields()

my @fields = $exception->$xfields();

Returns the list of field names that are in the exception.

$xfield()

my $value = $exception->$xfield('field_name');

$exception->$xfield(field_name => $value);

Set or get the given field. If the value contains one of these forbidden caracters, then it is transparently base64 encoded and decoded.

The list of forbidden caracters are:

:

the semicolon

|

the pipe

\034

\034, the 0x28 seperator ASCII caracter.

$xmessage(), $xerror()

my $text = $exception->$xmessage();
my $text = $exception->$xerror();

$exception->$xmessage("Error message");
$exception->$xerror("Error message");

Set or get the error message of the exception

ADVANCED OPTIONS

use Exception::Stringy (
    MyException => { ... },
    _package_prefix => 'exception',
);

my $e = MyException->new("error message");
say $e->exception::message();

When use-ing this module, you can specify special keys that starts with an underscore ( _ ). They will be interpreted as options. Currently these special keys can be:

_package_prefix

If set, pseudo methods imported in the calling methods use the specified package prefix. By default, it is x, so methods will look like:

$e->$xthrow();
$e->$xfields();
...

But if you specify _package_prefix to be exception_ then imported pseudo methods will be like this:

$e->$exception_throw();
$e->$exception_fields();
...

AUTHOR

Damien Krotkine <dams@cpan.org>

COPYRIGHT AND LICENSE

This software is Copyright (c) 2014 by Damien Krotkine.

This is free software, licensed under:

The Artistic License 2.0 (GPL Compatible)