Это не лучшая практика. Я просто сделаю это.
Вы можете использовать Try::Tiny, чтобы поймать ошибки в контроллере, а также помощники, которые Catalyst :: Action :: REST приносит для отправки соответствующих кодов ответов. Он позаботится о преобразовании ответа в нужный формат (т. Е. JSON) для вас.
Но это все еще требует от вас сделать это для каждого типа ошибок. В основном это сводится к следующему:
use Try::Tiny;
BEGIN { extends 'Catalyst::Controller::REST' }
__PACKAGE__->config(
json_options => { relaxed => 1, allow_nonref => 1 },
default => 'application/json',
map => { 'application/json' => [qw(View JSON)] },
);
sub default : Path : ActionClass('REST') { }
sub default_GET {
my ($self, $c, $mid) = @_;
try {
# ... (there might be a $c->detach in here)
} catch {
# this is thrown by $c->detach(), so don't 400 in this case
return if $_->$_isa('Catalyst::Exception::Detach');
$self->status_bad_request($c, message => q{Boom!});
}
}
методы для этих видов ответов перечислены в Catalyst::Controller::REST under STATUS HELPERS. К ним относятся:
Вы можете реализовать свой собственный отсутствующего статус по подклассов REST Catalyst :: Контролера :: или путем добавления в его пространство имен , Refer to one of them для того, как они построены. Вот пример.
*Catalyst::Controller::REST::status_teapot = sub {
my $self = shift;
my $c = shift;
my %p = Params::Validate::validate(@_, { message => { type => SCALAR }, },);
$c->response->status(418);
$c->log->debug("Status I'm A Teapot: " . $p{'message'}) if $c->debug;
$self->_set_entity($c, { error => $p{'message'} });
return 1;
}
Если это слишком утомительно, потому что у вас есть много действий, я предлагаю вам сделать использование end
действия, как вы хотели. Подробнее о том, как это работает немного дальше.
В этом случае не добавляйте конструкцию Try :: Tiny к своим действиям. Вместо этого убедитесь, что все ваши модели или другие модули, которые вы используете, бросают хорошие исключения.Создавайте классы исключений для каждого из случаев и передавайте контроль над тем, что должно произойти в этом случае.
Хороший способ сделать все это, чтобы использовать Catalyst::ControllerRole::CatchErrors. Он позволяет определить метод catch_error
, который будет обрабатывать ошибки для вас. В этом методе вы создаете таблицу диспетчеризации, которая знает, какое исключение должно вызывать такой ответ. Также посмотрите на documentation of $c->error
, так как здесь есть ценная информация.
package MyApp::Controller::Root;
use Moose;
use Safe::Isa;
BEGIN { extends 'Catalyst::Controller::REST' }
with 'Catalyst::ControllerRole::CatchErrors';
__PACKAGE__->config(
json_options => { relaxed => 1, allow_nonref => 1 },
default => 'application/json',
map => { 'application/json' => [qw(View JSON)] },
);
sub default : Path : ActionClass('REST') { }
sub default_GET {
my ($self, $c, $mid) = @_;
$c->model('Foo')->frobnicate;
}
sub catch_errors : Private {
my ($self, $c, @errors) = @_;
# Build a callback for each of the exceptions.
# This might go as an attribute on $c in MyApp::Catalyst as well.
my %dispatch = (
'MyApp::Exception::BadRequest' => sub {
$c->status_bad_request(message => $_[0]->message);
},
'MyApp::Exception::Teapot' => sub {
$c->status_teapot;
},
);
# @errors is like $c->error
my $e = shift @errors;
# this might be a bit more elaborate
if (ref $e =~ /^MyAPP::Exception/) {
$dispatch{ref $e}->($e) if exists $dispatch{ref $e};
$c->detach;
}
# if not, rethrow or re-die (simplified)
die $e;
}
Выше сырой, непроверенных пример. Возможно, это не так, но это хорошее начало. Было бы целесообразно переместить диспетчер в атрибут вашего основного объекта приложения Catalyst (контекст, $c
). Поместите его в MyApp :: Catalyst для этого.
package MyApp::Catalyst;
# ...
has error_dispatch_table => (
is => 'ro',
isa => 'HashRef',
traits => 'Hash',
handles => {
can_dispatch_error => 'exists',
dispatch_error => 'get',
},
builder => '_build_error_dispatch_table',
);
sub _build_error_dispatch_table {
return {
'MyApp::Exception::BadRequest' => sub {
$c->status_bad_request(message => $_[0]->message);
},
'MyApp::Exception::Teapot' => sub {
$c->status_teapot;
},
};
}
А потом делать диспетчеризацию так:
$c->dispatch_error(ref $e)->($e) if $c->can_dispatch_error(ref $e);
Теперь все, что вам нужно, это хорошие исключения. Есть разные способы сделать это. Мне нравится Exception::Class или Throwable::Factory.
package MyApp::Model::Foo;
use Moose;
BEGIN { extends 'Catalyst::Model' };
# this would go in its own file for reusability
use Exception::Class (
'MyApp::Exception::Base',
'MyApp::Exception::BadRequest' => {
isa => 'MyApp::Exception::Base',
description => 'This is a 400',
fields => [ 'message' ],
},
'MyApp::Exception::Teapot' => {
isa => 'MyApp::Exception::Base',
description => 'I do not like coffee',
},
);
sub frobnicate {
my ($self) = @_;
MyApp::Exception::Teapot->throw;
}
Опять же, было бы целесообразно переместить исключения в их собственный модуль, чтобы вы могли использовать их повсюду.
Это может быть хорошо продлен, я считаю. Также имейте в виду, что соединение бизнес-логики или моделей слишком сильно связано с тем, что это веб-приложение - плохой дизайн. Я выбрал очень известные имена исключений, потому что это легко объяснить. Возможно, вам захочется использовать более общие или, скорее, менее ориентированные на веб-имена имена, и ваша диспетчеризация должна фактически отображать их. В противном случае он слишком привязан к веб-слою.
1) Да, это множественное число. См. here.
Отличный ответ, спасибо за то, что вы так тщательно. Я посмотрел на источник «Catalyst :: ControllerRole :: CatchErrors», и они используют 'before 'end' => sub {...}', что именно то, что я искал. Единственная проблема заключается в том, что объект '@ error' завершается в строку, такую как' Caught exception в App :: API :: Foo-> default ... ', вероятно, добавленный классом контроллера REST, поэтому исключения не могут быть легко отправляется. – ojosilva
@ojosilva hmm, это плохой. Нет ли там реального объекта? Или ты просто «умер»? – simbabque
Моя модель, которая существует от пути до этого нового REST api, над которым я работаю, использует 'die' обильно. Использование действия «Try :: Tiny» по действию не является хорошим вариантом, учитывая огромное количество действий, но это может быть единственный способ избежать того, чтобы внутренняя ошибка модели была завершена во внешнюю строку сообщения. – ojosilva