File Coverage

blib/lib/PONAPI/Exception.pm
Criterion Covered Total %
statement 55 58 94.8
branch 17 20 85.0
condition 8 12 66.6
subroutine 12 12 100.0
pod 2 4 50.0
total 94 106 88.6


line stmt bran cond sub pod time code
1             # ABSTRACT: Exceptions for PONAPI::Server
2             package PONAPI::Exception;
3              
4 8     8   33184 use Moose;
  8         474686  
  8         59  
5 8     8   52010 use Moose::Util qw/find_meta/;
  8         19  
  8         70  
6              
7 8     8   1574 use JSON::XS;
  8         17  
  8         993  
8              
9             sub throw {
10 20     20 0 2466 my $class_or_obj = shift;
11 20 50       820 die ( blessed $class_or_obj ? $class_or_obj : $class_or_obj->new(@_) );
12             }
13              
14             use overload
15 8         76 q{""} => 'as_string',
16 8     8   45 fallback => 1;
  8         17  
17              
18             has message => (
19             is => 'ro',
20             isa => 'Str',
21             required => 1,
22             );
23              
24             has status => (
25             is => 'ro',
26             isa => 'Int',
27             default => sub { 400 },
28             );
29              
30             has bad_request_data => (
31             is => 'ro',
32             isa => 'Bool',
33             );
34              
35             has sql_error => (
36             is => 'ro',
37             isa => 'Bool',
38             );
39              
40             has internal => (
41             is => 'ro',
42             isa => 'Bool',
43             );
44              
45             has json_api_version => (
46             is => 'ro',
47             isa => 'Str',
48             default => sub { '1.0' },
49             writer => '_set_json_api_version'
50             );
51              
52             # Picked from Throwable::Error
53             sub as_string {
54 52     52 1 102 my $self = shift;
55 52         1943 return $self->message;
56             }
57              
58             sub as_response {
59 48     48 1 84 my $self = shift;
60              
61 48         1745 my $status = $self->status;
62 48         1686 my $detail = $self->message;
63              
64 48 100       1733 if ( $self->sql_error ) {
    100          
65 3         11 $detail = "SQL error: $detail";
66             }
67             elsif ( $self->bad_request_data ) {
68 7         29 $detail = "Bad request data: $detail";
69             }
70             else {
71 38         55 $status = 500;
72 38 100       305 warn $detail if $detail;
73 38         133 $detail = "A fatal error has occured, please check server logs";
74             }
75              
76 48         1821 return $status, [], +{
77             jsonapi => { version => $self->json_api_version },
78             errors => [ { detail => $detail, status => $status } ],
79             };
80             }
81              
82             sub new_from_exception {
83 47     47 0 115 my ( $class, $e ) = @_;
84              
85 47 100 100     420 return $e if blessed($e) && $e->isa($class);
86              
87 28         111 my %args_for_new = $class->_handle_exception_obj($e);
88              
89 28 100 66     137 unless ( $args_for_new{status} and $args_for_new{message} ) {
90 24         87 %args_for_new = (
91             status => 500,
92             message => '',
93             );
94 24         403 warn "$e";
95             }
96              
97 28         1236 return $class->new(%args_for_new);
98             }
99              
100             sub _handle_exception_obj {
101 28     28   55 my ( $self, $e ) = @_;
102 28 100 66     396 return unless blessed($e) or $e->isa('Moose::Exception');
103              
104 4 100 33     67 if ( $e->isa('Moose::Exception::AttributeIsRequired') ) {
    50          
105 2         87 my $attribute = $e->attribute_name;
106 2         21 return _bad_req( "Parameter `$attribute` is required" );
107             }
108             elsif (
109             $e->isa('Moose::Exception::ValidationFailedForTypeConstraint') or
110             $e->isa('Moose::Exception::ValidationFailedForInlineTypeConstraint')
111             ) {
112 2         116 my $class = find_meta( $e->class_name );
113 2         157 my $attribute = $class->get_attribute( $e->attribute_name );
114 2         230 my $value_nice = JSON::XS->new->allow_nonref->utf8->canonical->encode( $e->value );
115              
116 2 50       49 if ( !$attribute ) {
117 0         0 my $attr = $e->attribute_name;
118 0         0 return _bad_req( "Parameter `$attr` got an expected data type: $value_nice" );
119             }
120              
121 2         14 my $attribute_name = $attribute->name;
122 2         12 my $type_name = _moose_type_to_nice_description( $attribute->{isa} );
123              
124 2         14 return _bad_req( "Parameter `$attribute_name` expected $type_name, but got a $value_nice" );
125             }
126              
127 0         0 return;
128             }
129              
130             sub _bad_req {
131             return (
132 4     4   34 message => shift,
133             status => 400,
134             bad_request_data => 1,
135             );
136             }
137              
138             # THIS IS NOT COMPLETE, NOR IS IT MEANT TO BE
139             sub _moose_type_to_nice_description {
140 2     2   8 my ($type_name) = @_;
141              
142 2         11 $type_name =~ s/ArrayRef/Collection/g;
143 2         12 $type_name =~ s/HashRef/Resource/g;
144 2         7 $type_name =~ s/Maybe\[(.+)]/null or $1/g;
145 2         6 $type_name =~ s/\|/ or /g;
146              
147 2         7 return $type_name;
148             }
149              
150             __PACKAGE__->meta->make_immutable;
151 8     8   6847 no Moose; 1;
  8         16  
  8         70  
