File Coverage

blib/lib/HTTP/Throwable/Factory.pm
Criterion Covered Total %
statement 48 48 100.0
branch 8 12 66.6
condition 3 4 75.0
subroutine 15 15 100.0
pod 6 9 66.6
total 80 88 90.9


line stmt bran cond sub pod time code
1             package HTTP::Throwable::Factory 0.028;
2             our $AUTHORITY = 'cpan:STEVAN';
3              
4 5     5   310537 use strict;
  5         58  
  5         141  
5 5     5   26 use warnings;
  5         9  
  5         120  
6              
7 5     5   2234 use HTTP::Throwable::Variant;
  5         14  
  5         31  
8              
9 5     5   2536 use Sub::Exporter::Util ();
  5         85171  
  5         251  
10 5         18 use Sub::Exporter -setup => {
11             exports => [
12             http_throw => Sub::Exporter::Util::curry_method('throw'),
13             http_exception => Sub::Exporter::Util::curry_method('new_exception'),
14             ],
15 5     5   39 };
  5         10  
16 5     5   2127 use Module::Runtime;
  5         15  
  5         34  
17              
18              
19             sub throw {
20 57     57 0 446327 my $factory = shift;
21 57 50       219 my $ident = (! ref $_[0]) ? shift(@_) : undef;
22 57   50     180 my $arg = shift || {};
23              
24 57         255 $factory->class_for($ident, $arg)->throw($arg);
25             }
26              
27             sub new_exception {
28 3     3 0 9629 my $factory = shift;
29 3 50       25 my $ident = (! ref $_[0]) ? shift(@_) : undef;
30 3   100     14 my $arg = shift || {};
31              
32 3         14 $factory->class_for($ident, $arg)->new($arg);
33             }
34              
35             sub core_roles {
36 60     60 1 272 return qw(
37             HTTP::Throwable
38             );
39             }
40              
41             sub extra_roles {
42 33     33 1 228 return qw(
43             HTTP::Throwable::Role::TextBody
44             );
45             }
46              
47             sub roles_for_ident {
48 58     58 1 148 my ($self, $ident) = @_;
49              
50 58 50       183 Carp::confess("roles_for_ident called with undefined ident")
51             unless defined $ident;
52              
53 58 50       138 Carp::confess("roles_for_ident called with empty ident string")
54             unless length $ident;
55              
56 58         232 return "HTTP::Throwable::Role::Status::$ident";
57             }
58              
59             sub roles_for_no_ident {
60 2     2 1 6 my ($self, $ident) = @_;
61              
62 2         8 return qw(
63             HTTP::Throwable::Role::Generic
64             HTTP::Throwable::Role::BoringText
65             );
66             }
67              
68 60     60 1 282 sub base_class { () }
69              
70             sub class_for {
71 60     60 0 139 my ($self, $ident) = @_;
72              
73 60         145 my @roles;
74 60 100       154 if (defined $ident) {
75 58 100       259 if ($ident =~ /\A[0-9]{3}\z/) {
76 4         23 @roles = $self->roles_for_status_code($ident);
77             } else {
78 54         196 @roles = $self->roles_for_ident($ident);
79             }
80             } else {
81 2         11 @roles = $self->roles_for_no_ident;
82             }
83              
84 60         306 Module::Runtime::use_module($_) for @roles;
85              
86 60         1807 my $class = HTTP::Throwable::Variant->build_variant(
87             superclasses => [ $self->base_class ],
88             roles => [
89             $self->core_roles,
90             $self->extra_roles,
91             @roles
92             ],
93             );
94              
95 60         153818 return $class;
96             }
97              
98             my %lookup = (
99             300 => 'MultipleChoices',
100             301 => 'MovedPermanently',
101             302 => 'Found',
102             303 => 'SeeOther',
103             304 => 'NotModified',
104             305 => 'UseProxy',
105             307 => 'TemporaryRedirect',
106              
107             400 => 'BadRequest',
108             401 => 'Unauthorized',
109             403 => 'Forbidden',
110             404 => 'NotFound',
111             405 => 'MethodNotAllowed',
112             406 => 'NotAcceptable',
113             407 => 'ProxyAuthenticationRequired',
114             408 => 'RequestTimeout',
115             409 => 'Conflict',
116             410 => 'Gone',
117             411 => 'LengthRequired',
118             412 => 'PreconditionFailed',
119             413 => 'RequestEntityTooLarge',
120             414 => 'RequestURITooLong',
121             415 => 'UnsupportedMediaType',
122             416 => 'RequestedRangeNotSatisfiable',
123             417 => 'ExpectationFailed',
124             418 => 'ImATeapot',
125              
126             500 => 'InternalServerError',
127             501 => 'NotImplemented',
128             502 => 'BadGateway',
129             503 => 'ServiceUnavailable',
130             504 => 'GatewayTimeout',
131             505 => 'HTTPVersionNotSupported',
132             );
133              
134             sub roles_for_status_code {
135 4     4 1 13 my ($self, $code) = @_;
136              
137 4         14 my $ident = $lookup{$code};
138 4         10 return $self->roles_for_ident($ident);
139             }
140              
141             1;
142              
143             =pod
144              
145             =encoding UTF-8
146              
147             =head1 NAME
148              
149             HTTP::Throwable::Factory - a factory that throws HTTP::Throwables for you
150              
151             =head1 VERSION
152              
153             version 0.028
154              
155             =head1 OVERVIEW
156              
157             L is a role that makes it easy to build exceptions that, once
158             thrown, can be turned into L-style HTTP responses. Because
159             HTTP::Throwable and all its related roles are, well, roles, they can't be
160             instantiated or thrown directly. Instead, they must be built into classes
161             first. HTTP::Throwable::Factory takes care of this job, building classes out
162             of the roles you need for the exception you want to throw.
163              
164             You can use the factory to either I or I an exception of either a
165             I or I type. Building and throwing are very similar -- the
166             only difference is whether or not the newly built object is thrown or returned.
167             To throw an exception, use the C method on the factory. To return it,
168             use the C method. In the examples below, we'll just use
169             C.
170              
171             To throw a generic exception -- one where you must specify the status code and
172             reason, and any other headers -- you pass C a hashref of arguments that
173             will be passed to the exception class's constructor.
174              
175             HTTP::Throwable::Factory->throw({
176             status_code => 301,
177             reason => 'Moved Permanently',
178             additional_headers => [
179             Location => '/new',
180             ],
181             });
182              
183             To throw a specific type of exception, include an exception type identifier,
184             like this:
185              
186             HTTP::Throwable::Factory->throw(MovedPermanently => { location => '/new' });
187              
188             The type identifier is (by default) the end of a role name in the form
189             C. The full list of such included
190             roles is given in L.
191              
192             =head2 Exports
193              
194             You can import routines called C and C that work
195             like the C and C methods, respectively, but are not
196             called as methods. For example:
197              
198             use HTTP::Throwable::Factory 'http_exception';
199              
200             builder {
201             mount '/old' => http_exception('Gone'),
202             };
203              
204             =head1 PERL VERSION
205              
206             This library should run on perls released even a long time ago. It should work
207             on any version of perl released in the last five years.
208              
209             Although it may work on older versions of perl, no guarantee is made that the
210             minimum required version will not be increased. The version may be increased
211             for any reason, and there is no promise that patches will be accepted to lower
212             the minimum required perl.
213              
214             =head1 SUBCLASSING
215              
216             One of the big benefits of using HTTP::Throwable::Factory is that you can
217             subclass it to change the kind of exceptions it provides.
218              
219             If you subclass it, you can change its behavior by overriding the following
220             methods -- provided in the order of likelihood that you'd want to override
221             them, most likely first.
222              
223             =head2 extra_roles
224              
225             This method returns a list of role names that will be included in any class
226             built by the factory. By default, it includes only
227             L to satisfy HTTP::Throwable's requirements
228             for methods needed to build a body.
229              
230             This is the method you're most likely to override in a subclass.
231              
232             =head2 roles_for_ident
233              
234             =head2 roles_for_status_code
235              
236             =head2 roles_for_no_ident
237              
238             This methods convert the exception type identifier to a role to apply. For
239             example, if you call:
240              
241             Factory->throw(NotFound => { ... })
242              
243             ...then C is called with "NotFound" as its argument.
244             C is used if the string is three ASCII digits.
245              
246             If C is called I a type identifier, C is
247             called.
248              
249             By default, C returns C
250             and C returns L and
251             L.
252              
253             =head2 base_class
254              
255             This is the base class that will be subclassed and into which all the roles
256             will be composed. By default, it is L, the universal base Moo
257             class.
258              
259             =head2 core_roles
260              
261             This method returns the roles that are expected to be applied to every
262             HTTP::Throwable exception. This method's results might change over time, and
263             you are encouraged I> to alter it.
264              
265             =head1 AUTHORS
266              
267             =over 4
268              
269             =item *
270              
271             Stevan Little
272              
273             =item *
274              
275             Ricardo Signes
276              
277             =back
278              
279             =head1 COPYRIGHT AND LICENSE
280              
281             This software is copyright (c) 2011 by Infinity Interactive, Inc.
282              
283             This is free software; you can redistribute it and/or modify it under
284             the same terms as the Perl 5 programming language system itself.
285              
286             =cut
287              
288             __END__