possum/
error.rs

1use thiserror::Error;
2
3use super::*;
4
5#[derive(Error, Debug)]
6#[repr(C)]
7pub enum Error {
8    #[error("no such key")]
9    NoSuchKey,
10    #[error("sqlite error: {0:?}")]
11    Sqlite(#[from] rusqlite::Error),
12    #[error("io error: {0:?}")]
13    Io(#[from] std::io::Error),
14    #[error("anyhow: {0:?}")]
15    // Having the #[from] here kinda sux because everything is stuffed into the anyhow variant
16    // automatically, even other instances of Error.
17    Anyhow(#[from] anyhow::Error),
18    #[error("unsupported filesystem")]
19    UnsupportedFilesystem,
20}
21
22use Error::*;
23
24pub trait FileAlreadyExistsError {
25    fn is_file_already_exists(&self) -> bool;
26}
27
28impl FileAlreadyExistsError for std::io::Error {
29    fn is_file_already_exists(&self) -> bool {
30        self.kind() == ErrorKind::AlreadyExists
31    }
32}
33
34impl FileAlreadyExistsError for Error {
35    // This isn't great, since such an error could already be wrapped up in anyhow, or come from
36    // Sqlite for example.
37    fn is_file_already_exists(&self) -> bool {
38        match self {
39            Io(err) => err.is_file_already_exists(),
40            _ => false,
41        }
42    }
43}
44
45impl Error {
46    pub fn root_cause(&self) -> &(dyn std::error::Error + 'static) {
47        match self {
48            NoSuchKey | UnsupportedFilesystem => self,
49            Sqlite(inner) => inner,
50            Anyhow(inner) => inner.root_cause(),
51            _ => unimplemented!(),
52        }
53    }
54
55    pub fn root_cause_is_unsupported_filesystem(&self) -> bool {
56        matches!(
57            self.root_cause().downcast_ref(),
58            Some(Self::UnsupportedFilesystem)
59        )
60    }
61}
62
63#[cfg(windows)]
64impl From<windows::core::Error> for Error {
65    fn from(from: windows::core::Error) -> Self {
66        anyhow::Error::from(from).into()
67    }
68}
69
70#[cfg(test)]
71mod tests {
72    use anyhow::Context;
73
74    use crate::{Error, PubResult};
75
76    #[test]
77    fn test_downcast_double_contexted() {
78        let res = Err::<(), _>(Error::UnsupportedFilesystem);
79        let res: PubResult<_> = res.context("sup").map_err(Into::into);
80        let res: PubResult<()> = res.context("bro").map_err(Into::into);
81        let err: Error = res.unwrap_err();
82        assert!(matches!(
83            err.root_cause().downcast_ref::<Error>(),
84            Some(Error::UnsupportedFilesystem)
85        ));
86        assert!(err.root_cause_is_unsupported_filesystem());
87    }
88}