File Coverage

blib/lib/Throwable/X.pm
Criterion Covered Total %
statement 14 14 100.0
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 20 20 100.0


line stmt bran cond sub pod time code
1             package Throwable::X 0.008;
2 1     1   443767 use Moose::Role;
  1         4073  
  1         3  
3             # ABSTRACT: useful eXtra behavior for Throwable exceptions
4              
5             #pod =head1 SYNOPSIS
6             #pod
7             #pod Write an exception class:
8             #pod
9             #pod package X::BadValue;
10             #pod use Moose;
11             #pod
12             #pod with qw(Throwable::X StackTrace::Auto);
13             #pod
14             #pod use Throwable::X -all; # to get the Payload helper
15             #pod
16             #pod sub x_tags { qw(value) }
17             #pod
18             #pod # What bad value were we given?
19             #pod has given_value => (
20             #pod is => 'ro',
21             #pod required => 1,
22             #pod traits => [ Payload ],
23             #pod );
24             #pod
25             #pod # What was the value supposed to be used for?
26             #pod has given_for => (
27             #pod is => 'ro',
28             #pod isa => 'Str',
29             #pod traits => [ Payload ],
30             #pod );
31             #pod
32             #pod Throw the exception when you need to:
33             #pod
34             #pod X::BadValue->throw({
35             #pod ident => 'bad filename',
36             #pod tags => [ qw(filename) ],
37             #pod public => 1,
38             #pod message => "invalid filename %{given_value}s for %{given_for}s",
39             #pod given_value => $input,
40             #pod given_for => 'user home directory',
41             #pod });
42             #pod
43             #pod ...and when catching:
44             #pod
45             #pod } catch {
46             #pod my $error = $_;
47             #pod
48             #pod if ($error->does('Throwable::X') and $error->is_public) {
49             #pod
50             #pod # Prints something like:
51             #pod # invalid filename \usr\local\src for user home directory
52             #pod
53             #pod print $error->message, "\n\n", $error->stack_trace->as_string;
54             #pod }
55             #pod }
56             #pod
57             #pod =head1 DESCRIPTION
58             #pod
59             #pod Throwable::X is a collection of behavior for writing exceptions. It's meant to
60             #pod provide:
61             #pod
62             #pod =for :list
63             #pod * means by which exceptions can be identified without string parsing
64             #pod * a structure that can be serialized and reconstituted in other environments
65             #pod * maximum composability by dividing features into individual roles
66             #pod
67             #pod Throwable::X composes the following roles. Each one is documented, but an
68             #pod overview of the features is also provided below so you don't need to hop around
69             #pod in a half dozen roles to understand how to benefit from Throwable::X.
70             #pod
71             #pod =for :list
72             #pod * L<Throwable>
73             #pod * L<Role::HasPayload::Merged>
74             #pod * L<Role::HasMessage::Errf>
75             #pod * L<Role::Identifiable::HasIdent>
76             #pod * L<Role::Identifiable::HasTags>
77             #pod
78             #pod Note that this list does I<not> include L<StackTrace::Auto>. Building a stack
79             #pod isn't needed in all scenarios, so if you want your exceptions to automatically
80             #pod capture a stack trace, compose StackTrace::Auto when building your exception
81             #pod classes.
82             #pod
83             #pod =head2 Features for Identification
84             #pod
85             #pod Every Throwable::X exception has a required C<ident> attribute that contains a
86             #pod one-line string with printable characters in it. Ideally, the ident doesn't
87             #pod try to describe everything about the error, but serves as a unique identifier
88             #pod for the kind of exception being thrown. Exception handlers looking for
89             #pod specific exceptions can then check the ident for known values. It can also be
90             #pod used for refinement or localization of the message format, described below.
91             #pod This feature is provided by L<Role::Identifiable::HasIdent>.
92             #pod
93             #pod For less specific identification of classes of exceptions, the exception can be
94             #pod checked for what roles it performs with C<does>, or its tags can be checked
95             #pod with C<has_tag>. All the tags reported by the C<x_tags> methods of every class
96             #pod and role in the exception's composition are present, as well as per-instance
97             #pod tags provided when the exception was thrown. Tags as simple strings consisting
98             #pod of letters, numbers, and dashes. This feature is provided by
99             #pod L<Role::Identifiable::HasTags>.
100             #pod
101             #pod Throwable::X exceptions also have a message, which (unlike the C<ident>) is
102             #pod meant to be a human-readable string describing precisely what happened. The
103             #pod C<message> argument given when throwing an exception uses a C<sprintf>-like
104             #pod dialect implemented (and described) by L<String::Errf>. It picks data out of
105             #pod the C<payload> (described below) to produce a filled-in string when the
106             #pod C<message> method is called. (The L<synopsis|/SYNOPSIS> above gives a very
107             #pod simple example of how this works, but the String::Errf documentation is more
108             #pod useful, generally.) This feature is provided by
109             #pod L<Role::HasMessage::Errf>.
110             #pod
111             #pod =head2 Features for Serialization
112             #pod
113             #pod The C<payload> method returns a hashref containing the name and value of every
114             #pod attribute with the trait L<Role::HasPayload::Meta::Attribute::Payload>, merged
115             #pod with the hashref (if any) provided as the C<payload> entry to the constructor.
116             #pod There's nothing more to it than that. It's used by the message formatting
117             #pod facility descibed above, and is also useful for serializing exceptions.
118             #pod Assuming no complex values are present in the payload, the structure below
119             #pod should be easy to serialize and use in another program, for example in a web
120             #pod browser receiving a serialized Throwable::X via JSON in response to an
121             #pod XMLHTTPRequest.
122             #pod
123             #pod {
124             #pod ident => $err->ident,
125             #pod message => $err->message_fmt,
126             #pod tags => [ $err->tags ],
127             #pod payload => $err->payload,
128             #pod }
129             #pod
130             #pod There is no specific code present to support doing this, yet.
131             #pod
132             #pod The C<payload> method is implemented by L<Role::HasPayload::Merged>.
133             #pod
134             #pod The C<public> attribute, checked with the C<is_public> method, is meant to
135             #pod indicate whether the exception's message is safe to display to end users or
136             #pod send across the wire to remote clients.
137             #pod
138             #pod =head2 Features for Convenience
139             #pod
140             #pod The C<throw> (or C<new>) method on a Throwable::X exception class can be passed
141             #pod a single string, in which case it will be used as the exception's C<ident>.
142             #pod This is (of course) only useful if no other attribute of the exception is
143             #pod required. This feature is provided by L<MooseX::OneArgNew>.
144             #pod
145             #pod =cut
146              
147 1     1   4785 use Throwable::X::Types;
  1         3  
  1         34  
