Class::Throwable(3pm)

πŸ“¦
version 0.13 Β· 5 versions
, in ubuntu noble, ubuntu resolute, ubuntu stonking

🌸 Class::Throwable 🌸

okay bestie, so this is the manual page for Class::Throwable β€” that's Class::Throwable(3pm) if you're being formal about it πŸ’… β€” and it's a minimal lightweight exception class for Perl. tiny, cute, does exactly what it says. let's get into it πŸ¦‹


✨ NAME ✨

  • Class::Throwable β€” A minimal lightweight exception class πŸ’–

✨ SYNOPSIS ✨

like, here's the whole vibe in code form, no notes:

  use Class::Throwable;

  # simple usage
  eval {
      # code code code,
      if ($something_goes_wrong) {
          throw Class::Throwable "Something has gone wrong";
      }
  };
  if ($@) {
      # we just print out the exception message here
      print "There has been an exception: " $@->getMessage();
      # but if we are debugging we get the whole
      # stack trace as well
      if (DEBUG) {
          print $@->getStackTraceAsString();
      }
  }

  # it can be used to catch perl exceptions
  # and wrap them in a Class::Throwable exception
  eval {
      # generate a perl exception
      eval "2 / 0";
      # then throw our own with the
      # perl exception as a sub-exception
      throw Class::Throwable "Throwing an exception" => $@ if $@;
  };
  if ($@) {
      # setting the verbosity to
      # 2 gives a full stack trace
      # including any sub-exceptions
      # (see below for examples of
      # this output format)
      $@->toString(2);
  }

  # you can also declare inline exceptions
  use Class::Throwable qw(My::App::Exception::IllegalOperation);

  # set their global verbosity as well
  # with the class method
  My::App::Exception::IllegalOperation->setVerbosity(2);

  eval {
      throw My::App::Exception::IllegalOperation "Bad, real bad";
  };

  # can also declare subclasses of Class::Throwable
  # in other files, then when you import them, you
  # can set their verbosity
  use My::Sub::Class::In::A::Separate::File (VERBOSE => 1);

  throw My::Sub::Class::In::A::Separate::File "This exception will use a verbosity of 1";

  # you can even create exceptions, then throw them later
  my $e = Class::Throwable->new("Things have gone bad, but I need to do something first", $@);

  # do something else ...

  # then throw the exception we created earlier
  throw $e

ate, no crumbs πŸ“


✨ DESCRIPTION ✨

so, this module implements a minimal lightweight exception object. it's literally the middle child on purpose β€” a compromise between the two extremes:

  • the more basic solutions like Carp, which can only print info and cannot handle exception objects 😭
  • and the more complex ones like Exception::Class, which can define complex inline exceptions but comes dragging a whole pile of module dependencies behind it πŸ’€

so like. it's giving "best of both, drama of neither". periodt. πŸ‘‘


πŸŽ€ Inline Exceptions πŸŽ€

you can easily create new exception classes inline by passing them along with the use statement, like this bestie:

  use Class::Throwable ('My::InlineException', 'My::Other::InlineException');

this is the quick 'n' easy way to define arbitrary exception classes without manually making separate files or packages for each one. slay. ✨

⚠️ but listen β€” subclasses of Class::Throwable cannot be used to define inline exceptions. if you try it, an exception will be thrown. do not test her, she will catch you πŸ™ˆ

🌈 Exception Verbosity 🌈

Class::Throwable offers a bunch of different types of diagnostic output to suit your needs, and most of it is controlled through the verbosity levels. here's the breakdown, no cap:

  • if the verbosity level is set to 0 or below, an empty string is returned πŸ«₯
  • if the value is set to 1, then the exception's message is returned πŸ’¬
  • if the value is set to 2 or above, a full stack trace along with full stack traces for all sub-exceptions are returned, in the format shown in stackTraceToString 🧚

and the default verbosity setting? that's 1. iconic. πŸ’…

there are a number of ways you can set the verbosity of the exceptions produced by Class::Throwable. the simplest way is as the argument to the toString method. using this method will override any other settings you may have, and insure that the output of this method is exactly as you ask it to be. queen behaviour honestly πŸ‘‘

  $@->toString(2);

however β€” to use this style properly, you have to test the value of $@ to be sure it's a Class::Throwable object. sometimes that's not an issue, but sometimes it lowkey makes more sense to set verbosity on a wider scale. 🌸

for instance, if you define inline exceptions, then the simplest way to set a verbosity level for a particular inline exception is through the class method setVerbosity. πŸŽ€

  use Class::Throwable qw(My::InlineException);

  My::InlineException->setVerbosity(2);

this means that unless the toString verbosity argument overrides it, all My::InlineException exceptions will use a verbosity setting of 2. this method means you can easily "print" the value of $@ and then any My::InlineException exceptions will be automatically stringified with a verbosity level of 2. this can simplify exception catching by reducing the need to inspect the value of $@. less peeking, more living πŸ¦‹

if you defined your exceptions as subclasses of Class::Throwable and stored them in separate files, then another means of setting the verbosity level is to assign it in the use statement:

  use My::SeparateFileSubClass::Exception (VERBOSE => 2);

