Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add custom error handling #847

Merged
merged 17 commits into from
Sep 7, 2021
21 changes: 18 additions & 3 deletions lib/Zonemaster/Backend/DB/MySQL.pm
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use DBI qw(:utils);
use JSON::PP;

use Zonemaster::Backend::Validator qw( untaint_ipv6_address );
use Zonemaster::Backend::Errors;

with 'Zonemaster::Backend::DB';

Expand Down Expand Up @@ -228,12 +229,14 @@ sub get_test_params {

my ( $params_json ) = $self->dbh->selectrow_array( "SELECT params FROM test_results WHERE hash_id=?", undef, $test_id );

die Zonemaster::Backend::Error::ResourceNotFound->new( message => "Test not found", data => { test_id => $test_id } ) unless defined $params_json;
hannaeko marked this conversation as resolved.
Show resolved Hide resolved

my $result;
eval {
$result = decode_json( $params_json );
};

warn "decoding of params_json failed (testi_id: [$test_id]):".Dumper($params_json) if $@;
die Zonemaster::Backend::Error::JsonError->new( reason => "$@", data => { test_id => $test_id } ) if $@;

return decode_json( $params_json );
}
Expand All @@ -249,8 +252,20 @@ sub test_results {
my $result;
my ( $hrefs ) = $self->dbh->selectall_hashref( "SELECT id, hash_id, CONVERT_TZ(`creation_time`, \@\@session.time_zone, '+00:00') AS creation_time, params, results FROM test_results WHERE hash_id=?", 'hash_id', undef, $test_id );
$result = $hrefs->{$test_id};
$result->{params} = decode_json( $result->{params} );
$result->{results} = decode_json( $result->{results} );

die Zonemaster::Backend::Error::ResourceNotFound->new( message => "Test not found", data => { test_id => $test_id } ) unless defined $result;

eval {
$result->{params} = decode_json( $result->{params} );

if (defined $result->{results}) {
$result->{results} = decode_json( $result->{results} );
} else {
$result->{results} = [];
}
};

die Zonemaster::Backend::Error::JsonError->new( reason => "$@", data => { test_id => $test_id } ) if $@;

return $result;
}
Expand Down
32 changes: 22 additions & 10 deletions lib/Zonemaster/Backend/DB/PostgreSQL.pm
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use Encode;
use JSON::PP;

use Zonemaster::Backend::DB;
use Zonemaster::Backend::Errors;

with 'Zonemaster::Backend::DB';

