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.026';
4 5     5   113672 use strict;
  5         11  
  5         122  
5 5     5   24 use warnings;
  5         10  
  5         150  
6              
7 5     5   2733 use HTTP::Throwable::Variant;
  5         14  
  5         32  
8              
9 5     5   3925 use Sub::Exporter::Util ();
  5         80821  
  5         278  
10 5         31 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   41 };
  5         10  
16 5     5   1863 use Module::Runtime;
  5         10  
  5         38  
17              
18              
19             sub throw {
20 57     57 0 328745 my $factory = shift;
21 57 50       222 my $ident = (! ref $_[0]) ? shift(@_) : undef;
22 57   50     195 my $arg = shift || {};
23              
24 57         212 $factory->class_for($ident, $arg)->throw($arg);
25             }
26              
27             sub new_exception {
28 3     3 0 6437 my $factory = shift;
29 3 50       12 my $ident = (! ref $_[0]) ? shift(@_) : undef;
30 3   100     17 my $arg = shift || {};
31              
32 3         13 $factory->class_for($ident, $arg)->new($arg);
33             }
34              
35             sub core_roles {
36 60     60 1 293 return qw(
37             HTTP::Throwable
38             );
39             }
40              
41             sub extra_roles {
42 33     33 1 278 return qw(
43             HTTP::Throwable::Role::TextBody
44             );
45             }
46              
47             sub roles_for_ident {
48 58     58 1 122 my ($self, $ident) = @_;
49              
50 58 50       156 Carp::confess("roles_for_ident called with undefined ident")
51             unless defined $ident;
52              
53 58 50       157 Carp::confess("roles_for_ident called with empty ident string")
54             unless length $ident;
55              
56 58         245 return "HTTP::Throwable::Role::Status::$ident";
57             }
58              
59             sub roles_for_no_ident {
60 2     2 1 4 my ($self, $ident) = @_;
61              
62 2         7 return qw(
63             HTTP::Throwable::Role::Generic
64             HTTP::Throwable::Role::BoringText
65             );
66             }
67              
68 60     60 1 259 sub base_class { () }
69              
70             sub class_for {
71 60     60 0 111 my ($self, $ident) = @_;
72              
73 60         100 my @roles;
74 60 100       135 if (defined $ident) {
75 58 100       224 if ($ident =~ /\A[0-9]{3}\z/) {
76 4         24 @roles = $self->roles_for_status_code($ident);
77             } else {
78 54         206 @roles = $self->roles_for_ident($ident);
79             }
80             } else {
81 2         10 @roles = $self->roles_for_no_ident;
82             }
83              
84 60         364 Module::Runtime::use_module($_) for @roles;
85              
86 60         1577 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         123091 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              
125             500 => 'InternalServerError',
126             501 => 'NotImplemented',
127             502 => 'BadGateway',
128             503 => 'Status::ServiceUnavailable',
129             504 => 'GatewayTimeout',
130             505 => 'HTTPVersionNotSupported',
131             );
132              
133             sub roles_for_status_code {
134 4     4 1 9 my ($self, $code) = @_;
135              
136 4         12 my $ident = $lookup{$code};
137 4         17 return $self->roles_for_ident($ident);
138             }
139              
140             1;
141              
142             =pod
143              
144             =encoding UTF-8
145              
146             =head1 NAME
147              
148             HTTP::Throwable::Factory - a factory that throws HTTP::Throwables for you
149              
150             =head1 VERSION
151              
152             version 0.026
153              
154             =head1 OVERVIEW
155              
156             L is a role that makes it easy to build exceptions that, once
157             thrown, can be turned into L-style HTTP responses. Because
158             HTTP::Throwable and all its related roles are, well, roles, they can't be
159             instantiated or thrown directly. Instead, they must be built into classes
160             first. HTTP::Throwable::Factory takes care of this job, building classes out
161             of the roles you need for the exception you want to throw.
162              
163             You can use the factory to either I or I an exception of either a
164             I or I type. Building and throwing are very similar -- the
165             only difference is whether or not the newly built object is thrown or returned.
166             To throw an exception, use the C method on the factory. To return it,
167             use the C method. In the examples below, we'll just use
168             C.
169              
170             To throw a generic exception -- one where you must specify the status code and
171             reason, and any other headers -- you pass C a hashref of arguments that
172             will be passed to the exception class's constructor.
173              
174             HTTP::Throwable::Factory->throw({
175             status_code => 301,
176             reason => 'Moved Permanently',
177             additional_headers => [
178             Location => '/new',
179             ],
180             });
181              
182             To throw a specific type of exception, include an exception type identifier,
183             like this:
184              
185             HTTP::Throwable::Factory->throw(MovedPermanently => { location => '/new' });
186              
187             The type identifier is (by default) the end of a role name in the form
188             C. The full list of such included
189             roles is given in L.
190              
191             =head2 Exports
192              
193             You can import routines called C and C that work
194             like the C and C methods, respectively, but are not
195             called as methods. For example:
196              
197             use HTTP::Throwable::Factory 'http_exception';
198              
199             builder {
200             mount '/old' => http_exception('Gone'),
201             };
202              
203             =head1 SUBCLASSING
204              
205             One of the big benefits of using HTTP::Throwable::Factory is that you can
206             subclass it to change the kind of exceptions it provides.
207              
208             If you subclass it, you can change its behavior by overriding the following
209             methods -- provided in the order of likelihood that you'd want to override
210             them, most likely first.
211              
212             =head2 extra_roles
213              
214             This method returns a list of role names that will be included in any class
215             built by the factory. By default, it includes only
216             L to satisfy HTTP::Throwable's requirements
217             for methods needed to build a body.
218              
219             This is the method you're most likely to override in a subclass.
220              
221             =head2 roles_for_ident
222              
223             =head2 roles_for_status_code
224              
225             =head2 roles_for_no_ident
226              
227             This methods convert the exception type identifier to a role to apply. For
228             example, if you call:
229              
230             Factory->throw(NotFound => { ... })
231              
232             ...then C is called with "NotFound" as its argument.
233             C is used if the string is three ASCII digits.
234              
235             If C is called I a type identifier, C is
236             called.
237              
238             By default, C returns C
239             and C returns L and
240             L.
241              
242             =head2 base_class
243              
244             This is the base class that will be subclassed and into which all the roles
245             will be composed. By default, it is L, the universal base Moo
246             class.
247              
248             =head2 core_roles
249              
250             This method returns the roles that are expected to be applied to every
251             HTTP::Throwable exception. This method's results might change over time, and
252             you are encouraged I> to alter it.
253              
254             =head1 AUTHORS
255              
256             =over 4
257              
258             =item *
259              
260             Stevan Little
261              
262             =item *
263              
264             Ricardo Signes
265              
266             =back
267              
268             =head1 COPYRIGHT AND LICENSE
269              
270             This software is copyright (c) 2011 by Infinity Interactive, Inc..
271              
272             This is free software; you can redistribute it and/or modify it under
273             the same terms as the Perl 5 programming language system itself.
274              
275             =cut
276              
277             __END__