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.
|