Skip to main content

common_error/
ext.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::any::Any;
16use std::fmt::{Debug, Formatter};
17use std::io::ErrorKind;
18use std::str::FromStr;
19use std::sync::Arc;
20
21use snafu::{FromString, Snafu};
22
23use crate::status_code::StatusCode;
24
25/// Describes whether an error instance is safe and useful to retry.
26///
27/// This is intentionally separate from [`StatusCode`]: status code describes the
28/// error category exposed to users, while retry hint describes retry policy for
29/// this specific error instance.
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum RetryHint {
32    /// The operation may succeed if retried later.
33    Retryable,
34    /// Retrying the same operation is not expected to help.
35    ///
36    /// This is the default for errors that do not explicitly opt in to retry.
37    NonRetryable,
38}
39
40const RETRY_HINT_RETRYABLE: &str = "retryable";
41const RETRY_HINT_NON_RETRYABLE: &str = "non_retryable";
42
43impl RetryHint {
44    pub fn is_retryable(self) -> bool {
45        matches!(self, RetryHint::Retryable)
46    }
47
48    pub fn as_str(self) -> &'static str {
49        match self {
50            RetryHint::Retryable => RETRY_HINT_RETRYABLE,
51            RetryHint::NonRetryable => RETRY_HINT_NON_RETRYABLE,
52        }
53    }
54}
55
56impl FromStr for RetryHint {
57    type Err = ();
58
59    fn from_str(s: &str) -> Result<Self, Self::Err> {
60        match s {
61            RETRY_HINT_RETRYABLE => Ok(RetryHint::Retryable),
62            RETRY_HINT_NON_RETRYABLE => Ok(RetryHint::NonRetryable),
63            _ => Err(()),
64        }
65    }
66}
67
68/// Converts a [`std::io::Error`] into a conservative [`RetryHint`].
69///
70/// This helper classifies known transient I/O conditions as retryable and treats
71/// request, permission, filesystem-capacity, and data-shape errors as
72/// non-retryable. `std::io::ErrorKind` is non-exhaustive, so future or
73/// unclassified kinds are considered non-retryable until reviewed explicitly.
74pub fn retry_hint_from_io_error(error: &std::io::Error) -> RetryHint {
75    match error.kind() {
76        ErrorKind::ConnectionRefused
77        | ErrorKind::ConnectionReset
78        | ErrorKind::HostUnreachable
79        | ErrorKind::NetworkUnreachable
80        | ErrorKind::ConnectionAborted
81        | ErrorKind::NotConnected
82        | ErrorKind::NetworkDown
83        | ErrorKind::BrokenPipe
84        | ErrorKind::WouldBlock
85        | ErrorKind::StaleNetworkFileHandle
86        | ErrorKind::TimedOut
87        | ErrorKind::ResourceBusy
88        | ErrorKind::Interrupted => RetryHint::Retryable,
89
90        _ => RetryHint::NonRetryable,
91    }
92}
93
94/// Extension to [`Error`](std::error::Error) in std.
95pub trait ErrorExt: StackError {
96    /// Map this error to [StatusCode].
97    fn status_code(&self) -> StatusCode {
98        StatusCode::Unknown
99    }
100
101    /// Returns the retry hint for this error instance.
102    ///
103    /// Implementations should return [`RetryHint::Retryable`] only when retrying the
104    /// same operation may succeed without changing the request. The default is
105    /// [`RetryHint::NonRetryable`] to avoid accidental retry loops.
106    fn retry_hint(&self) -> RetryHint {
107        RetryHint::NonRetryable
108    }
109
110    /// Returns whether this error instance is marked retryable.
111    ///
112    /// This is derived from [`Self::retry_hint`]. Transport-level retries, such as a
113    /// gRPC `Unavailable`, may still be handled separately by client code.
114    fn is_retryable(&self) -> bool {
115        self.retry_hint().is_retryable()
116    }
117
118    /// Returns the error as [Any](std::any::Any) so that it can be
119    /// downcast to a specific implementation.
120    fn as_any(&self) -> &dyn Any;
121
122    fn output_msg(&self) -> String
123    where
124        Self: Sized,
125    {
126        match self.status_code() {
127            StatusCode::Unknown | StatusCode::Internal => {
128                // masks internal error from end user
129                format!("Internal error: {}", self.status_code() as u32)
130            }
131            _ => {
132                let error = self.last();
133                if let Some(external_error) = error.source() {
134                    let external_root = external_error.sources().last().unwrap();
135
136                    if error.transparent() {
137                        format!("{external_root}")
138                    } else {
139                        format!("{error}: {external_root}")
140                    }
141                } else {
142                    format!("{error}")
143                }
144            }
145        }
146    }
147
148    /// Find out root level error for nested error
149    fn root_cause(&self) -> Option<&dyn std::error::Error>
150    where
151        Self: Sized,
152    {
153        let error = self.last();
154        if let Some(external_error) = error.source() {
155            let external_root = external_error.sources().last().unwrap();
156            Some(external_root)
157        } else {
158            None
159        }
160    }
161}
162
163pub trait StackError: std::error::Error {
164    fn debug_fmt(&self, layer: usize, buf: &mut Vec<String>);
165
166    fn next(&self) -> Option<&dyn StackError>;
167
168    fn last(&self) -> &dyn StackError
169    where
170        Self: Sized,
171    {
172        let Some(mut result) = self.next() else {
173            return self;
174        };
175        while let Some(err) = result.next() {
176            result = err;
177        }
178        result
179    }
180
181    /// Indicates whether this error is "transparent", that it delegates its "display" and "source"
182    /// to the underlying error. Could be useful when you are just wrapping some external error,
183    /// **AND** can not or would not provide meaningful contextual info. For example, the
184    /// `DataFusionError`.
185    fn transparent(&self) -> bool {
186        false
187    }
188}
189
190impl<T: ?Sized + StackError> StackError for Arc<T> {
191    fn debug_fmt(&self, layer: usize, buf: &mut Vec<String>) {
192        self.as_ref().debug_fmt(layer, buf)
193    }
194
195    fn next(&self) -> Option<&dyn StackError> {
196        self.as_ref().next()
197    }
198}
199
200impl<T: StackError> StackError for Box<T> {
201    fn debug_fmt(&self, layer: usize, buf: &mut Vec<String>) {
202        self.as_ref().debug_fmt(layer, buf)
203    }
204
205    fn next(&self) -> Option<&dyn StackError> {
206        self.as_ref().next()
207    }
208}
209
210/// A simple [Result] of which the error is convertible from [ErrorExt] (which every GreptimeDB
211/// error implements). Use this if you are tired of writing `unwrap`s in test codes, that you can
212/// use the `?` on all GreptimeDB errors.
213pub type WhateverResult<T> = Result<T, Whatever>;
214
215#[derive(Snafu)]
216#[snafu(display("{inner}"))]
217pub struct Whatever {
218    inner: snafu::Whatever,
219}
220
221impl Debug for Whatever {
222    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
223        write!(f, "{}", self.inner)
224    }
225}
226
227impl<E: ErrorExt> From<E> for Whatever {
228    fn from(e: E) -> Self {
229        Self {
230            inner: FromString::without_source(format!("{e:?}")),
231        }
232    }
233}
234
235impl From<String> for Whatever {
236    fn from(s: String) -> Self {
237        Self {
238            inner: FromString::without_source(s),
239        }
240    }
241}
242
243/// An opaque boxed error based on errors that implement [ErrorExt] trait.
244pub struct BoxedError {
245    inner: Box<dyn crate::ext::ErrorExt + Send + Sync>,
246}
247
248impl BoxedError {
249    pub fn new<E: crate::ext::ErrorExt + Send + Sync + 'static>(err: E) -> Self {
250        Self {
251            inner: Box::new(err),
252        }
253    }
254
255    pub fn into_inner(self) -> Box<dyn crate::ext::ErrorExt + Send + Sync> {
256        self.inner
257    }
258}
259
260impl std::fmt::Debug for BoxedError {
261    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
262        let mut buf = vec![];
263        self.debug_fmt(0, &mut buf);
264        write!(f, "{}", buf.join("\n"))
265    }
266}
267
268impl std::fmt::Display for BoxedError {
269    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
270        write!(f, "{}", self.inner)
271    }
272}
273
274impl std::error::Error for BoxedError {
275    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
276        self.inner.source()
277    }
278}
279
280impl crate::ext::ErrorExt for BoxedError {
281    fn status_code(&self) -> crate::status_code::StatusCode {
282        self.inner.status_code()
283    }
284
285    fn retry_hint(&self) -> RetryHint {
286        self.inner.retry_hint()
287    }
288
289    fn as_any(&self) -> &dyn std::any::Any {
290        self.inner.as_any()
291    }
292}
293
294// Implement ErrorCompat for this opaque error so the backtrace is also available
295// via `ErrorCompat::backtrace()`.
296impl crate::snafu::ErrorCompat for BoxedError {
297    fn backtrace(&self) -> Option<&crate::snafu::Backtrace> {
298        None
299    }
300}
301
302impl StackError for BoxedError {
303    fn debug_fmt(&self, layer: usize, buf: &mut Vec<String>) {
304        self.inner.debug_fmt(layer, buf)
305    }
306
307    fn next(&self) -> Option<&dyn StackError> {
308        self.inner.next()
309    }
310}
311
312/// Error type with plain error message
313#[derive(Debug)]
314pub struct PlainError {
315    msg: String,
316    status_code: StatusCode,
317}
318
319impl PlainError {
320    pub fn new(msg: String, status_code: StatusCode) -> Self {
321        Self { msg, status_code }
322    }
323}
324
325impl std::fmt::Display for PlainError {
326    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
327        write!(f, "{}", self.msg)
328    }
329}
330
331impl std::error::Error for PlainError {
332    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
333        None
334    }
335}
336
337impl crate::ext::ErrorExt for PlainError {
338    fn status_code(&self) -> crate::status_code::StatusCode {
339        self.status_code
340    }
341
342    fn as_any(&self) -> &dyn std::any::Any {
343        self as _
344    }
345}
346
347impl StackError for PlainError {
348    fn debug_fmt(&self, layer: usize, buf: &mut Vec<String>) {
349        buf.push(format!("{}: {}", layer, self.msg))
350    }
351
352    fn next(&self) -> Option<&dyn StackError> {
353        None
354    }
355}