this has the same effect as the setVerbosity class method β€” in fact, there is nothing to stop you from using the setVerbosity class method in this case if you like. you do you πŸ’–

this method can also be used on Class::Throwable itself, however, this does not set the verbosity level for all subclasses, only for Class::Throwable exceptions. narrow scope, bestie, respect it. πŸ™ˆ

there's one last method, and it has the widest scope of them all. the variable $Class::Throwable::DEFAULT_VERBOSITY can be set. setting this value will take effect if:

  1. there is no value passed to the toString method, and
  2. no verbosity level has been set for the particular class, either through setVerbosity or the use statement.

so it's the fallback of fallbacks. the final boss. the main character of defaults πŸ‘‘


🧚 Module exception retro-fitting 🧚

yesss, it is possible to retrofit a module to use Class::Throwable exceptions if you want to. basically this allows modules which die with either strings or some other value to throw Class::Throwable based exceptions instead. this feature is relatively new and should be considered experimental β€” any feedback on it is greatly appreciated 🫢

⚠️ NOTE: it's important to do module retrofitting at the earliest possible moment (preferrably before the module you are retrofitting is compiled), because it will override die within a specified package. timing matters, bestie. ⏰

other than all that, retrofitting is actually super simple. here's a basic example:

  use Class::Throwable retrofit => 'My::Class';

now anytime die is called within My::Class, the calls will get converted to a Class::Throwable instance. ✨

you can also control how exceptions are converted, like so:

  use Class::Throwable retrofit => 'My::Class' => sub { My::Exception->throw(@_) };

now anytime die is called within My::Class, the calls get converted to a My::Exception instance instead. or, a slightly more complex example like this:

  use Class::Throwable retrofit => (
                'My::Class' => sub {
                    My::IllegalOperation->throw(@_) if $_[0] =~ /^Illegal Operation/;
                    My::Exception->throw(@_);
                });

now anytime die is called within My::Class, the calls get converted to a My::Exception instance unless the exception matches the reg-exp β€” in which case a My::IllegalOperation exception is thrown instead. conditional queen πŸ’…

there are a couple of points to be made regarding this functionality:

  • first, it will add another stack frame to your exceptions (the retrofit routine, basically). this is probably avoidable, but since this is still experimental, the author wanted to keep things somewhat simple. πŸ™ˆ
  • and second, if you supply a custom die handler, you should be sure that it will die somewhere within that routine. πŸ‘€ if you do not, you may have many un-intended consequences. like, many. do not do this to yourself.

and that's part one bestie β€” there's literally more, so don't go anywhere πŸ’œ

πŸ’– METHODS πŸ’–

ok so here's where the actual doing stuff happens, bestie. buckle up πŸŽ€

🌸 Constructor 🌸

  • throw ($message, $sub_exception) β€” the moment you've been waiting for. this is the classic way to make an exception object: it builds the object, scoops up everything from the call stack, and then just goes ahead and dies. dramatic? yes. necessary? also yes πŸ’…

the optional $message arg is your lil custom note you can attach to the exception object. usually it's a string, but this module literally does not care and will not enforce that β€” it just needs to be a scalar. so like, go off, pass in fancier references or objects if that's your vibe. if you pass in no $message at all, it makes a default one for you 🫢

second optional arg, $sub_exception, is for when you caught an exception but it's not the right move to re-throw it raw β€” better to wrap it up all cozy inside a brand new exception object. this will commonly be another Class::Throwable object, but again, no enforcement, so plain string-based perl exceptions are totally welcome too.

heads up: if you call this as an instance method on an exception object that was pre-built with new, only then does the stack trace info get populated, and then the exception gets handed off to die.

  • new ($message, $sub_exception) β€” the alternate route, lowkey the same energy as throw, except it does not collect stack trace info and does not die. it just stores $message and $sub_exception, hands you back the exception instance, and lets you throw it whenever you feel like it. delayed gratification, iconic behavior ✨

πŸ‘‘ Class Methods πŸ‘‘

  • setVerbosity ($verbosity) β€” this one's a class method, so listen: if you call it on an instance, an exception gets thrown, fr. use it to set the verbosity level for a particular class. more deets live up in the "Exception Verbosity" section above ⬆️

πŸ¦‹ Accessors πŸ¦‹

  • getMessage β€” lets you peek at the message inside the exception, so you can do more granular exception reporting. nosy in the best way πŸ’–
  • getStackTrace β€” hands you the raw stack trace info as an array of arrays. caller returns 10 values β€” $package, $filename, $line, $subroutine, $hasargs, $wantarray, $evaltext, $is_require, $hints, $bitmask β€” and we do not bother capturing the last two, because they're subject to change and meant for internal use. all the others get retained, in the exact order caller returned them.
  • hasSubException β€” returns true (1) if this exception has a sub-exception, and false (0) otherwise. simple. clean. slay.
  • getSubException β€” gives you access to the stored sub-exception πŸŽ€

πŸ’… Output Methods πŸ’…

