use
warnings
qw(FATAL all NONFATAL misc)
;
(
fields
=> [
qw/cf_at_done
cf_error_handler
cf_die_in_error
cf_ext_pattern
cf_in_sig_die
/
]);
use
constant
DEBUG_ERROR
=>
$ENV
{DEBUG_YATT_ERROR};
sub
error {
(
my
MY
$self
) =
map
{
ref
$_
?
$_
: MY}
shift
;
$self
->raise(
error
=> incr_opt(
depth
=> \
@_
),
@_
);
}
sub
error_with_status {
(
my
MY
$self
) =
map
{
ref
$_
?
$_
: MY}
shift
;
my
(
$code
) =
shift
;
my
$opts
= incr_opt(
depth
=> \
@_
);
$opts
->{http_status_code} =
$code
;
$self
->raise(
error
=>
$opts
,
@_
);
}
sub
make_error {
my
(
$self
,
$depth
,
$opts
) =
splice
@_
, 0, 3;
my
(
$fmtOrReason
,
@args
) =
@_
;
my
(
$pkg
,
$file
,
$line
) =
caller
(
$depth
);
my
$bt
=
do
{
my
@bt_opts
= (
ignore_package
=> [__PACKAGE__]);
if
(
my
$frm
=
delete
$opts
->{ignore_frame}) {
push
@bt_opts
,
frame_filter
=>
sub
{
my
(
$hash
) =
@_
;
my
$caller
=
$hash
->{
'caller'
};
my
$all_match
=
grep
{(
$frm
->[
$_
] //
''
) eq (
$caller
->[
$_
] //
''
)}
1, 2;
$all_match
!= 2;
}
}
Devel::StackTrace->new(
@bt_opts
);
};
my
$pattern
=
$self
->{cf_ext_pattern} //
qr/\.(yatt|ytmpl|ydo)$/
;
my
@tmplinfo
;
foreach
my
$fr
(
$bt
->frames) {
my
$fn
=
$fr
->filename
or
next
;
$fn
=~
$pattern
or
next
;
push
@tmplinfo
,
tmpl_file
=>
$fn
,
tmpl_line
=>
$fr
->line;
last
;
}
my
@error_diag
=
do
{
if
(
@args
) {
(
format
=>
$fmtOrReason
,
args
=> \
@args
);
}
else
{
(
reason
=>
do
{
if
(Encode::is_utf8(
$fmtOrReason
) and not utf8::valid(
$fmtOrReason
)) {
YATT::Lite::Util::reencode_malformed_utf8(
$fmtOrReason
);
}
else
{
$fmtOrReason
;
}
});
}
};
$self
->Error->new
(
file
=>
$opts
->{file} //
$file
,
line
=>
$opts
->{line} //
$line
,
@tmplinfo
,
@error_diag
,
backtrace
=>
$bt
,
$opts
?
%$opts
: ());
}
sub
raise {
(
my
MY
$self
,
my
$type
) =
splice
@_
, 0, 2;
my
$opts
=
shift
if
@_
and
ref
$_
[0] eq
'HASH'
;
my
$depth
= (
delete
(
$opts
->{depth}) // 0);
my
Error
$err
=
$self
->make_error(2 +
$depth
,
$opts
,
@_
);
if
(
ref
$self
and
my
$sub
= deref(
$self
->{cf_error_handler})) {
print
STDERR
"# raise by cf_error_handler\n"
if
DEBUG_ERROR;
unless
(
ref
$sub
eq
'CODE'
) {
die
"error_handler is not a CODE ref: $sub"
;
}
$sub
->(
$type
,
$err
);
}
elsif
(
$sub
=
$self
->can(
'error_handler'
)) {
print
STDERR
"# raise by ->error_handler\n"
if
DEBUG_ERROR;
$sub
->(
$self
,
$type
,
$err
);
}
elsif
(not
ref
$self
or
$self
->{cf_die_in_error}) {
print
STDERR
"# raise by die_in_error\n"
if
DEBUG_ERROR;
die
$err
->message;
}
elsif
(
$err
->{cf_http_status_code}) {
print
STDERR
"# raise by http_status_code\n"
if
DEBUG_ERROR;
$self
->raise_psgi_html(
$err
->{cf_http_status_code}
,
$err
->reason);
}
else
{
print
STDERR
"# raise pass-thrue error object\n"
if
DEBUG_ERROR;
return
$err
;
}
}
sub
DONE {
my
MY
$self
=
shift
;
if
(
my
$sub
=
$self
->{cf_at_done}) {
$sub
->(
@_
);
}
else
{
die
\
'DONE'
;
}
}
sub
raise_psgi_html {
(
my
MY
$self
,
my
(
$status
,
$html
,
@rest
)) =
@_
;
die
[
$status
, [
"Content-type"
=>
"text/html; charset=utf-8"
,
@rest
]
, [
$html
]];
}
sub
deref {
return
undef
unless
defined
$_
[0];
if
(
ref
$_
[0] eq
'REF'
or
ref
$_
[0] eq
'SCALAR'
) {
${
$_
[0]};
}
else
{
$_
[0];
}
}
1;