148              
149 1     1   379 use namespace::clean -except => 'meta';
  1         4919  
  1         5  
150              
151             # Does this belong elsewhere? -- rjbs, 2010-10-18
152 1         7 use Sub::Exporter -setup => {
153             exports => { Payload => \'__payload' },
154 1     1   297 };
  1         2  
155 1     1   4737 sub __payload { sub { 'Role::HasPayload::Meta::Attribute::Payload' } }
  1     1   189  
156              
157             with(
158             'Throwable',
159             'Role::HasPayload::Merged',
160             'Role::Identifiable::HasIdent',
161             'Role::Identifiable::HasTags',
162              
163             'Role::HasMessage::Errf' => {
164             default => sub { $_[0]->ident },
165             lazy => 1,
166             },
167              
168             'MooseX::OneArgNew' => {
169             type => 'Throwable::X::_VisibleStr',
170             init_arg => 'ident',
171             },
172             );
173              
174             # Can't do this because we can't +attr in roles. Can't use methods with type,
175             # because methods are too late to parameterize roles. Would rather not add
176             # MXRP as a prereq to all the subroles. -- rjbs, 2010-10-28
177             # has '+ident' => (isa => 'Throwable::X::_Ident');
178             # has '+message_fmt' => (isa => 'Throwable::X::_VisibleStr');
179              
180             has is_public => (
181             is => 'ro',
182             isa => 'Bool',
183             init_arg => 'public',
184             default => 0,
185             );
186              
187             1;
188              
189             __END__
190              
191             =pod
192              
193             =encoding UTF-8
194              
195             =head1 NAME
196              
197             Throwable::X - useful eXtra behavior for Throwable exceptions
198              
199             =head1 VERSION
200              
201             version 0.008
202              
203             =head1 SYNOPSIS
204              
205             Write an exception class:
206              
207             package X::BadValue;
208             use Moose;
209              
210             with qw(Throwable::X StackTrace::Auto);
211              
212             use Throwable::X -all; # to get the Payload helper
213              
214             sub x_tags { qw(value) }
215              
216             # What bad value were we given?
217             has given_value => (
218             is => 'ro',
219             required => 1,
220             traits => [ Payload ],
221             );
222              
223             # What was the value supposed to be used for?
224             has given_for => (
225             is => 'ro',
226             isa => 'Str',
227             traits => [ Payload ],
228             );
229              
230             Throw the exception when you need to:
231              
232             X::BadValue->throw({
233             ident => 'bad filename',
234             tags => [ qw(filename) ],
235             public => 1,
236             message => "invalid filename %{given_value}s for %{given_for}s",
237             given_value => $input,
238             given_for => 'user home directory',
239             });
240              
241             ...and when catching:
242              
243             } catch {
244             my $error = $_;
245              
246             if ($error->does('Throwable::X') and $error->is_public) {
247              
248             # Prints something like:
249             # invalid filename \usr\local\src for user home directory
250              
251             print $error->message, "\n\n", $error->stack_trace->as_string;
252             }
253             }
254              
255             =head1 DESCRIPTION
256              
257             Throwable::X is a collection of behavior for writing exceptions. It's meant to
258             provide:
259              
260             =over 4
261              
262             =item *
263              
264             means by which exceptions can be identified without string parsing
265              
266             =item *
267              
268             a structure that can be serialized and reconstituted in other environments
269              
270             =item *
271              
272             maximum composability by dividing features into individual roles
273              
274             =back
275              
276             Throwable::X composes the following roles. Each one is documented, but an
277             overview of the features is also provided below so you don't need to hop around
278             in a half dozen roles to understand how to benefit from Throwable::X.
279              
280             =over 4
281              
282             =item *
283              
284             L<Throwable>
285              
286             =item *
287              
288             L<Role::HasPayload::Merged>
289              
290             =item *
291              
292             L<Role::HasMessage::Errf>
293              
294             =item *
295              
296             L<Role::Identifiable::HasIdent>
297              
298             =item *
299              
300             L<Role::Identifiable::HasTags>
301              
302             =back
303              
304             Note that this list does I<not> include L<StackTrace::Auto>. Building a stack
305             isn't needed in all scenarios, so if you want your exceptions to automatically
306             capture a stack trace, compose StackTrace::Auto when building your exception
307             classes.
308              
309             =head2 Features for Identification
310              
311             Every Throwable::X exception has a required C<ident> attribute that contains a
312             one-line string with printable characters in it. Ideally, the ident doesn't
313             try to describe everything about the error, but serves as a unique identifier
314             for the kind of exception being thrown. Exception handlers looking for
315             specific exceptions can then check the ident for known values. It can also be
316             used for refinement or localization of the message format, described below.
317             This feature is provided by L<Role::Identifiable::HasIdent>.
318              
319             For less specific identification of classes of exceptions, the exception can be
320             checked for what roles it performs with C<does>, or its tags can be checked
321             with C<has_tag>. All the tags reported by the C<x_tags> methods of every class
322             and role in the exception's composition are present, as well as per-instance
323             tags provided when the exception was thrown. Tags as simple strings consisting
324             of letters, numbers, and dashes. This feature is provided by
325             L<Role::Identifiable::HasTags>.
326              
327             Throwable::X exceptions also have a message, which (unlike the C<ident>) is
328             meant to be a human-readable string describing precisely what happened. The
329             C<message> argument given when throwing an exception uses a C<sprintf>-like
330             dialect implemented (and described) by L<String::Errf>. It picks data out of
331             the C<payload> (described below) to produce a filled-in string when the
332             C<message> method is called. (The L<synopsis|/SYNOPSIS> above gives a very
333             simple example of how this works, but the String::Errf documentation is more
334             useful, generally.) This feature is provided by
335             L<Role::HasMessage::Errf>.
336              
337             =head2 Features for Serialization
338              
339             The C<payload> method returns a hashref containing the name and value of every
340             attribute with the trait L<Role::HasPayload::Meta::Attribute::Payload>, merged
341             with the hashref (if any) provided as the C<payload> entry to the constructor.
342             There's nothing more to it than that. It's used by the message formatting
343             facility descibed above, and is also useful for serializing exceptions.
344             Assuming no complex values are present in the payload, the structure below
345             should be easy to serialize and use in another program, for example in a web
346             browser receiving a serialized Throwable::X via JSON in response to an
347             XMLHTTPRequest.
348              
349             {
350             ident => $err->ident,
351             message => $err->message_fmt,
352             tags => [ $err->tags ],
353             payload => $err->payload,
354             }
355              
356             There is no specific code present to support doing this, yet.
357              
358             The C<payload> method is implemented by L<Role::HasPayload::Merged>.
359              
360             The C<public> attribute, checked with the C<is_public> method, is meant to
361             indicate whether the exception's message is safe to display to end users or
362             send across the wire to remote clients.
363              
364             =head2 Features for Convenience
365              
366             The C<throw> (or C<new>) method on a Throwable::X exception class can be passed
367             a single string, in which case it will be used as the exception's C<ident>.
368             This is (of course) only useful if no other attribute of the exception is
369             required. This feature is provided by L<MooseX::OneArgNew>.
370              
371             =head1 PERL VERSION
372              
373             This library should run on perls released even a long time ago. It should work
374             on any version of perl released in the last five years.
375              
376             Although it may work on older versions of perl, no guarantee is made that the
377             minimum required version will not be increased. The version may be increased
378             for any reason, and there is no promise that patches will be accepted to lower
379             the minimum required perl.
380              
381             =head1 AUTHOR
382              
383             Ricardo Signes <cpan@semiotic.systems>
384              
385             =head1 CONTRIBUTOR
386              
387             =for stopwords Ricardo Signes
388              
389             Ricardo Signes <rjbs@semiotic.systems>
390              
391             =head1 COPYRIGHT AND LICENSE
392              
393             This software is copyright (c) 2022 by Ricardo Signes.
394              
395             This is free software; you can redistribute it and/or modify it under
396             the same terms as the Perl 5 programming language system itself.
397              
398             =cut