]> Dogcows Code - chaz/p5-File-KDBX/blob - lib/File/KDBX/Error.pm
add initial WIP
[chaz/p5-File-KDBX] / lib / File / KDBX / Error.pm
1 package File::KDBX::Error;
2 # ABSTRACT: Represents something bad that happened
3
4 use warnings;
5 use strict;
6
7 use Exporter qw(import);
8 use Scalar::Util qw(blessed);
9 use namespace::clean -except => 'import';
10
11 our $VERSION = '999.999'; # VERSION
12
13 our @EXPORT = qw(alert error throw);
14
15 my $WARNINGS_CATEGORY;
16 BEGIN {
17 $WARNINGS_CATEGORY = 'File::KDBX';
18 warnings::register_categories($WARNINGS_CATEGORY) if warnings->can('register_categories');
19 }
20
21 use overload '""' => 'to_string', cmp => '_cmp';
22
23 =method new
24
25 $error = File::KDBX::Error->new($message, %details);
26
27 Construct a new error.
28
29 =cut
30
31 sub new {
32 my $class = shift;
33 my %args = @_ % 2 == 0 ? @_ : (_error => shift, @_);
34
35 my $error = delete $args{_error};
36 my $e = $error;
37 # $e =~ s/ at \H+ line \d+.*//g;
38
39 my $self = bless {
40 details => \%args,
41 error => $e // 'Something happened',
42 errno => $!,
43 previous => $@,
44 trace => do {
45 require Carp;
46 local $Carp::CarpInternal{''.__PACKAGE__} = 1;
47 my $mess = $error =~ /at \H+ line \d+/ ? $error : Carp::longmess($error);
48 [map { /^\h*(.*?)\.?$/ ? $1 : $_ } split(/\n/, $mess)];
49 },
50 }, $class;
51 chomp $self->{error};
52 return $self;
53 }
54
55 =method error
56
57 $error = error($error);
58 $error = error($message, %details);
59 $error = File::KDBX::Error->error($error);
60 $error = File::KDBX::Error->error($message, %details);
61
62 Wrap a thing to make it an error object. If the thing is already an error, it gets returned. Otherwise what is
63 passed will be forwarded to L</new> to create a new error object.
64
65 This can be convenient for error handling when you're not sure what the exception is but you want to treat it
66 as a B<File::KDBX::Error>. Example:
67
68 eval { .... };
69 if (my $error = error(@_)) {
70 if ($error->type eq 'key.missing') {
71 handle_missing_key($error);
72 }
73 else {
74 handle_other_error($error);
75 }
76 }
77
78 =cut
79
80 sub error {
81 my $self = (blessed($_[0]) && $_[0]->isa('File::KDBX::Error'))
82 ? shift
83 : (@_ && $_[0] eq __PACKAGE__)
84 ? shift->new(@_)
85 : __PACKAGE__->new(@_);
86 return $self;
87 }
88
89 =attr details
90
91 \%details = $error->details;
92
93 Get the error details.
94
95 =cut
96
97 sub details {
98 my $self = shift;
99 my %args = @_;
100 my $details = $self->{details} //= {};
101 @$details{keys %args} = values %args;
102 return $details;
103 }
104
105 sub errno { $_[0]->{errno} }
106
107 sub previous { $_[0]->{previous} }
108
109 sub trace { $_[0]->{trace} // [] }
110
111 sub type { $_[0]->details->{type} // '' }
112
113 =method to_string
114
115 $message = $error->to_string;
116 $message = "$error";
117
118 Stringify an error.
119
120 This does not contain a stack trace, but you can set the C<DEBUG> environment
121 variable to truthy to stringify the whole error object.
122
123 =cut
124
125 sub _cmp { "$_[0]" cmp "$_[1]" }
126
127 sub PROPAGATE {
128 'wat';
129 }
130
131 sub to_string {
132 my $self = shift;
133 # return "uh oh\n";
134 my $msg = "$self->{trace}[0]";
135 $msg .= '.' if $msg !~ /[\.\!\?]$/; # Why does this cause infinite recursion on some perls?
136 # $msg .= '.' if $msg !~ /(?:\.|!|\?)$/;
137 if ($ENV{DEBUG}) {
138 require Data::Dumper;
139 local $Data::Dumper::Indent = 1;
140 local $Data::Dumper::Quotekeys = 0;
141 local $Data::Dumper::Sortkeys = 1;
142 local $Data::Dumper::Terse = 1;
143 local $Data::Dumper::Trailingcomma = 1;
144 local $Data::Dumper::Useqq = 1;
145 $msg .= "\n" . Data::Dumper::Dumper $self;
146 }
147 $msg .= "\n" if $msg !~ /\n$/;
148 return $msg;
149 }
150
151 =method throw
152
153 File::KDBX::Error::throw($message, %details);
154 $error->throw;
155
156 Throw an error.
157
158 =cut
159
160 sub throw {
161 my $self = error(@_);
162 die $self;
163 }
164
165 =method warn
166
167 File::KDBX::Error::warn($message, %details);
168 $error->warn;
169
170 Log a warning.
171
172 =cut
173
174 sub warn {
175 return if !($File::KDBX::WARNINGS // 1);
176
177 my $self = error(@_);
178
179 # Use die and warn directly instead of warnings::warnif because the latter only provides the stringified
180 # error to the warning signal handler (perl 5.34). Maybe that's a warnings.pm bug?
181
182 if (my $fatal = warnings->can('fatal_enabled_at_level')) {
183 my $blame = _find_blame_frame();
184 die $self if $fatal->($WARNINGS_CATEGORY, $blame);
185 }
186
187 if (my $enabled = warnings->can('enabled_at_level')) {
188 my $blame = _find_blame_frame();
189 warn $self if $enabled->($WARNINGS_CATEGORY, $blame);
190 }
191 elsif ($enabled = warnings->can('enabled')) {
192 warn $self if $enabled->($WARNINGS_CATEGORY);
193 }
194 else {
195 warn $self;
196 }
197 return $self;
198 }
199
200 =method alert
201
202 alert $error;
203
204 Importable alias for L</warn>.
205
206 =cut
207
208 sub alert { goto &warn }
209
210 sub _find_blame_frame {
211 my $frame = 1;
212 while (1) {
213 my ($package) = caller($frame);
214 last if !$package;
215 return $frame - 1 if $package !~ /^\Q$WARNINGS_CATEGORY\E/;
216 $frame++;
217 }
218 return 0;
219 }
220
221 1;
This page took 0.042189 seconds and 4 git commands to generate.