saola_user_facing_errors/
lib.rs1#![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
23pub 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 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 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 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 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}