Expand Down Expand Up @@ -207,8 +208,12 @@ sub get_test_params {

my $dbh = $self->dbh;
my ( $params_json ) = $dbh->selectrow_array( "SELECT params FROM test_results WHERE hash_id=?", undef, $test_id );

die Zonemaster::Backend::Error::ResourceNotFound->new( message => "Test not found", data => { test_id => $test_id } ) unless defined $params_json;

eval { $result = decode_json( encode_utf8( $params_json ) ); };
die "$@ \n" if $@;

die Zonemaster::Backend::Error::JsonError->new( reason => "$@", data => { test_id => $test_id } ) if $@;

return $result;
}
Expand All @@ -222,27 +227,34 @@ sub test_results {
if ( $results );

my $result;
eval {
my ( $hrefs ) = $dbh->selectall_hashref( "SELECT id, hash_id, creation_time at time zone current_setting('TIMEZONE') at time zone 'UTC' as creation_time, params, results FROM test_results WHERE hash_id=?", 'hash_id', undef, $test_id );
$result = $hrefs->{$test_id};
my ( $hrefs ) = $dbh->selectall_hashref( "SELECT id, hash_id, creation_time at time zone current_setting('TIMEZONE') at time zone 'UTC' as creation_time, params, results FROM test_results WHERE hash_id=?", 'hash_id', undef, $test_id );
$result = $hrefs->{$test_id};

die Zonemaster::Backend::Error::ResourceNotFound->new( message => "Test not found", data => { test_id => $test_id } ) unless defined $result;

eval {
# This workaround is needed to properly handle all versions of perl and the DBD::Pg module
# More details in the zonemaster backend issue #570
if (utf8::is_utf8($result->{params}) ) {
$result->{params} = decode_json( encode_utf8($result->{params}) );
$result->{params} = decode_json( encode_utf8($result->{params}) );
}
else {
$result->{params} = decode_json( $result->{params} );
$result->{params} = decode_json( $result->{params} );
}

if (utf8::is_utf8($result->{results} ) ) {
if (defined $result->{results} ) {
if (utf8::is_utf8($result->{results} ) ) {
$result->{results} = decode_json( encode_utf8($result->{results}) );
}
else {
}
else {
$result->{results} = decode_json( $result->{results} );
}
} else {
$result->{results} = [];
}
};
die "$@ \n" if $@;

die Zonemaster::Backend::Error::JsonError->new( reason => "$@", data => { test_id => $test_id }) if $@;

return $result;
}
Expand Down
22 changes: 19 additions & 3 deletions lib/Zonemaster/Backend/DB/SQLite.pm
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ use Digest::MD5 qw(md5_hex);
use JSON::PP;
use Log::Any qw( $log );

use Zonemaster::Backend::Errors;

with 'Zonemaster::Backend::DB';

has 'dbh' => (
Expand Down Expand Up @@ -244,12 +246,14 @@ sub get_test_params {

my ( $params_json ) = $self->dbh->selectrow_array( "SELECT params FROM test_results WHERE hash_id=?", undef, $test_id );

die Zonemaster::Backend::Error::ResourceNotFound->new( message => "Test not found", data => { test_id => $test_id } ) unless defined $params_json;

my $result;
eval {
$result = decode_json( $params_json );
};

$log->warn( "decoding of params_json failed (test_id: [$test_id]):".Dumper($params_json) ) if $@;
die Zonemaster::Backend::Error::JsonError->new( reason => "$@", data => { test_id => $test_id } ) if $@;

return $result;
}
Expand All @@ -265,8 +269,20 @@ sub test_results {
my $result;
my ( $hrefs ) = $self->dbh->selectall_hashref( "SELECT id, hash_id, creation_time, params, results FROM test_results WHERE hash_id=?", 'hash_id', undef, $test_id );
$result = $hrefs->{$test_id};
$result->{params} = decode_json( $result->{params} );
$result->{results} = decode_json( $result->{results} );

die Zonemaster::Backend::Error::ResourceNotFound->new( message => "Test not found", data => { test_id => $test_id } ) unless defined $result;

eval {
$result->{params} = decode_json( $result->{params} );

if (defined $result->{results}) {
$result->{results} = decode_json( $result->{results} );
} else {
$result->{results} = [];
}
};

die Zonemaster::Backend::Error::JsonError->new( reason => "$@", data => { test_id => $test_id } ) if $@;

return $result;
}
Expand Down
160 changes: 160 additions & 0 deletions lib/Zonemaster/Backend/Errors.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package Zonemaster::Backend::Error;
use Moose;
use Data::Dumper;


has 'message' => (
is => 'rw',
hannaeko marked this conversation as resolved.
Show resolved Hide resolved
isa => 'Str',
required => 1,
);

has 'code' => (
is => 'rw',
isa => 'Int',
required => 1,
);

has 'data' => (
is => 'rw',
isa => 'Any',
default => undef,
);

sub as_hash {
my $self = shift;
my $error = {
code => $self->code,
message => $self->message,
error => ref($self),
};
$error->{data} = $self->data if defined $self->data;
return $error;
}

sub as_string {
my $self = shift;
my $str = sprintf "%s (code %d)", $self->message, $self->code;
if (defined $self->data) {
$str .= sprintf "; Context: %s", $self->_data_dump;
}
return $str;
}

sub _data_dump {
my $self = shift;
local $Data::Dumper::Indent = 0;
local $Data::Dumper::Terse = 1;
my $data = Dumper($self->data);
$data =~ s/[\n\r]/ /g;
return $data ;
}

package Zonemaster::Backend::Error::Internal;
use Moose;

extends 'Zonemaster::Backend::Error';

has '+message' => (
default => 'Internal server error'
);

has '+code' => (
default => -32603
);

has 'reason' => (
isa => 'Str',
is => 'rw',
mattias-p marked this conversation as resolved.
Show resolved Hide resolved
initializer => 'reason',
);

has 'method' => (
is => 'rw',
isa => 'Str',
builder => '_build_method'
);

has 'id' => (
is => 'rw',
isa => 'Int',
default => 0,
);

sub _build_method {
my $s = 0;
while (my @c = caller($s)) {
$s ++;
last if $c[3] eq 'Moose::Object::new';
}
my @c = caller($s);
if ($c[3] =~ /^(.*)::handle_exception$/ ) {
@c = caller(++$s);
}

return $c[3];
}

around 'reason' => sub {
my $orig = shift;
my $self = shift;

my ( $value, $setter, $attr ) = @_;

# reader
return $self->$orig if not $value;

# trim new lines
$value =~ s/\n/ /g;
$value =~ s/^\s+|\s+$//g;

# initializer
return $setter->($value) if $setter;

# writer
$self->$orig($value);
};

around 'as_hash' => sub {
my $orig = shift;
my $self = shift;

my $href = $self->$orig;

$href->{exception_id} = $self->id;
$href->{reason} = $self->reason;
$href->{method} = $self->method;
mattias-p marked this conversation as resolved.
Show resolved Hide resolved

return $href;
};


sub as_string {
my $self = shift;
my $str = sprintf "Internal error %0.3d (%s): Unexpected error in the `%s` method: [%s]", $self->id, ref($self), $self->method, $self->reason;
if (defined $self->data) {
$str .= sprintf "; Context: %s", $self->_data_dump;
}
return $str;
}


package Zonemaster::Backend::Error::ResourceNotFound;
use Moose;

extends 'Zonemaster::Backend::Error';

has '+message' => (
default => 'Resource not found'
);

has '+code' => (
default => -32000
);

package Zonemaster::Backend::Error::JsonError;
use Moose;

extends 'Zonemaster::Backend::Error::Internal';

1;
21 changes: 15 additions & 6 deletions lib/Zonemaster/Backend/RPCAPI.pm
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use Zonemaster::Backend;
use Zonemaster::Backend::Config;
use Zonemaster::Backend::Translator;
use Zonemaster::Backend::Validator;
use Log::Any qw( $log );
use Zonemaster::Backend::Errors;

my $zm_validator = Zonemaster::Backend::Validator->new;
my %json_schemas;
Expand Down Expand Up @@ -68,18 +68,27 @@ sub _init_db {
my $dbclass = Zonemaster::Backend::DB->get_db_class( $dbtype );
$self->{db} = $dbclass->from_config( $self->{config} );
};

if ($@) {
handle_exception('_init_db', "Failed to initialize the [$dbtype] database backend module: [$@]", '002');
}
}

sub handle_exception {
my ( $method, $exception, $exception_id ) = @_;
my ( $_method, $exception, $exception_id ) = @_;

if ( !$exception->isa('Zonemaster::Backend::Error') ) {
my $reason = $exception;
$exception = Zonemaster::Backend::Error::Internal->new(reason => $reason, id => $exception_id);
}

if ( $exception->isa('Zonemaster::Backend::Error::Internal') ) {
$log->error($exception->as_string);
} else {
$log->info($exception->as_string);
}

$exception =~ s/\n/ /g;
$exception =~ s/^\s+|\s+$//g;
$log->error("Internal error $exception_id: Unexpected error in the $method API call: [$exception]");
die "Internal error $exception_id \n";
die $exception->as_hash;
}

$json_schemas{version_info} = joi->object->strict;
Expand Down