tc_error/
lib.rs

1//! Provides common error types and associated convenience methods for TinyChain.
2//!
3//! This crate is a part of TinyChain: [http://github.com/haydnv/tinychain](http://github.com/haydnv/tinychain)
4
5use std::convert::Infallible;
6use std::str::FromStr;
7use std::{fmt, io};
8
9use async_trait::async_trait;
10use destream::{de, en};
11
12/// A result of type `T`, or a [`TCError`]
13pub type TCResult<T> = Result<T, TCError>;
14
15#[derive(Clone)]
16struct ErrorData {
17    message: String,
18    stack: Vec<String>,
19}
20
21struct DataVisitor;
22
23#[async_trait]
24impl de::Visitor for DataVisitor {
25    type Value = ErrorData;
26
27    fn expecting() -> &'static str {
28        "an error message and optional stacktrace"
29    }
30
31    async fn visit_map<A: de::MapAccess>(self, mut access: A) -> Result<Self::Value, A::Error> {
32        let message = if let Some(key) = access.next_key::<String>(()).await? {
33            if key == "message" {
34                access.next_value(()).await
35            } else {
36                Err(de::Error::invalid_value(key, "message"))
37            }
38        } else {
39            Err(de::Error::invalid_length(0, Self::expecting()))
40        }?;
41
42        let stack = if let Some(key) = access.next_key::<String>(()).await? {
43            if key == "stack" {
44                access.next_value(()).await
45            } else {
46                Err(de::Error::invalid_value(key, "stack"))
47            }
48        } else {
49            Ok(Default::default())
50        }?;
51
52        Ok(ErrorData { message, stack })
53    }
54
55    fn visit_string<E: de::Error>(self, message: String) -> Result<Self::Value, E> {
56        Ok(ErrorData {
57            message,
58            stack: vec![],
59        })
60    }
61}
62
63#[async_trait]
64impl de::FromStream for ErrorData {
65    type Context = ();
66
67    async fn from_stream<D: de::Decoder>(_: (), decoder: &mut D) -> Result<Self, D::Error> {
68        decoder.decode_any(DataVisitor).await
69    }
70}
71
72impl<'en> en::IntoStream<'en> for ErrorData {
73    fn into_stream<E: en::Encoder<'en>>(self, encoder: E) -> Result<E::Ok, E::Error> {
74        if self.stack.is_empty() {
75            return en::IntoStream::into_stream(self.message, encoder);
76        }
77
78        use en::EncodeMap;
79
80        let mut map = encoder.encode_map(Some(2))?;
81        map.encode_entry("message", self.message)?;
82        map.encode_entry("stack", self.stack)?;
83        map.end()
84    }
85}
86
87impl<'en> en::ToStream<'en> for ErrorData {
88    fn to_stream<E: en::Encoder<'en>>(&'en self, encoder: E) -> Result<E::Ok, E::Error> {
89        if self.stack.is_empty() {
90            return en::ToStream::to_stream(&self.message, encoder);
91        }
92
93        use en::EncodeMap;
94
95        let mut map = encoder.encode_map(Some(2))?;
96        map.encode_entry("message", &self.message)?;
97        map.end()
98    }
99}
100
101impl<T> From<T> for ErrorData
102where
103    T: fmt::Display,
104{
105    fn from(message: T) -> Self {
106        Self {
107            message: message.to_string(),
108            stack: vec![],
109        }
110    }
111}
112
113/// The category of a `TCError`.
114#[derive(Clone, Copy, Eq, PartialEq)]
115pub enum ErrorKind {
116    BadGateway,
117    BadRequest,
118    Conflict,
119    Forbidden,
120    Internal,
121    MethodNotAllowed,
122    NotFound,
123    NotImplemented,
124    Timeout,
125    Unauthorized,
126    Unavailable,
127}
128
129impl ErrorKind {
130    pub fn is_conflict(&self) -> bool {
131        *self == Self::Conflict
132    }
133
134    pub fn is_retriable(&self) -> bool {
135        [Self::Timeout, Self::Unavailable]
136            .into_iter()
137            .any(|code| *self == code)
138    }
139}
140
141impl FromStr for ErrorKind {
142    type Err = TCError;
143
144    fn from_str(s: &str) -> Result<Self, Self::Err> {
145        match s {
146            "bad_gateway" => Ok(Self::BadGateway),
147            "bad_request" => Ok(Self::BadRequest),
148            "conflict" => Ok(Self::Conflict),
149            "forbidden" => Ok(Self::Forbidden),
150            "internal_error" => Ok(Self::Internal),
151            "method_not_allowed" => Ok(Self::MethodNotAllowed),
152            "not_found" => Ok(Self::NotFound),
153            "not_implemented" => Ok(Self::NotImplemented),
154            "request_timeout" => Ok(Self::Timeout),
155            "unauthorized" => Ok(Self::Unauthorized),
156            "temporarily_unavailable" => Ok(Self::Unavailable),
157            other => Err(bad_request!("unrecognized error code: {other}")),
158        }
159    }
160}
161
162#[async_trait]
163impl de::FromStream for ErrorKind {
164    type Context = ();
165
166    async fn from_stream<D: de::Decoder>(cxt: (), decoder: &mut D) -> Result<Self, D::Error> {
167        let code = String::from_stream(cxt, decoder).await?;
168        code.parse()
169            .map_err(|_| de::Error::invalid_value(code, "an error code"))
170    }
171}
172
173impl<'en> en::IntoStream<'en> for ErrorKind {
174    fn into_stream<E: en::Encoder<'en>>(self, encoder: E) -> Result<E::Ok, E::Error> {
175        self.to_string().into_stream(encoder)
176    }
177}
178
179impl fmt::Debug for ErrorKind {
180    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
181        write!(f, "{}", self.to_string().replace("_", " "))
182    }
183}
184
185impl fmt::Display for ErrorKind {
186    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
187        f.write_str(match self {
188            Self::BadGateway => "bad_gateway",
189            Self::BadRequest => "bad_request",
190            Self::Conflict => "conflict",
191            Self::Forbidden => "forbidden",
192            Self::Internal => "internal_error",
193            Self::MethodNotAllowed => "method_not_allowed",
194            Self::NotFound => "not_found",
195            Self::NotImplemented => "not_implemented",
196            Self::Timeout => "request_timeout",
197            Self::Unauthorized => "unauthorized",
198            Self::Unavailable => "temporarily_unavailable",
199        })
200    }
201}
202
203/// A general error description.
204#[derive(Clone)]
205pub struct TCError {
206    kind: ErrorKind,
207    data: ErrorData,
208}
209
210impl TCError {
211    /// Returns a new error with the given code and message.
212    pub fn new<I: fmt::Display>(code: ErrorKind, message: I) -> Self {
213        #[cfg(debug_assertions)]
214        match code {
215            ErrorKind::Internal | ErrorKind::MethodNotAllowed | ErrorKind::NotImplemented => {
216                panic!("{code}: {message}")
217            }
218            other => log::warn!("{other}: {message}"),
219        }
220
221        Self {
222            kind: code,
223            data: message.into(),
224        }
225    }
226
227    /// Reconstruct a [`TCError`] from its [`ErrorKind`] and data.
228    pub fn with_stack<I, S, SI>(code: ErrorKind, message: I, stack: S) -> Self
229    where
230        I: fmt::Display,
231        SI: fmt::Display,
232        S: IntoIterator<Item = SI>,
233    {
234        let stack = stack.into_iter().map(|msg| msg.to_string()).collect();
235
236        #[cfg(debug_assertions)]
237        match code {
238            ErrorKind::Internal | ErrorKind::MethodNotAllowed | ErrorKind::NotImplemented => {
239                panic!("{code}: {message} (cause: {stack:?})")
240            }
241            other => log::warn!("{other}: {message} (cause: {stack:?})"),
242        }
243
244        Self {
245            kind: code,
246            data: ErrorData {
247                message: message.to_string(),
248                stack,
249            },
250        }
251    }
252
253    /// Error to indicate a malformed or nonsensical request
254    pub fn bad_request<I: fmt::Display>(info: I) -> Self {
255        Self::new(ErrorKind::BadGateway, info)
256    }
257
258    /// Error to convey an upstream problem
259    pub fn bad_gateway<I: fmt::Display>(locator: I) -> Self {
260        Self::new(ErrorKind::BadGateway, locator)
261    }
262
263    /// Error to indicate that the requested resource is already locked
264    pub fn conflict<I: fmt::Display>(info: I) -> Self {
265        #[cfg(debug_assertions)]
266        panic!("conflict: {info}");
267
268        #[cfg(not(debug_assertions))]
269        Self::new(ErrorKind::Conflict, info)
270    }
271
272    /// An internal error which should never occur.
273    pub fn internal<I: fmt::Display>(info: I) -> Self {
274        #[cfg(debug_assertions)]
275        panic!("internal error: {info}");
276
277        #[cfg(not(debug_assertions))]
278        Self::new(ErrorKind::Internal, info)
279    }
280
281    /// Error to indicate that the requested resource exists but does not support the request method
282    pub fn method_not_allowed<M: fmt::Debug, P: fmt::Display>(method: M, path: P) -> Self {
283        let message = format!("endpoint {} does not support {:?}", path, method);
284
285        #[cfg(debug_assertions)]
286        panic!("{message}");
287
288        #[cfg(not(debug_assertions))]
289        Self::new(ErrorKind::MethodNotAllowed, message)
290    }
291
292    /// Error to indicate that the requested resource does not exist at the specified location
293    pub fn not_found<I: fmt::Display>(locator: I) -> Self {
294        Self::new(ErrorKind::NotFound, locator)
295    }
296
297    /// Error to indicate that the end-user is not authorized to perform the requested action
298    pub fn unauthorized<I: fmt::Display>(info: I) -> Self {
299        Self::new(ErrorKind::Unauthorized, info)
300    }
301
302    /// Error to indicate an unexpected input value or type
303    pub fn unexpected<V: fmt::Debug>(value: V, expected: &str) -> Self {
304        Self::bad_request(format!("invalid value {value:?}: expected {expected}"))
305    }
306
307    /// Error to indicate that the requested action cannot be performed due to technical limitations
308    pub fn unsupported<I: fmt::Display>(info: I) -> Self {
309        Self::bad_request(info)
310    }
311
312    /// The [`ErrorKind`] of this error
313    pub fn code(&self) -> ErrorKind {
314        self.kind
315    }
316
317    /// The error message of this error
318    pub fn message(&self) -> &str {
319        &self.data.message
320    }
321
322    /// Construct a new error with the given `cause`
323    pub fn consume<I: fmt::Debug>(mut self, cause: I) -> Self {
324        self.data.stack.push(format!("{:?}", cause));
325        self
326    }
327}
328
329impl std::error::Error for TCError {}
330
331impl From<pathlink::ParseError> for TCError {
332    fn from(err: pathlink::ParseError) -> Self {
333        Self::bad_request(err)
334    }
335}
336
337#[cfg(feature = "ha-ndarray")]
338impl From<ha_ndarray::Error> for TCError {
339    fn from(err: ha_ndarray::Error) -> Self {
340        Self::new(ErrorKind::Internal, err)
341    }
342}
343
344#[cfg(feature = "rjwt")]
345impl From<rjwt::Error> for TCError {
346    fn from(err: rjwt::Error) -> Self {
347        #[cfg(debug_assertions)]
348        panic!("rjwt error: {err}");
349
350        #[cfg(not(debug_assertions))]
351        match err.into_inner() {
352            (rjwt::ErrorKind::Auth | rjwt::ErrorKind::Time, msg) => Self::unauthorized(msg),
353            (rjwt::ErrorKind::Base64 | rjwt::ErrorKind::Format | rjwt::ErrorKind::Json, msg) => {
354                Self::bad_request(msg)
355            }
356            (rjwt::ErrorKind::Fetch, msg) => Self::bad_gateway(msg),
357        }
358    }
359}
360
361#[cfg(feature = "txn_lock")]
362impl From<txn_lock::Error> for TCError {
363    fn from(err: txn_lock::Error) -> Self {
364        Self::conflict(err)
365    }
366}
367
368#[cfg(feature = "txfs")]
369impl From<txfs::Error> for TCError {
370    fn from(cause: txfs::Error) -> Self {
371        match cause {
372            txfs::Error::Conflict(cause) => Self::conflict(cause),
373            txfs::Error::IO(cause) => Self::from(cause),
374            txfs::Error::NotFound(cause) => Self::not_found(cause),
375            txfs::Error::Parse(cause) => Self::from(cause),
376        }
377    }
378}
379
380impl From<io::Error> for TCError {
381    fn from(cause: io::Error) -> Self {
382        match cause.kind() {
383            io::ErrorKind::AlreadyExists => {
384                #[cfg(debug_assertions)]
385                panic!("tried to create an entry that already exists: {}", cause);
386
387                #[cfg(not(debug_assertions))]
388                bad_request!("tried to create an entry that already exists").consume(cause)
389            }
390            io::ErrorKind::InvalidInput => bad_request!("{}", cause),
391            io::ErrorKind::NotFound => TCError::not_found(cause),
392            io::ErrorKind::PermissionDenied => {
393                bad_gateway!("host filesystem permission denied").consume(cause)
394            }
395            io::ErrorKind::WouldBlock => {
396                conflict!("synchronous filesystem access failed").consume(cause)
397            }
398            kind => internal!("host filesystem error: {:?}", kind).consume(cause),
399        }
400    }
401}
402
403impl From<Infallible> for TCError {
404    fn from(_: Infallible) -> Self {
405        internal!("an unanticipated error occurred--please file a bug report")
406    }
407}
408
409struct ErrorVisitor;
410
411#[async_trait]
412impl de::Visitor for ErrorVisitor {
413    type Value = TCError;
414
415    fn expecting() -> &'static str {
416        "an error code, message, and optional stacktrace"
417    }
418
419    async fn visit_map<A: de::MapAccess>(self, mut access: A) -> Result<Self::Value, A::Error> {
420        if let Some(kind) = access.next_key(()).await? {
421            let data = access.next_value(()).await?;
422            Ok(TCError { kind, data })
423        } else {
424            Err(de::Error::invalid_length(0, Self::expecting()))
425        }
426    }
427}
428
429#[async_trait]
430impl de::FromStream for TCError {
431    type Context = ();
432
433    async fn from_stream<D: de::Decoder>(_: (), decoder: &mut D) -> Result<Self, D::Error> {
434        decoder.decode_map(ErrorVisitor).await
435    }
436}
437
438impl<'en> en::IntoStream<'en> for TCError {
439    fn into_stream<E: en::Encoder<'en>>(self, encoder: E) -> Result<E::Ok, E::Error> {
440        use en::EncodeMap;
441        let mut map = encoder.encode_map(Some(1))?;
442        map.encode_entry(self.kind, self.data)?;
443        map.end()
444    }
445}
446
447impl<'en> en::ToStream<'en> for TCError {
448    fn to_stream<E: en::Encoder<'en>>(&'en self, encoder: E) -> Result<E::Ok, E::Error> {
449        use en::EncodeMap;
450        let mut map = encoder.encode_map(Some(1))?;
451        map.encode_entry(self.kind, &self.data)?;
452        map.end()
453    }
454}
455
456impl fmt::Debug for TCError {
457    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
458        fmt::Display::fmt(self, f)
459    }
460}
461
462impl fmt::Display for TCError {
463    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
464        write!(f, "{}: {}", self.kind, self.data.message)
465    }
466}
467
468/// Error to convey an upstream problem
469#[macro_export]
470macro_rules! bad_gateway {
471    ($($t:tt)*) => {{
472        $crate::TCError::new($crate::ErrorKind::BadGateway, format!($($t)*))
473    }}
474}
475
476/// Error to indicate that the request is badly-constructed or nonsensical
477#[macro_export]
478macro_rules! bad_request {
479    ($($t:tt)*) => {{
480        $crate::TCError::bad_request(format!($($t)*))
481    }}
482}
483
484/// Error to indicate that the request cannot be fulfilled due to a conflict with another request.
485#[macro_export]
486macro_rules! conflict {
487    ($($t:tt)*) => {{
488        $crate::TCError::conflict(format!($($t)*))
489    }}
490}
491
492/// Error to indicate that the requestor's credentials do not authorize the request to be fulfilled
493#[macro_export]
494macro_rules! forbidden {
495    ($($t:tt)*) => {{
496        $crate::TCError::new($crate::ErrorKind::Unauthorized, format!($($t)*))
497    }}
498}
499
500/// Error to indicate that no resource exists at the requested path or key.
501#[macro_export]
502macro_rules! not_found {
503    ($($t:tt)*) => {{
504        $crate::TCError::not_found(format!($($t)*))
505    }}
506}
507
508/// Error to indicate that a required feature is not yet implemented.
509#[macro_export]
510macro_rules! not_implemented {
511    ($($t:tt)*) => {{
512        $crate::TCError::new($crate::ErrorKind::NotImplemented, format!($($t)*))
513    }}
514}
515
516/// Error to indicate that the request failed to complete in the allotted time.
517#[macro_export]
518macro_rules! timeout {
519    ($($t:tt)*) => {{
520        $crate::TCError::new($crate::ErrorKind::Timeout, format!($($t)*))
521    }}
522}
523
524/// A truly unexpected error, for which no handling behavior can be defined
525#[macro_export]
526macro_rules! internal {
527    ($($t:tt)*) => {{
528        $crate::TCError::internal(format!($($t)*))
529    }}
530}
531
532/// Error to indicate that the user's credentials are missing or nonsensical.
533#[macro_export]
534macro_rules! unauthorized {
535    ($($t:tt)*) => {{
536        $crate::TCError::unauthorized(format!($($t)*))
537    }}
538}
539
540/// Error to indicate that this host is currently overloaded
541#[macro_export]
542macro_rules! unavailable {
543    ($($t:tt)*) => {{
544        $crate::TCError::new($crate::ErrorKind::Unavailable, format!($($t)*))
545    }}
546}