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