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
//! Alexandria specific error handling
//!
//! Generally, not all errors can be expressed as one, and many of the
//! errors that happen internally are filtered and repacked into a set
//! of common errors that users of the library will have to deal with.
//! They are most commonly related to user mistakes, scheduling
//! problems, etc.
//!
//! However there are some errors that the database itself can't
//! handle, and so it has to bubble up via the `IternalError` variant
//! on `Error`.  These can be bugs in Alexandria itself, or some
//! runtime constraint like having run out of memory or disk space.

use std::fmt::{self, Display, Formatter};

/// Common alexandria error fascade
#[derive(Debug, thiserror::Error)]
pub enum Error {
    #[error("failed to add a user that already exits")]
    UserAlreadyExists,

    #[error("operation failed because user `{}` doesn't exist", id)]
    NoSuchUser { id: String },

    #[error("failed to initialise library at offset `{}`", offset)]
    InitFailed { offset: String },

    #[error("failed to perform action because user `{}` is locked", id)]
    UserNotOpen { id: String },

    #[error("bad unlock token (password?) for id `{}`", id)]
    UnlockFailed { id: String },

    #[error("tried to operate on locked encrypted state: {}", msg)]
    LockedState { msg: String },

    #[error("tried to unlock user Id `{}` twice", id)]
    AlreadyUnlocked { id: String },

    #[error("no such path: `{}`", path)]
    NoSuchPath { path: String },

    #[error("path exists already: {}", path)]
    PathExists { path: String },

    #[error("failed to load data: `{}`", msg)]
    LoadFailed { msg: String },

    #[error("failed to sync data: `{}`", msg)]
    SyncFailed { msg: String },

    #[error("tried to apply Diff of incompatible type")]
    BadDiffType,

    #[error("a Diff failed to apply: \n{}", msgs)]
    BadDiff { msgs: DiffErrors },

    #[error(
        "can't merge two iterators with different underlying queries: a: '{}', b: '{}'",
        q1,
        q2
    )]
    IncompatibleQuery { q1: String, q2: String },

    #[doc(hidden)]
    #[error("An alexandria internal error occured: `{}`", msg)]
    InternalError { msg: String },
}

/// A convenience alias to contain a common alexandria error
pub type Result<T> = std::result::Result<T, Error>;

/// Span info errors that can occur while applying a diff to a record
#[derive(Debug)]
pub struct DiffErrors(Vec<(usize, String)>);

impl DiffErrors {
    pub(crate) fn add(mut self, new: Self) -> Self {
        let mut ctr = self.0.len();
        new.0.into_iter().for_each(|(_, e)| {
            self.0.push((ctr, e));
            ctr += 1;
        });

        self
    }

    /// Helper function to apply text replacements to nested messages
    pub(crate) fn replace_text<'n, 'o>(self, old: &'o str, new: &'n str) -> Self {
        Self(
            self.0
                .into_iter()
                .map(|(i, s)| (i, s.as_str().replace(old, new).into()))
                .collect(),
        )
    }
}

impl Display for DiffErrors {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        self.0.iter().fold(Ok(()), |res, (num, msg)| {
            res.and_then(|_| write!(f, r#"{}: "{}""#, num, msg))
        })
    }
}

impl From<Vec<(usize, String)>> for DiffErrors {
    fn from(vec: Vec<(usize, String)>) -> Self {
        Self(vec)
    }
}

impl From<(usize, String)> for DiffErrors {
    fn from(tup: (usize, String)) -> Self {
        Self(vec![tup])
    }
}

impl From<DiffErrors> for Error {
    fn from(msgs: DiffErrors) -> Self {
        Error::BadDiff { msgs }
    }
}

impl From<bincode::Error> for Error {
    fn from(be: bincode::Error) -> Self {
        use bincode::ErrorKind::*;

        // FIXME: this isn't great but like... whatevs
        let msg = match *be {
            Io(e) => format!("I/O error: '{}'", e),
            SizeLimit => "Payload too large!".into(),
            SequenceMustHaveLength => "Internal sequencing error".into(),
            _ => "Unknown encoding error".into(),
        };

        Self::SyncFailed { msg }
    }
}

impl From<async_std::io::Error> for Error {
    fn from(e: async_std::io::Error) -> Self {
        Self::LoadFailed {
            msg: format!("{}", e),
        }
    }
}