File Coverage

blib/lib/YATT/Lite/Partial/ErrorReporter.pm
Criterion Covered Total %
statement 60 68 88.2
branch 22 32 68.7
condition 16 27 59.2
subroutine 12 13 92.3
pod 0 7 0.0
total 110 147 74.8


line stmt bran cond sub pod time code
1             package YATT::Lite::Partial::ErrorReporter; sub MY () {__PACKAGE__}
2             # -*- coding: utf-8 -*-
3 20     20   559 use strict;
  20         48  
  20         606  
4 20     20   100 use warnings qw(FATAL all NONFATAL misc);
  20         44  
  20         791  
5             use YATT::Lite::Partial
6 20         124 (fields => [qw/cf_at_done
7             cf_error_handler
8             cf_die_in_error
9             cf_ext_pattern
10 20     20   6605 /]);
  20         50  
11             require Devel::StackTrace;
12              
13 20     20   8453 use YATT::Lite::Error;
  20         54  
  20         908  
14 20     20   115 use YATT::Lite::Util qw/incr_opt/;
  20         43  
  20         13617  
15              
16              
17             #========================================
18             # error reporting.
19             #========================================
20              
21             sub error {
22 33 50   33 0 627 (my MY $self) = map {ref $_ ? $_ : MY} shift;
  33         223  
23 33         161 $self->raise(error => incr_opt(depth => \@_), @_);
24             }
25              
26             sub error_with_status {
27 7 50   7 0 49 (my MY $self) = map {ref $_ ? $_ : MY} shift;
  7         31  
28 7         18 my ($code) = shift;
29 7         33 my $opts = incr_opt(depth => \@_);
30 7         19 $opts->{http_status_code} = $code;
31 7         59 $self->raise(error => $opts, @_);
32             }
33              
34             sub make_error {
35 45     45 0 143 my ($self, $depth, $opts) = splice @_, 0, 3;
36 45         130 my ($fmt, @args) = @_;
37 45         256 my ($pkg, $file, $line) = caller($depth);
38 45         107 my $bt = do {
39 45         142 my @bt_opts = (ignore_package => [__PACKAGE__]);
40 45 100       160 if (my $frm = delete $opts->{ignore_frame}) {
41             # $YATT::Lite::CON->logdump(ignore_frame => $frm);
42             push @bt_opts, frame_filter => sub {
43 64     64   3080 my ($hash) = @_;
44 64         110 my $caller = $hash->{'caller'};
45 64   50     101 my $all_match = grep {($frm->[$_] // '') eq ($caller->[$_] // '')}
  128   50     437  
46             1, 2; # __FILE__, __LINE__
47             # print STDERR YATT::Lite::Util::terse_dump("filter: ", $all_match, $frm, $caller), "\n";
48 64         166 $all_match != 2;
49             }
50 2         17 }
51 45         321 Devel::StackTrace->new(@bt_opts);
52             };
53              
54 45   33     38847 my $pattern = $self->{cf_ext_pattern} // qr/\.(yatt|ytmpl|ydo)$/;
55              
56 45         112 my @tmplinfo;
57 45         190 foreach my $fr ($bt->frames) {
58 914 50       44002 my $fn = $fr->filename
59             or next;
60 914 50       5983 $fn =~ $pattern
61             or next;
62 0         0 push @tmplinfo, tmpl_file => $fn, tmpl_line => $fr->line;
63 0         0 last;
64             }
65              
66             $self->Error->new
67 45 100 100     850 (file => $opts->{file} // $file, line => $opts->{line} // $line
    50 100        
68             , @tmplinfo
69             , (@args
70             ? (format => $fmt, args => \@args)
71             : (reason => $fmt))
72             , backtrace => $bt
73             , $opts ? %$opts : ());
74             }
75              
76             # $yatt->raise($errType => ?{opts}?, $errFmt, @fmtArgs)
77              
78             sub raise {
79 45     45 0 141 (my MY $self, my $type) = splice @_, 0, 2;
80 45 50 33     250 my $opts = shift if @_ and ref $_[0] eq 'HASH';
81             # shift/splice しないのは、引数を stack trace に残したいから
82 45   50     175 my $depth = (delete($opts->{depth}) // 0);
83 45         223 my Error $err = $self->make_error(2 + $depth, $opts, @_); # 2==raise+make_error
84 45 100 66     322 if (ref $self and my $sub = deref($self->{cf_error_handler})) {
    100 33        
    50          
    100          
85             # $con を引数で引きずり回すのは大変なので、むしろ外から closure を渡そう、と。
86             # $SIG{__DIE__} を使わないのはなぜかって? それはユーザに開放しておきたいのよん。
87 2 50       8 unless (ref $sub eq 'CODE') {
88 0         0 die "error_handler is not a CODE ref: $sub";
89             }
90 2         7 $sub->($type, $err);
91             } elsif ($sub = $self->can('error_handler')) {
92 11         55 $sub->($self, $type, $err);
93             } elsif (not ref $self or $self->{cf_die_in_error}) {
94 0         0 die $err->message;
95             } elsif ($err->{cf_http_status_code}) {
96             # If http_status_code is specified explicitly (from error_with_status),
97             # raise it immediately, with simple reason. (not full backtrace message).
98             $self->raise_psgi_html($err->{cf_http_status_code}
99 3         22 , $err->reason);
100             } else {
101             # 即座に die しないモードは、デバッガから error 呼び出し箇所に step して戻れるようにするため。
102             # ... でも、受け側を do {my $err = $con->error; die $err} にでもしなきゃダメかも?
103 29         957 return $err;
104             }
105             }
106              
107             # XXX: 将来、拡張されるかも。
108             sub DONE {
109 0     0 0 0 my MY $self = shift;
110 0 0       0 if (my $sub = $self->{cf_at_done}) {
111 0         0 $sub->(@_);
112             } else {
113 0         0 die \ 'DONE';
114             }
115             }
116              
117             sub raise_psgi_html {
118 13     13 0 44 (my MY $self, my ($status, $html, @rest)) = @_;
119 13         202 die [$status, ["Content-type" => "text/html; charset=utf-8", @rest]
120             , [$html]];
121             }
122              
123             sub deref {
124 45 100   45 0 484 return undef unless defined $_[0];
125 13 100 66     108 if (ref $_[0] eq 'REF' or ref $_[0] eq 'SCALAR') {
126 11         21 ${$_[0]};
  11         100  
127             } else {
128 2         8 $_[0];
129             }
130             }
131              
132             1;