1package Search::Elasticsearch::Error;
2$Search::Elasticsearch::Error::VERSION = '6.00';
3our $DEBUG = 0;
4
5@Search::Elasticsearch::Error::Internal::ISA     = __PACKAGE__;
6@Search::Elasticsearch::Error::Param::ISA        = __PACKAGE__;
7@Search::Elasticsearch::Error::NoNodes::ISA      = __PACKAGE__;
8@Search::Elasticsearch::Error::Unauthorized::ISA = __PACKAGE__;
9@Search::Elasticsearch::Error::Forbidden::ISA    = __PACKAGE__;
10@Search::Elasticsearch::Error::Illegal::ISA      = __PACKAGE__;
11@Search::Elasticsearch::Error::Request::ISA      = __PACKAGE__;
12@Search::Elasticsearch::Error::Timeout::ISA      = __PACKAGE__;
13@Search::Elasticsearch::Error::Cxn::ISA          = __PACKAGE__;
14@Search::Elasticsearch::Error::Serializer::ISA   = __PACKAGE__;
15
16@Search::Elasticsearch::Error::Conflict::ISA
17    = ( 'Search::Elasticsearch::Error::Request', __PACKAGE__ );
18
19@Search::Elasticsearch::Error::Missing::ISA
20    = ( 'Search::Elasticsearch::Error::Request', __PACKAGE__ );
21
22@Search::Elasticsearch::Error::RequestTimeout::ISA
23    = ( 'Search::Elasticsearch::Error::Request', __PACKAGE__ );
24
25@Search::Elasticsearch::Error::ContentLength::ISA
26    = ( __PACKAGE__, 'Search::Elasticsearch::Error::Request' );
27
28@Search::Elasticsearch::Error::SSL::ISA
29    = ( __PACKAGE__, 'Search::Elasticsearch::Error::Cxn' );
30
31@Search::Elasticsearch::Error::BadGateway::ISA
32    = ( 'Search::Elasticsearch::Error::Cxn', __PACKAGE__ );
33
34@Search::Elasticsearch::Error::Unavailable::ISA
35    = ( 'Search::Elasticsearch::Error::Cxn', __PACKAGE__ );
36
37@Search::Elasticsearch::Error::GatewayTimeout::ISA
38    = ( 'Search::Elasticsearch::Error::Cxn', __PACKAGE__ );
39
40use overload (
41    '""'  => '_stringify',
42    'cmp' => '_compare',
43);
44
45use Data::Dumper();
46
47#===================================
48sub new {
49#===================================
50    my ( $class, $type, $msg, $vars, $caller ) = @_;
51    return $type if ref $type;
52    $caller ||= 0;
53
54    my $error_class = 'Search::Elasticsearch::Error::' . $type;
55    $msg = 'Unknown error' unless defined $msg;
56
57    local $DEBUG = 2 if $type eq 'Internal';
58
59    my $stack = $class->_stack;
60
61    my $self = bless {
62        type  => $type,
63        text  => $msg,
64        vars  => $vars,
65        stack => $stack,
66    }, $error_class;
67
68    return $self;
69}
70
71#===================================
72sub is {
73#===================================
74    my $self = shift;
75    for (@_) {
76        return 1 if $self->isa("Search::Elasticsearch::Error::$_");
77    }
78    return 0;
79}
80
81#===================================
82sub _stringify {
83#===================================
84    my $self = shift;
85    local $Data::Dumper::Terse  = 1;
86    local $Data::Dumper::Indent = !!$DEBUG;
87
88    unless ( $self->{msg} ) {
89        my $stack  = $self->{stack};
90        my $caller = $stack->[0];
91        $self->{msg} = sprintf( "[%s] ** %s, called from sub %s at %s line %d.",
92            $self->{type}, $self->{text}, @{$caller}[ 3, 1, 2 ] );
93
94        if ( $self->{vars} ) {
95            $self->{msg} .= sprintf( " With vars: %s\n",
96                Data::Dumper::Dumper $self->{vars} );
97        }
98
99        if ( @$stack > 1 ) {
100            $self->{msg}
101                .= sprintf( "Stacktrace:\n%s\n", $self->stacktrace($stack) );
102        }
103    }
104    return $self->{msg};
105
106}
107
108#===================================
109sub _compare {
110#===================================
111    my ( $self, $other, $swap ) = @_;
112    $self .= '';
113    ( $self, $other ) = ( $other, $self ) if $swap;
114    return $self cmp $other;
115}
116
117#===================================
118sub _stack {
119#===================================
120    my $self = shift;
121    my $caller = shift() || 2;
122
123    my @stack;
124    while ( my @caller = caller( ++$caller ) ) {
125        next if $caller[0] eq 'Try::Tiny';
126
127        if ( $caller[3] =~ /^(.+)::__ANON__\[(.+):(\d+)\]$/ ) {
128            @caller = ( $1, $2, $3, '(ANON)' );
129        }
130        elsif ( $caller[1] =~ /^\(eval \d+\)/ ) {
131            $caller[3] = "modified(" . $caller[3] . ")";
132        }
133
134        next
135            if $caller[0] =~ /^Search::Elasticsearch/
136            and ( $DEBUG < 2 or $caller[3] eq 'Try::Tiny::try' );
137        push @stack, [ @caller[ 0, 1, 2, 3 ] ];
138        last unless $DEBUG > 1;
139    }
140    return \@stack;
141}
142
143#===================================
144sub stacktrace {
145#===================================
146    my $self = shift;
147    my $stack = shift || $self->_stack();
148
149    my $o = sprintf "%s\n%-4s %-50s %-5s %s\n%s\n",
150        '-' x 80, '#', 'Package', 'Line', 'Sub-routine', '-' x 80;
151
152    my $i = 1;
153    for (@$stack) {
154        $o .= sprintf "%-4d %-50s %4d  %s\n", $i++, @{$_}[ 0, 2, 3 ];
155    }
156
157    return $o .= ( '-' x 80 ) . "\n";
158}
159
160#===================================
161sub TO_JSON {
162#===================================
163    my $self = shift;
164    return $self->_stringify;
165}
1661;
167
168# ABSTRACT: Errors thrown by Search::Elasticsearch
169
170__END__
171
172=pod
173
174=encoding UTF-8
175
176=head1 NAME
177
178Search::Elasticsearch::Error - Errors thrown by Search::Elasticsearch
179
180=head1 VERSION
181
182version 6.00
183
184=head1 DESCRIPTION
185
186Errors thrown by Search::Elasticsearch are error objects, which can include
187a stack trace and information to help debug problems. An error object
188consists of the following:
189
190    {
191        type  => $type,              # eg Missing
192        text  => 'Error message',
193        vars  => {...},              # vars which may help to explain the error
194        stack => [...],              # a stack trace
195    }
196
197The C<$Search::Elasticsearch::Error::DEBUG> variable can be set to C<1> or C<2>
198to increase the verbosity of errors.
199
200Error objects stringify to a human readable error message when used in text
201context (for example: C<print 'Oh no! '.$error>).  They also support the C<TO_JSON>
202method to support conversion to JSON when L<JSON/convert_blessed> is enabled.
203
204=head1 ERROR CLASSES
205
206The following error classes are defined:
207
208=over
209
210=item * C<Search::Elasticsearch::Error::Param>
211
212A bad parameter has been passed to a method.
213
214=item * C<Search::Elasticsearch::Error::Request>
215
216There was some generic error performing your request in Elasticsearch.
217This error is triggered by HTTP status codes C<400> and C<500>. This class
218has the following sub-classes:
219
220=over
221
222=item * C<Search::Elasticsearch::Error::Unauthorized>
223
224Invalid (or no) username/password provided as C<userinfo> for a password
225protected service. These errors are triggered by the C<401> HTTP status code.
226
227=item * C<Search::Elasticsearch::Error::Missing>
228
229A resource that you requested was not found.  These errors are triggered
230by the C<404> HTTP status code.
231
232=item * C<Elastisearch::Error::Conflict>
233
234Your request could not be performed because of some conflict.  For instance,
235if you try to delete a document with a particular version number, and the
236document has already changed, it will throw a C<Conflict> error.  If it can,
237it will include the C<current_version> in the error vars. This error
238is triggered by the C<409> HTTP status code.
239
240=item * C<Search::Elasticsearch::Error::ContentLength>
241
242The request body was longer than the
243L<max_content_length|Search::Elasticsearch::Role::Cxn/max_content_length>.
244
245=item * C<Search::Elasticsearch::Error::RequestTimeout>
246
247The request took longer than the specified C<timeout>.  Currently only
248applies to the
249L<cluster_health|Search::Elasticsearch::Client::6_0::Direct::Cluster/cluster_health()>
250request.
251
252=back
253
254=item * C<Search::Elasticsearch::Error::Timeout>
255
256The request timed out.
257
258=item * C<Search::Elasticsearch::Error::Cxn>
259
260There was an error connecting to a node in the cluster.  This error
261indicates node failure and will be retried on another node.
262This error has the following sub-classes:
263
264=over
265
266=item * C<Search::Elasticsearch::Error::Unavailable>
267
268The current node is unable to handle your request at the moment. Your
269request will be retried on another node.  This error is triggered by
270the C<503> HTTP status code.
271
272=item * C<Search::Elasticsearch::Error::BadGateway>
273
274A proxy between the client and Elasticsearch is unable to connect to Elasticsearch.
275This error is triggered by the C<502> HTTP status code.
276
277=item * C<Search::Elasticsearch::Error::GatewayTimeout>
278
279A proxy between the client and Elasticsearch is unable to connect to Elasticsearch
280within its own timeout. This error is triggered by the C<504> HTTP status code.
281
282=item * C<Search::Elasticsearch::Error::SSL>
283
284There was a problem validating the SSL certificate.  Not all
285backends support this error type.
286
287=back
288
289=item * C<Search::Elasticsearch::Error::Forbidden>
290
291Either the cluster was unable to process the request because it is currently
292blocking, eg there are not enough master nodes to form a cluster, or
293because the authenticated user is trying to perform an unauthorized
294action. This error is triggered by the C<403> HTTP status code.
295
296=item * C<Search::Elasticsearch::Error::Illegal>
297
298You have attempted to perform an illegal operation.
299For instance, you attempted to use a Scroll helper in a different process
300after forking.
301
302=item * C<Search::Elasticsearch::Error::Serializer>
303
304There was an error serializing a variable or deserializing a string.
305
306=item * C<Elasticsarch::Error::Internal>
307
308An internal error occurred - please report this as a bug in
309this module.
310
311=back
312
313=head1 AUTHOR
314
315Clinton Gormley <drtech@cpan.org>
316
317=head1 COPYRIGHT AND LICENSE
318
319This software is Copyright (c) 2017 by Elasticsearch BV.
320
321This is free software, licensed under:
322
323  The Apache License, Version 2.0, January 2004
324
325=cut
326