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