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