1use conjure_object::log_safety::MaybeLogSafe;
16use conjure_object::Any;
17use serde::Serialize;
18use std::borrow::Cow;
19use std::collections::hash_map::{self, HashMap};
20use std::fmt;
21use std::ops::Index;
22use std::sync::OnceLock;
23use std::time::Duration;
24use std::{backtrace, error};
25
26use crate::{ErrorType, Internal, SerializableError};
27
28#[derive(Debug)]
30pub struct ThrottleError {
31 duration: Option<Duration>,
32}
33
34impl ThrottleError {
35 #[inline]
37 pub fn duration(&self) -> Option<Duration> {
38 self.duration
39 }
40}
41
42#[derive(Debug)]
44pub struct UnavailableError(());
45
46#[derive(Debug)]
48#[non_exhaustive]
49pub enum ErrorKind {
50 Service(SerializableError),
52 Throttle(ThrottleError),
54 Unavailable(UnavailableError),
56}
57
58#[derive(Debug)]
59struct Inner {
60 cause: Box<dyn error::Error + Sync + Send>,
61 cause_safe: bool,
62 kind: ErrorKind,
63 safe_params: HashMap<Cow<'static, str>, Any>,
64 unsafe_params: HashMap<Cow<'static, str>, Any>,
65 backtraces: Vec<Backtrace>,
66}
67
68#[derive(Debug)]
80pub struct Error(Box<Inner>);
81
82impl Error {
83 pub fn service<E, T>(cause: E, error_type: T) -> Error
85 where
86 E: Into<Box<dyn error::Error + Sync + Send>>,
87 T: ErrorType + Serialize,
88 {
89 Error::service_inner(
90 cause.into(),
91 false,
92 crate::encode(&error_type),
93 T::safe_args(),
94 )
95 }
96
97 pub fn service_safe<E, T>(cause: E, error_type: T) -> Error
99 where
100 E: Into<Box<dyn error::Error + Sync + Send>>,
101 T: ErrorType + Serialize,
102 {
103 Error::service_inner(
104 cause.into(),
105 true,
106 crate::encode(&error_type),
107 T::safe_args(),
108 )
109 }
110
111 pub fn propagated_service<E>(cause: E, error: SerializableError) -> Error
113 where
114 E: Into<Box<dyn error::Error + Sync + Send>>,
115 {
116 Error::service_inner(cause.into(), false, error, &[])
117 }
118
119 pub fn propagated_service_safe<E>(cause: E, error: SerializableError) -> Error
121 where
122 E: Into<Box<dyn error::Error + Sync + Send>>,
123 {
124 Error::service_inner(cause.into(), true, error, &[])
125 }
126
127 fn service_inner(
128 cause: Box<dyn error::Error + Sync + Send>,
129 cause_safe: bool,
130 error: SerializableError,
131 safe_args: &[&str],
132 ) -> Error {
133 let mut safe_params = HashMap::new();
134 let mut unsafe_params = HashMap::new();
135
136 for (key, value) in error.parameters() {
137 let key = Cow::Owned(key.clone());
138 let value = Any::new(value).unwrap();
139 if safe_args.contains(&&*key) {
140 safe_params.insert(key, value);
141 } else {
142 unsafe_params.insert(key, value);
143 }
144 }
145
146 let mut error = Error::new(cause, cause_safe, ErrorKind::Service(error));
147 error.0.safe_params = safe_params;
148 error.0.unsafe_params = unsafe_params;
149 error
150 }
151
152 pub fn throttle<E>(cause: E) -> Error
154 where
155 E: Into<Box<dyn error::Error + Sync + Send>>,
156 {
157 Error::new(
158 cause.into(),
159 false,
160 ErrorKind::Throttle(ThrottleError { duration: None }),
161 )
162 }
163
164 pub fn throttle_safe<E>(cause: E) -> Error
166 where
167 E: Into<Box<dyn error::Error + Sync + Send>>,
168 {
169 Error::new(
170 cause.into(),
171 true,
172 ErrorKind::Throttle(ThrottleError { duration: None }),
173 )
174 }
175
176 pub fn throttle_for<E>(cause: E, duration: Duration) -> Error
179 where
180 E: Into<Box<dyn error::Error + Sync + Send>>,
181 {
182 Error::new(
183 cause.into(),
184 false,
185 ErrorKind::Throttle(ThrottleError {
186 duration: Some(duration),
187 }),
188 )
189 }
190
191 pub fn throttle_for_safe<E>(cause: E, duration: Duration) -> Error
194 where
195 E: Into<Box<dyn error::Error + Sync + Send>>,
196 {
197 Error::new(
198 cause.into(),
199 true,
200 ErrorKind::Throttle(ThrottleError {
201 duration: Some(duration),
202 }),
203 )
204 }
205
206 pub fn unavailable<E>(cause: E) -> Error
208 where
209 E: Into<Box<dyn error::Error + Sync + Send>>,
210 {
211 Error::new(
212 cause.into(),
213 false,
214 ErrorKind::Unavailable(UnavailableError(())),
215 )
216 }
217
218 pub fn unavailable_safe<E>(cause: E) -> Error
220 where
221 E: Into<Box<dyn error::Error + Sync + Send>>,
222 {
223 Error::new(
224 cause.into(),
225 true,
226 ErrorKind::Unavailable(UnavailableError(())),
227 )
228 }
229
230 pub fn internal<E>(cause: E) -> Error
232 where
233 E: Into<Box<dyn error::Error + Sync + Send>>,
234 {
235 Error::service(cause, Internal::new())
236 }
237
238 pub fn internal_safe<E>(cause: E) -> Error
240 where
241 E: Into<Box<dyn error::Error + Sync + Send>>,
242 {
243 Error::service_safe(cause, Internal::new())
244 }
245
246 fn new(cause: Box<dyn error::Error + Sync + Send>, cause_safe: bool, kind: ErrorKind) -> Error {
247 let inner = Inner {
248 cause,
249 cause_safe,
250 kind,
251 safe_params: HashMap::new(),
252 unsafe_params: HashMap::new(),
253 backtraces: vec![],
254 };
255 Error(Box::new(inner)).with_backtrace()
256 }
257
258 #[inline]
262 pub fn cause(&self) -> &(dyn error::Error + 'static + Sync + Send) {
263 &*self.0.cause
264 }
265
266 #[inline]
268 pub fn cause_safe(&self) -> bool {
269 self.0.cause_safe
270 }
271
272 #[inline]
274 pub fn kind(&self) -> &ErrorKind {
275 &self.0.kind
276 }
277
278 pub fn with_safe_param<T>(mut self, key: &'static str, value: T) -> Error
289 where
290 T: Serialize + MaybeLogSafe,
291 {
292 let value = Any::new(value).expect("value failed to serialize");
293 self.0.safe_params.insert(Cow::Borrowed(key), value);
294 self
295 }
296
297 pub fn with_unsafe_param<T>(mut self, key: &'static str, value: T) -> Error
303 where
304 T: Serialize,
305 {
306 let value = Any::new(value).expect("value failed to serialize");
307 self.0.unsafe_params.insert(Cow::Borrowed(key), value);
308 self
309 }
310
311 #[inline]
313 pub fn safe_params(&self) -> Params<'_> {
314 Params(&self.0.safe_params)
315 }
316
317 #[inline]
319 pub fn unsafe_params(&self) -> Params<'_> {
320 Params(&self.0.unsafe_params)
321 }
322
323 #[inline]
328 pub fn with_backtrace(mut self) -> Error {
329 self.0.backtraces.push(Backtrace::new());
330 self
331 }
332
333 #[inline]
335 pub fn with_custom_safe_backtrace(mut self, backtrace: String) -> Error {
336 self.0.backtraces.push(Backtrace::custom(backtrace));
337 self
338 }
339
340 #[inline]
342 pub fn backtraces(&self) -> &[Backtrace] {
343 &self.0.backtraces
344 }
345}
346
347#[derive(Debug)]
349pub struct Params<'a>(&'a HashMap<Cow<'static, str>, Any>);
350
351impl<'a> Params<'a> {
352 #[inline]
354 pub fn iter(&self) -> ParamsIter<'a> {
355 ParamsIter(self.0.iter())
356 }
357
358 #[inline]
360 pub fn len(&self) -> usize {
361 self.0.len()
362 }
363
364 #[inline]
366 pub fn is_empty(&self) -> bool {
367 self.0.is_empty()
368 }
369}
370
371impl Index<&str> for Params<'_> {
372 type Output = Any;
373
374 #[inline]
375 fn index(&self, key: &str) -> &Any {
376 &self.0[key]
377 }
378}
379
380impl<'a> IntoIterator for &Params<'a> {
381 type Item = (&'a str, &'a Any);
382 type IntoIter = ParamsIter<'a>;
383
384 #[inline]
385 fn into_iter(self) -> ParamsIter<'a> {
386 self.iter()
387 }
388}
389
390pub struct ParamsIter<'a>(hash_map::Iter<'a, Cow<'static, str>, Any>);
392
393impl<'a> Iterator for ParamsIter<'a> {
394 type Item = (&'a str, &'a Any);
395
396 #[inline]
397 fn next(&mut self) -> Option<(&'a str, &'a Any)> {
398 self.0.next().map(|(a, b)| (&**a, b))
399 }
400
401 #[inline]
402 fn size_hint(&self) -> (usize, Option<usize>) {
403 self.0.size_hint()
404 }
405}
406
407static BACKTRACE_PROVIDER: OnceLock<Box<dyn Fn() -> String + Send + Sync>> = OnceLock::new();
408
409pub fn set_safe_custom_backtrace_provider<F>(provider: F) -> Result<(), Error>
413where
414 F: Fn() -> String + Send + Sync + 'static,
415{
416 BACKTRACE_PROVIDER
417 .set(Box::new(provider))
418 .or(Err(Error::internal_safe("backtrace provider already set")))
419}
420
421pub struct Backtrace(BacktraceInner);
423
424impl fmt::Debug for Backtrace {
425 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
426 match &self.0 {
427 BacktraceInner::Rust(b) => fmt::Display::fmt(b, fmt),
428 BacktraceInner::Custom(b) => fmt::Display::fmt(b, fmt),
429 }
430 }
431}
432
433impl Backtrace {
434 #[inline]
435 fn new() -> Backtrace {
436 if let Some(provider) = BACKTRACE_PROVIDER.get() {
437 Backtrace(BacktraceInner::Custom(provider()))
438 } else {
439 Backtrace(BacktraceInner::Rust(backtrace::Backtrace::force_capture()))
440 }
441 }
442
443 #[inline]
444 fn custom(s: String) -> Backtrace {
445 Backtrace(BacktraceInner::Custom(s))
446 }
447}
448
449enum BacktraceInner {
450 Rust(backtrace::Backtrace),
451 Custom(String),
452}