152              
153             __END__
154              
155             =pod
156              
157             =encoding UTF-8
158              
159             =head1 NAME
160              
161             PONAPI::Exception - Exceptions for PONAPI::Server
162              
163             =head1 VERSION
164              
165             version 0.002005
166              
167             =head1 SYNOPSIS
168              
169             use PONAPI::Exception;
170             PONAPI::Exception->throw( message => "Generic exception" );
171             PONAPI::Exception->throw(
172             message => "Explanation for the sql error, maybe $DBI::errstr",
173             sql => 1,
174             );
175             PONAPI::Exception->throw(
176             message => "Data had type `foo` but we wanted `bar`",
177             bad_request_data => 1,
178             );
179              
180             =head1 DESCRIPTION
181              
182             I<PONAPI::Exception> can be used by repositories to signal errors;
183             exceptions thrown this way will be caught by L<the DAO|PONAPI::DAO> and
184             handled gracefully.
185              
186             Different kinds of exceptions can be thrown by changing the arguments
187             to C<throw>; C<sql =E<gt> 1> will throw a SQL exception,
188             C<bad_request_data =E<gt> 1> will throw an exception due to the
189             input data being wrong, and not passing any of those will
190             throw a generic exception.
191              
192             The human-readable C<message> for all of those will end up in the
193             error response returned to the user.
194              
195             =head1 METHODS
196              
197             =head2 message
198              
199             This attribute contains the exception message.
200              
201             =head2 as_string
202              
203             Returns a stringified form of the exception. The object is overloaded
204             to return this if used in string context.
205              
206             =head2 as_response
207              
208             Returns the exception as a 3-element list that may be fed directly
209             to plack as a {json:api} response.
210              
211             $e->as_response; # ( $status, [], { errors => [ { detail => $message } ] } )
212              
213             =head2 json_api_version
214              
215             Defaults to 1.0; only used in C<as_response>.
216              
217             =head2 status
218              
219             HTTP Status code for the exception; in most cases you don't need to
220             set this manually.
221              
222             =end
223              
224             =head1 AUTHORS
225              
226             =over 4
227              
228             =item *
229              
230             Mickey Nasriachi <mickey@cpan.org>
231              
232             =item *
233              
234             Stevan Little <stevan@cpan.org>
235              
236             =item *
237              
238             Brian Fraser <hugmeir@cpan.org>
239              
240             =back
241              
242             =head1 COPYRIGHT AND LICENSE
243              
244             This software is copyright (c) 2016 by Mickey Nasriachi, Stevan Little, Brian Fraser.
245              
246             This is free software; you can redistribute it and/or modify it under
247             the same terms as the Perl 5 programming language system itself.
248              
249             =cut