Skip to main content

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