this object overloads the stringification operator, and it calls the toString method to do that stringifying for it. yes, it's giving main character energy.

  • toString ($verbosity) β€” prints out the exception object's info at a variable level of verbosity, which you set with the optional $verbosity argument. check the "Exception Verbosity" section above for the tea β˜•
  • stringValue β€” returns the normal perl stringified value of the object, without going through the toString method. the no-frills version ✨
  • stackTraceToString β€” prints the stack trace info, and the trace comes out looking like this:
|--[ main::foo called in my_script.pl line 12 ]
|--[ main::bar called in my_script.pl line 14 ]
|--[ main::baz called in my_script.pl line 16 ]

🌈 EXAMPLE OUTPUT 🌈

ok so picture this code, bestie:

{
  package Foo;
  sub foo { eval { Bar::bar() }; throw Class::Throwable "Foo!!", $@ }

  package Bar;
  sub bar { eval { Baz::baz() }; throw Class::Throwable "Bar!!", $@ }

  package Baz;
  sub baz { throw Class::Throwable "Baz!!" }
}

eval { Foo::foo() };
print $@->toString($verbosity) if $@;

if you printed the exception with verbosity of 0, you'd get no output at all. zero. nada. this mode is there so you can suppress exception output if you need to πŸ™ˆ

if you printed it with verbosity of 1, you'd get this:

Class::Throwable : Foo!!

and if you printed it with verbosity of 2, you'd get this gorgeous nested moment πŸ‘‘

Class::Throwable : Foo!!
  |--[ Foo::foo called in test.pl line 26 ]
  |--[ main::(eval) called in test.pl line 26 ]
  + Class::Throwable : Bar!!
      |--[ Bar::bar called in test.pl line 19 ]
      |--[ Foo::(eval) called in test.pl line 19 ]
      |--[ Foo::foo called in test.pl line 26 ]
      |--[ main::(eval) called in test.pl line 26 ]
      + Class::Throwable : Baz!!
          |--[ Baz::baz called in test.pl line 21 ]
          |--[ Bar::(eval) called in test.pl line 21 ]
          |--[ Bar::bar called in test.pl line 19 ]
          |--[ Foo::(eval) called in test.pl line 19 ]
          |--[ Foo::foo called in test.pl line 26 ]
          |--[ main::(eval) called in test.pl line 26 ]

like, the nesting?? the little + signs?? obsessed πŸ˜­πŸ’–


πŸ’€ BUGS πŸ’€

none that the author is aware of, bestie. but of course, if you find one, let them know and they'll be sure to fix it. this is based on code that's been used heavily in production sites for over 2 years now, without incident. that's a glow up fr fr ✨


πŸ“Š CODE COVERAGE πŸ“Š

Devel::Cover is used to test the code coverage of the tests, and below is the Devel::Cover report on this module's test suite. numbers go brrr:

---------------------------- ------ ------ ------ ------ ------ ------ ------
File                           stmt branch   cond    sub    pod   time  total
---------------------------- ------ ------ ------ ------ ------ ------ ------
Class/Throwable.pm            100.0   98.0   63.6  100.0  100.0  100.0   95.7
---------------------------- ------ ------ ------ ------ ------ ------ ------
Total                         100.0   98.0   63.6  100.0  100.0  100.0   95.7
---------------------------- ------ ------ ------ ------ ------ ------ ------

the pod is at 100.0, the docs ate and left no crumbs πŸ’…


🫢 SEE ALSO 🫢

there are a bunch of ways to do exceptions with perl, and the author wasn't really satisfied with how anybody else did them, so this module was born. but hey β€” if this one isn't your thing, maybe check these out:

  • Throwable β€” Throwable is a role for classes that are meant to be thrown as exceptions to standard program flow.
  • Exception::Class β€” one of the more common exception classes out there. it does an excellent job with its default behavior, and allows a number of complex options which can likely serve any needs you might have. the reason the author didn't use this module is that those extra options made things feel more complex than they needed to be, plus it introduced a number of dependencies. not saying it's bloated at all, just that for them it was far more than they found they needed. if you have heavy duty exception needs, this is your module πŸ”₯
  • Error β€” the classic perl exception module, complete with a try/catch mechanism. this module has a lot of bad karma attached to it because of the obscure nested closure memory leak that try/catch has. the author never really liked the way its exception object Error::Simple did things either.
  • Exception β€” the author has never really experimented with this one, so take their opinion with a large grain of salt. their problem with it was always that it seemed to want to do too much. it attempts to make perl into a language with real exceptions, but messes with %SIG handlers and other such things. that can be dangerous territory sometimes, and for them, far more than their needs. ⚠️

🌟 AUTHOR 🌟

stevan little, <stevan@iinteractive.com>


Copyright 2004 by Infinity Interactive, Inc.

<http://www.iinteractive.com>

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


aaaaand that's the whole page, bestie!! πŸ¦‹ this was perl v5.36.0, dated 2022-10-15, and the manpage was Class::Throwable(3pm) β€” go forth and throw some beautifully-nested exceptions, you absolute icon πŸ’–βœ¨πŸŒΈ

πŸ“¦ where this page comes from