Skip to main content

saola_user_facing_errors/
lib.rs

1#![deny(unsafe_code, warnings, rust_2018_idioms)]
2#![allow(clippy::derive_partial_eq_without_eq)]
3
4mod panic_hook;
5
6pub mod common;
7#[cfg(feature = "sql")]
8pub mod quaint;
9pub mod query_engine;
10pub mod schema_engine;
11
12use serde::{Deserialize, Serialize};
13use std::{backtrace::Backtrace, borrow::Cow};
14
15pub use panic_hook::set_panic_hook;
16
17pub trait UserFacingError: serde::Serialize {
18    const ERROR_CODE: &'static str;
19
20    fn message(&self) -> String;
21}
22
23/// A less dynamic type of user-facing errors. This is used in the introspection and migration
24/// engines for simpler, more robust and helpful error handling — extra details are attached
25/// opportunistically.
26pub trait SimpleUserFacingError {
27    const ERROR_CODE: &'static str;
28    const MESSAGE: &'static str;
29}
30
31impl<T> UserFacingError for T
32where
33    T: SimpleUserFacingError + Serialize,
34{
35    const ERROR_CODE: &'static str = <Self as SimpleUserFacingError>::ERROR_CODE;
36
37    fn message(&self) -> String {
38        <Self as SimpleUserFacingError>::MESSAGE.to_owned()
39    }
40}
41
42#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
43pub struct KnownError {
44    pub message: String,
45    pub meta: serde_json::Value,
46    pub error_code: Cow<'static, str>,
47}
48
49impl KnownError {
50    pub fn new<T: UserFacingError>(inner: T) -> KnownError {
51        KnownError {
52            message: inner.message(),
53            meta: serde_json::to_value(&inner).expect("Failed to render user facing error metadata to JSON"),
54            error_code: Cow::from(T::ERROR_CODE),
55        }
56    }
57}
58
59#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
60pub struct UnknownError {
61    pub message: String,
62    pub backtrace: Option<String>,
63}
64
65impl UnknownError {
66    pub fn new(err: &dyn std::error::Error) -> Self {
67        UnknownError {
68            message: err.to_string(),
69            backtrace: None,
70        }
71    }
72}
73
74#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
75pub struct Error {
76    is_panic: bool,
77    #[serde(flatten)]
78    inner: ErrorType,
79
80    #[serde(skip_serializing_if = "Option::is_none")]
81    batch_request_idx: Option<usize>,
82}
83
84#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
85#[serde(untagged)]
86enum ErrorType {
87    Known(KnownError),
88    Unknown(UnknownError),
89}
90
91impl Error {
92    /// Try to interpret the error as a known error.
93    pub fn as_known(&self) -> Option<&KnownError> {
94        match &self.inner {
95            ErrorType::Known(err) => Some(err),
96            ErrorType::Unknown(_) => None,
97        }
98    }
99
100    pub fn message(&self) -> &str {
101        match &self.inner {
102            ErrorType::Known(err) => &err.message,
103            ErrorType::Unknown(err) => &err.message,
104        }
105    }
106
107    pub fn batch_request_idx(&self) -> Option<usize> {
108        self.batch_request_idx
109    }
110
111    pub fn new_non_panic_with_current_backtrace(message: String) -> Self {
112        Error {
113            inner: ErrorType::Unknown(UnknownError {
114                message,
115                backtrace: Some(format!("{:?}", Backtrace::force_capture())),
116            }),
117            is_panic: false,
118            batch_request_idx: None,
119        }
120    }
121
122    /// Construct a new UnknownError from a [`std::panic::PanicHookInfo`] in a
123    /// panic hook. [`UnknownError`]s created with this constructor will have a
124    /// proper, useful backtrace.
125    pub fn new_in_panic_hook(panic_info: &std::panic::PanicHookInfo<'_>) -> Self {
126        let message = panic_utils::downcast_ref_to_string(panic_info.payload()).unwrap_or("<unknown panic>");
127
128        let backtrace = Some(format!("{:?}", Backtrace::force_capture()));
129        let location = panic_info
130            .location()
131            .map(|loc| format!("{loc}"))
132            .unwrap_or_else(|| "<unknown location>".to_owned());
133
134        Error {
135            inner: ErrorType::Unknown(UnknownError {
136                message: format!("[{location}] {message}"),
137                backtrace,
138            }),
139            is_panic: true,
140            batch_request_idx: None,
141        }
142    }
143
144    /// Build from a KnownError
145    pub fn new_known(err: KnownError) -> Self {
146        Error {
147            inner: ErrorType::Known(err),
148            is_panic: false,
149            batch_request_idx: None,
150        }
151    }
152
153    pub fn from_panic_payload(panic_payload: Box<dyn std::any::Any + Send + 'static>) -> Self {
154        let message = Self::extract_panic_message(panic_payload).unwrap_or_else(|| "<unknown panic>".to_owned());
155
156        Error {
157            inner: ErrorType::Unknown(UnknownError {
158                message,
159                backtrace: None,
160            }),
161            is_panic: true,
162            batch_request_idx: None,
163        }
164    }
165
166    pub fn extract_panic_message(panic_payload: Box<dyn std::any::Any + Send + 'static>) -> Option<String> {
167        panic_payload
168            .downcast_ref::<&str>()
169            .map(|s| -> String { (*s).to_owned() })
170            .or_else(|| panic_payload.downcast_ref::<String>().map(|s| s.to_owned()))
171    }
172
173    /// Extract the inner known error, or panic.
174    pub fn unwrap_known(self) -> KnownError {
175        match self.inner {
176            ErrorType::Known(err) => err,
177            err @ ErrorType::Unknown(_) => panic!("Expected known error, got {err:?}"),
178        }
179    }
180
181    pub fn set_batch_request_idx(&mut self, batch_request_idx: usize) {
182        self.batch_request_idx = Some(batch_request_idx)
183    }
184}
185
186pub fn new_backtrace() -> Backtrace {
187    Backtrace::force_capture()
188}
189
190impl<T: UserFacingError> From<T> for Error {
191    fn from(err: T) -> Self {
192        Self::from(KnownError::new(err))
193    }
194}
195
196impl From<UnknownError> for Error {
197    fn from(unknown_error: UnknownError) -> Self {
198        Error {
199            inner: ErrorType::Unknown(unknown_error),
200            is_panic: false,
201            batch_request_idx: None,
202        }
203    }
204}
205
206impl From<KnownError> for Error {
207    fn from(known_error: KnownError) -> Self {
208        Error {
209            is_panic: false,
210            inner: ErrorType::Known(known_error),
211            batch_request_idx: None,
212        }
213    }
214}