File: Factory.pm

package info (click to toggle)
libhttp-throwable-perl 0.028-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 428 kB
  • sloc: perl: 1,354; makefile: 2; sh: 1
file content (390 lines) | stat: -rw-r--r-- 11,807 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
package HTTP::Throwable::Factory 0.028;
our $AUTHORITY = 'cpan:STEVAN';

use strict;
use warnings;

use HTTP::Throwable::Variant;

use Sub::Exporter::Util ();
use Sub::Exporter -setup => {
  exports => [
    http_throw     => Sub::Exporter::Util::curry_method('throw'),
    http_exception => Sub::Exporter::Util::curry_method('new_exception'),
  ],
};
use Module::Runtime;


sub throw {
    my $factory = shift;
    my $ident   = (! ref $_[0]) ? shift(@_) : undef;
    my $arg     = shift || {};

    $factory->class_for($ident, $arg)->throw($arg);
}

sub new_exception {
    my $factory = shift;
    my $ident   = (! ref $_[0]) ? shift(@_) : undef;
    my $arg     = shift || {};

    $factory->class_for($ident, $arg)->new($arg);
}

sub core_roles {
    return qw(
        HTTP::Throwable
    );
}

sub extra_roles {
    return qw(
        HTTP::Throwable::Role::TextBody
    );
}

sub roles_for_ident {
    my ($self, $ident) = @_;

    Carp::confess("roles_for_ident called with undefined ident")
      unless defined $ident;

    Carp::confess("roles_for_ident called with empty ident string")
      unless length $ident;

    return "HTTP::Throwable::Role::Status::$ident";
}

sub roles_for_no_ident {
    my ($self, $ident) = @_;

    return qw(
        HTTP::Throwable::Role::Generic
        HTTP::Throwable::Role::BoringText
    );
}

sub base_class { () }

sub class_for {
    my ($self, $ident) = @_;

    my @roles;
    if (defined $ident) {
        if ($ident =~ /\A[0-9]{3}\z/) {
          @roles = $self->roles_for_status_code($ident);
        } else {
          @roles = $self->roles_for_ident($ident);
        }
    } else {
        @roles = $self->roles_for_no_ident;
    }

    Module::Runtime::use_module($_) for @roles;

    my $class = HTTP::Throwable::Variant->build_variant(
        superclasses => [ $self->base_class ],
        roles        => [
          $self->core_roles,
          $self->extra_roles,
          @roles
        ],
    );

    return $class;
}

my %lookup = (
    300 => 'MultipleChoices',
    301 => 'MovedPermanently',
    302 => 'Found',
    303 => 'SeeOther',
    304 => 'NotModified',
    305 => 'UseProxy',
    307 => 'TemporaryRedirect',

    400 => 'BadRequest',
    401 => 'Unauthorized',
    403 => 'Forbidden',
    404 => 'NotFound',
    405 => 'MethodNotAllowed',
    406 => 'NotAcceptable',
    407 => 'ProxyAuthenticationRequired',
    408 => 'RequestTimeout',
    409 => 'Conflict',
    410 => 'Gone',
    411 => 'LengthRequired',
    412 => 'PreconditionFailed',
    413 => 'RequestEntityTooLarge',
    414 => 'RequestURITooLong',
    415 => 'UnsupportedMediaType',
    416 => 'RequestedRangeNotSatisfiable',
    417 => 'ExpectationFailed',
    418 => 'ImATeapot',

    500 => 'InternalServerError',
    501 => 'NotImplemented',
    502 => 'BadGateway',
    503 => 'ServiceUnavailable',
    504 => 'GatewayTimeout',
    505 => 'HTTPVersionNotSupported',
);

sub roles_for_status_code {
    my ($self, $code) = @_;

    my $ident = $lookup{$code};
    return $self->roles_for_ident($ident);
}

1;

=pod

=encoding UTF-8

=head1 NAME

HTTP::Throwable::Factory - a factory that throws HTTP::Throwables for you

=head1 VERSION

version 0.028

=head1 OVERVIEW

