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;
2             our $AUTHORITY = 'cpan:STEVAN';
3             $HTTP::Throwable::Factory::VERSION = '0.027';
4 5     5   298939 use strict;
  5         49  
  5         139  
5 5     5   28 use warnings;
  5         10  
  5         120  
6              
7 5     5   2049 use HTTP::Throwable::Variant;
  5         12  
  5         31  
8              
9 5     5   2363 use Sub::Exporter::Util ();
  5         71137  
  5         280  
10 5         21 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   42 };
  5         10  
16 5     5   2179 use Module::Runtime;
  5         11  
  5         36  
17              
18              
19             sub throw {
20 57     57 0 449258 my $factory = shift;
21 57 50       304 my $ident = (! ref $_[0]) ? shift(@_) : undef;
22 57   50     219 my $arg = shift || {};
23              
24 57         287 $factory->class_for($ident, $arg)->throw($arg);
25             }
26              
27             sub new_exception {
28 3     3 0 9159 my $factory = shift;
29 3 50       19 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 274 return qw(
37             HTTP::Throwable
38             );
39             }
40              
41             sub extra_roles {
42 33     33 1 264 return qw(
43             HTTP::Throwable::Role::TextBody
44             );
45             }
46              
47             sub roles_for_ident {
48 58     58 1 179 my ($self, $ident) = @_;
49              
50 58 50       163 Carp::confess("roles_for_ident called with undefined ident")
51             unless defined $ident;
52              
53 58 50       192 Carp::confess("roles_for_ident called with empty ident string")
54             unless length $ident;
55              
56 58         270 return "HTTP::Throwable::Role::Status::$ident";
57             }
58              
59             sub roles_for_no_ident {
60 2     2 1 7 my ($self, $ident) = @_;
61              
62 2         6 return qw(
63             HTTP::Throwable::Role::Generic
64             HTTP::Throwable::Role::BoringText
65             );
66             }
67              
68 60     60 1 274 sub base_class { () }
69              
70             sub class_for {
71 60     60 0 235 my ($self, $ident) = @_;
72              
73 60         124 my @roles;
74 60 100       187 if (defined $ident) {
75 58 100       306 if ($ident =~ /\A[0-9]{3}\z/) {
76 4         73 @roles = $self->roles_for_status_code($ident);
77             } else {
78 54         262 @roles = $self->roles_for_ident($ident);
79             }
80             } else {
81 2         10 @roles = $self->roles_for_no_ident;
82             }
83              
84 60         644 Module::Runtime::use_module($_) for @roles;
85              
86 60         2004 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         156045 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 => 'Status::ServiceUnavailable',
130             504 => 'GatewayTimeout',
131             505 => 'HTTPVersionNotSupported',
132             );
133              
134             sub roles_for_status_code {
135 4     4 1 15 my ($self, $code) = @_;
136              
137 4         13 my $ident = $lookup{$code};
138 4         16 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.027
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 SUBCLASSING
205              
206             One of the big benefits of using HTTP::Throwable::Factory is that you can
207             subclass it to change the kind of exceptions it provides.
208              
209             If you subclass it, you can change its behavior by overriding the following
210             methods -- provided in the order of likelihood that you'd want to override
211             them, most likely first.
212              
213             =head2 extra_roles
214              
215             This method returns a list of role names that will be included in any class
216             built by the factory. By default, it includes only
217             L to satisfy HTTP::Throwable's requirements
218             for methods needed to build a body.
219              
220             This is the method you're most likely to override in a subclass.
221              
222             =head2 roles_for_ident
223              
224             =head2 roles_for_status_code
225              
226             =head2 roles_for_no_ident
227              
228             This methods convert the exception type identifier to a role to apply. For
229             example, if you call:
230              
231             Factory->throw(NotFound => { ... })
232              
233             ...then C is called with "NotFound" as its argument.
234             C is used if the string is three ASCII digits.
235              
236             If C is called I a type identifier, C is
237             called.
238              
239             By default, C returns C
240             and C returns L and
241             L.
242              
243             =head2 base_class
244              
245             This is the base class that will be subclassed and into which all the roles
246             will be composed. By default, it is L, the universal base Moo
247             class.
248              
249             =head2 core_roles
250              
251             This method returns the roles that are expected to be applied to every
252             HTTP::Throwable exception. This method's results might change over time, and
253             you are encouraged I> to alter it.
254              
255             =head1 AUTHORS
256              
257             =over 4
258              
259             =item *
260              
261             Stevan Little
262              
263             =item *
264              
265             Ricardo Signes
266              
267             =back
268              
269             =head1 COPYRIGHT AND LICENSE
270              
271             This software is copyright (c) 2011 by Infinity Interactive, Inc.
272              
273             This is free software; you can redistribute it and/or modify it under
274             the same terms as the Perl 5 programming language system itself.
275              
276             =cut
277              
278             __END__