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   39076 use Moose;
  8         495878  
  8         52  
5 8     8   51468 use Moose::Util qw/find_meta/;
  8         21  
  8         62  
6              
7 8     8   1569 use JSON::XS;
  8         18  
  8         976  
8              
9             sub throw {
10 20     20 0 1750 my $class_or_obj = shift;
11 20 50       808 die ( blessed $class_or_obj ? $class_or_obj : $class_or_obj->new(@_) );
12             }
13              
14             use overload
15 8         74 q{""} => 'as_string',
16 8     8   48 fallback => 1;
  8         13  
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 107 my $self = shift;
55 52         1848 return $self->message;
56             }
57              
58             sub as_response {
59 48     48 1 78 my $self = shift;
60              
61 48         1717 my $status = $self->status;
62 48         1588 my $detail = $self->message;
63              
64 48 100       1643 if ( $self->sql_error ) {
    100          
65 3         10 $detail = "SQL error: $detail";
66             }
67             elsif ( $self->bad_request_data ) {
68 7         25 $detail = "Bad request data: $detail";
69             }
70             else {
71 38         56 $status = 500;
72 38 100       271 warn $detail if $detail;
73 38         126 $detail = "A fatal error has occured, please check server logs";
74             }
75              
76 48         1785 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 127 my ( $class, $e ) = @_;
84              
85 47 100 100     406 return $e if blessed($e) && $e->isa($class);
86              
87 28         108 my %args_for_new = $class->_handle_exception_obj($e);
88              
89 28 100 66     124 unless ( $args_for_new{status} and $args_for_new{message} ) {
90 24         76 %args_for_new = (
91             status => 500,
92             message => '',
93             );
94 24         354 warn "$e";
95             }
96              
97 28         1151 return $class->new(%args_for_new);
98             }
99              
100             sub _handle_exception_obj {
101 28     28   52 my ( $self, $e ) = @_;
102 28 100 66     358 return unless blessed($e) or $e->isa('Moose::Exception');
103              
104 4 100 33     60 if ( $e->isa('Moose::Exception::AttributeIsRequired') ) {
    50          
105 2         100 my $attribute = $e->attribute_name;
106 2         23 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         99 my $class = find_meta( $e->class_name );
113 2         134 my $attribute = $class->get_attribute( $e->attribute_name );
114 2         210 my $value_nice = JSON::XS->new->allow_nonref->utf8->canonical->encode( $e->value );
115              
116 2 50       46 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         11 my $attribute_name = $attribute->name;
122 2         11 my $type_name = _moose_type_to_nice_description( $attribute->{isa} );
123              
124 2         13 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   37 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   5 my ($type_name) = @_;
141              
142 2         10 $type_name =~ s/ArrayRef/Collection/g;
143 2         8 $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         5 return $type_name;
148             }
149              
150             __PACKAGE__->meta->make_immutable;
151 8     8   6800 no Moose; 1;
  8         17  
  8         51  
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.002006
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             =head1 AUTHORS
223              
224             =over 4
225              
226             =item *
227              
228             Mickey Nasriachi <mickey@cpan.org>
229              
230             =item *
231              
232             Stevan Little <stevan@cpan.org>
233              
234             =item *
235              
236             Brian Fraser <hugmeir@cpan.org>
237              
238             =back
239              
240             =head1 COPYRIGHT AND LICENSE
241              
242             This software is copyright (c) 2016 by Mickey Nasriachi, Stevan Little, Brian Fraser.
243              
244             This is free software; you can redistribute it and/or modify it under
245             the same terms as the Perl 5 programming language system itself.
246              
247             =cut