L<HTTP::Throwable> is a role that makes it easy to build exceptions that, once
thrown, can be turned into L<PSGI>-style HTTP responses.  Because
HTTP::Throwable and all its related roles are, well, roles, they can't be
instantiated or thrown directly.  Instead, they must be built into classes
first.  HTTP::Throwable::Factory takes care of this job, building classes out
of the roles you need for the exception you want to throw.

You can use the factory to either I<build> or I<throw> an exception of either a
I<generic> or I<specific> type.  Building and throwing are very similar -- the
only difference is whether or not the newly built object is thrown or returned.
To throw an exception, use the C<throw> method on the factory.  To return it,
use the C<new_exception> method.  In the examples below, we'll just use
C<throw>.

To throw a generic exception -- one where you must specify the status code and
reason, and any other headers -- you pass C<throw> a hashref of arguments that
will be passed to the exception class's constructor.

  HTTP::Throwable::Factory->throw({
      status_code => 301,
      reason      => 'Moved Permanently',
      additional_headers => [
        Location => '/new',
      ],
  });

To throw a specific type of exception, include an exception type identifier,
like this:

  HTTP::Throwable::Factory->throw(MovedPermanently => { location => '/new' });

The type identifier is (by default) the end of a role name in the form
C<HTTP::Throwable::Role::Status::IDENTIFIER>.  The full list of such included
roles is given in L<the HTTP::Throwable docs|HTTP::Throwable/WELL-KNOWN TYPES>.

=head2 Exports

You can import routines called C<http_throw> and C<http_exception> that work
like the C<throw> and C<new_exception> methods, respectively, but are not
called as methods.  For example:

  use HTTP::Throwable::Factory 'http_exception';

  builder {
      mount '/old' => http_exception('Gone'),
  };

=head1 PERL VERSION

This library should run on perls released even a long time ago.  It should work
on any version of perl released in the last five years.

Although it may work on older versions of perl, no guarantee is made that the
minimum required version will not be increased.  The version may be increased
for any reason, and there is no promise that patches will be accepted to lower
the minimum required perl.

=head1 SUBCLASSING

One of the big benefits of using HTTP::Throwable::Factory is that you can
subclass it to change the kind of exceptions it provides.

If you subclass it, you can change its behavior by overriding the following
methods -- provided in the order of likelihood that you'd want to override
them, most likely first.

=head2 extra_roles

This method returns a list of role names that will be included in any class
built by the factory.  By default, it includes only
L<HTTP::Throwable::Role::TextBody> to satisfy HTTP::Throwable's requirements
for methods needed to build a body.

This is the method you're most likely to override in a subclass.

=head2 roles_for_ident

=head2 roles_for_status_code

=head2 roles_for_no_ident

This methods convert the exception type identifier to a role to apply.  For
example, if you call:

  Factory->throw(NotFound => { ... })

...then C<roles_for_ident> is called with "NotFound" as its argument.
C<roles_for_status_code> is used if the string is three ASCII digits.

If C<throw> is called I<without> a type identifier, C<roles_for_no_ident> is
called.

By default, C<roles_for_ident> returns C<HTTP::Throwable::Role::Status::$ident>
and C<roles_for_no_ident> returns L<HTTP::Throwable::Role::Generic> and
L<HTTP::Throwable::Role::BoringText>.

=head2 base_class

This is the base class that will be subclassed and into which all the roles
will be composed.  By default, it is L<Moo::Object>, the universal base Moo
class.

=head2 core_roles

This method returns the roles that are expected to be applied to every
HTTP::Throwable exception.  This method's results might change over time, and
you are encouraged I<B<not>> to alter it.

=head1 AUTHORS

=over 4

=item *

Stevan Little <stevan.little@iinteractive.com>

=item *

Ricardo Signes <cpan@semiotic.systems>

=back

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2011 by Infinity Interactive, Inc.

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

=cut

__END__

# ABSTRACT: a factory that throws HTTP::Throwables for you

