1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
use derive_more::Display;
use std::error::Error;

#[derive(Debug, Display)]
pub enum BlockStoreError {
    /// Error when interacting with the sqlite database
    #[display(
        fmt = "sqlite error while {}: {} caused by {:?}",
        _1,
        _0,
        "_0.source().map(std::string::ToString::to_string)"
    )]
    SqliteError(rusqlite::Error, &'static str),
    /// Error convering from a cid to a fixed sized representation.
    /// This can be caused by hashes with more than 32 bytes of size
    #[display(fmt = "error packing a CID into fixed size: {}", _0)]
    CidError(libipld::cid::Error),
    /// Error when converting i64 from sqlite to u64.
    /// This is unlikely to ever happen.
    #[display(
        fmt = "DB corrupted, got unsuitable integer value while {}: {}",
        _1,
        _0
    )]
    TryFromIntError(std::num::TryFromIntError, &'static str),
    #[display(fmt = "cannot open additional connection for in-memory DB")]
    NoAdditionalInMemory,
    /// Other error
    Other(anyhow::Error),
}

impl From<anyhow::Error> for BlockStoreError {
    fn from(v: anyhow::Error) -> Self {
        Self::Other(v)
    }
}

impl From<libipld::cid::Error> for BlockStoreError {
    fn from(v: libipld::cid::Error) -> Self {
        Self::CidError(v)
    }
}

impl std::error::Error for BlockStoreError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            BlockStoreError::SqliteError(_e, _) => None,
            BlockStoreError::CidError(e) => Some(e),
            BlockStoreError::TryFromIntError(e, _) => Some(e),
            BlockStoreError::Other(e) => AsRef::<dyn Error>::as_ref(e).source(),
            BlockStoreError::NoAdditionalInMemory => None,
        }
    }
}

pub type Result<T> = std::result::Result<T, BlockStoreError>;

pub(crate) trait Context {
    type Output;
    fn ctx(self, s: &'static str) -> Self::Output;
}

impl<T> Context for std::result::Result<T, rusqlite::Error> {
    type Output = crate::Result<T>;

    fn ctx(self, s: &'static str) -> Self::Output {
        self.map_err(|e| BlockStoreError::SqliteError(e, s))
    }
}

impl<T> Context for std::result::Result<T, std::num::TryFromIntError> {
    type Output = crate::Result<T>;

    fn ctx(self, s: &'static str) -> Self::Output {
        self.map_err(|e| BlockStoreError::TryFromIntError(e, s))
    }
}

#[cfg(test)]
mod tests {
    use super::Context;
    use crate::BlockStoreError;
    use anyhow::Context as _;

    #[test]
    fn show() {
        let sqlite = std::result::Result::<(), _>::Err(rusqlite::Error::SqliteFailure(
            rusqlite::ffi::Error::new(517),
            Some("sql string".to_owned()),
        ));

        assert_eq!(format!("x {:?}", sqlite), "x Err(SqliteFailure(Error { code: DatabaseBusy, extended_code: 517 }, Some(\"sql string\")))");
        if let Err(ref sqlite) = sqlite {
            assert_eq!(format!("x {}", sqlite), "x sql string");
            assert_eq!(format!("x {:#}", sqlite), "x sql string");
        }

        let db = sqlite.ctx("first");

        assert_eq!(format!("x {:?}", db), "x Err(SqliteError(SqliteFailure(Error { code: DatabaseBusy, extended_code: 517 }, Some(\"sql string\")), \"first\"))");
        if let Err(ref db) = db {
            assert_eq!(
                format!("x {}", db),
                "x sqlite error while first: sql string caused by \
                Some(\"Error code 517: \
                    Cannot promote read transaction to write transaction because of writes by another connection\")"
            );
            assert_eq!(
                format!("x {:#}", db),
                "x sqlite error while first: sql string caused by \
                Some(\"Error code 517: \
                    Cannot promote read transaction to write transaction because of writes by another connection\")"
            );
        }

        let app = db.context("second").map_err(BlockStoreError::from);

        assert_eq!(
            format!("x {:?}", app),
            r#"x Err(Other(second

Caused by:
    sqlite error while first: sql string caused by Some("Error code 517: Cannot promote read transaction to write transaction because of writes by another connection")))"#
        );
        if let Err(ref app) = app {
            assert_eq!(format!("x {}", app), "x second");
            assert_eq!(format!("x {:#}", app), "x second: \
                sqlite error while first: \
                sql string caused by Some(\"Error code 517: \
                Cannot promote read transaction to write transaction because of writes by another connection\")");
        }
    }

    #[test]
    fn double() {
        let e = BlockStoreError::Other(anyhow::anyhow!("hello"));
        assert_eq!(format!("{}", e), "hello");
        assert_eq!(format!("{:#}", e), "hello");

        let e = Result::<(), _>::Err(e).context("world").unwrap_err();
        assert_eq!(format!("{:#}", e), "world: hello");

        assert_eq!(e.to_string(), "world");
        let e = e.source().unwrap();
        assert_eq!(e.to_string(), "hello");
        assert!(e.source().is_none());
    }
}