#pod =head1 OVERVIEW
#pod
#pod L<HTTP::Throwable> is a role that makes it easy to build exceptions that, once
#pod thrown, can be turned into L<PSGI>-style HTTP responses.  Because
#pod HTTP::Throwable and all its related roles are, well, roles, they can't be
#pod instantiated or thrown directly.  Instead, they must be built into classes
#pod first.  HTTP::Throwable::Factory takes care of this job, building classes out
#pod of the roles you need for the exception you want to throw.
#pod
#pod You can use the factory to either I<build> or I<throw> an exception of either a
#pod I<generic> or I<specific> type.  Building and throwing are very similar -- the
#pod only difference is whether or not the newly built object is thrown or returned.
#pod To throw an exception, use the C<throw> method on the factory.  To return it,
#pod use the C<new_exception> method.  In the examples below, we'll just use
#pod C<throw>.
#pod
#pod To throw a generic exception -- one where you must specify the status code and
#pod reason, and any other headers -- you pass C<throw> a hashref of arguments that
#pod will be passed to the exception class's constructor.
#pod
#pod   HTTP::Throwable::Factory->throw({
#pod       status_code => 301,
#pod       reason      => 'Moved Permanently',
#pod       additional_headers => [
#pod         Location => '/new',
#pod       ],
#pod   });
#pod
#pod To throw a specific type of exception, include an exception type identifier,
#pod like this:
#pod
#pod   HTTP::Throwable::Factory->throw(MovedPermanently => { location => '/new' });
#pod
#pod The type identifier is (by default) the end of a role name in the form
#pod C<HTTP::Throwable::Role::Status::IDENTIFIER>.  The full list of such included
#pod roles is given in L<the HTTP::Throwable docs|HTTP::Throwable/WELL-KNOWN TYPES>.
#pod
#pod =head2 Exports
#pod
#pod You can import routines called C<http_throw> and C<http_exception> that work
#pod like the C<throw> and C<new_exception> methods, respectively, but are not
#pod called as methods.  For example:
#pod
#pod   use HTTP::Throwable::Factory 'http_exception';
#pod
#pod   builder {
#pod       mount '/old' => http_exception('Gone'),
#pod   };
#pod
#pod =head1 SUBCLASSING
#pod
#pod One of the big benefits of using HTTP::Throwable::Factory is that you can
#pod subclass it to change the kind of exceptions it provides.
#pod
#pod If you subclass it, you can change its behavior by overriding the following
#pod methods -- provided in the order of likelihood that you'd want to override
#pod them, most likely first.
#pod
#pod =head2 extra_roles
#pod
#pod This method returns a list of role names that will be included in any class
#pod built by the factory.  By default, it includes only
#pod L<HTTP::Throwable::Role::TextBody> to satisfy HTTP::Throwable's requirements
#pod for methods needed to build a body.
#pod
#pod This is the method you're most likely to override in a subclass.
#pod
#pod =head2 roles_for_ident
#pod
#pod =head2 roles_for_status_code
#pod
#pod =head2 roles_for_no_ident
#pod
#pod This methods convert the exception type identifier to a role to apply.  For
#pod example, if you call:
#pod
#pod   Factory->throw(NotFound => { ... })
#pod
#pod ...then C<roles_for_ident> is called with "NotFound" as its argument.
#pod C<roles_for_status_code> is used if the string is three ASCII digits.
#pod
#pod If C<throw> is called I<without> a type identifier, C<roles_for_no_ident> is
#pod called.
#pod
#pod By default, C<roles_for_ident> returns C<HTTP::Throwable::Role::Status::$ident>
#pod and C<roles_for_no_ident> returns L<HTTP::Throwable::Role::Generic> and
#pod L<HTTP::Throwable::Role::BoringText>.
#pod
#pod =head2 base_class
#pod
#pod This is the base class that will be subclassed and into which all the roles
#pod will be composed.  By default, it is L<Moo::Object>, the universal base Moo
#pod class.
#pod
#pod =head2 core_roles
#pod
#pod This method returns the roles that are expected to be applied to every
#pod HTTP::Throwable exception.  This method's results might change over time, and
#pod you are encouraged I<B<not>> to alter it.