Skip to main content

datumesh_store/
error.rs

1//! The store's error type.
2
3use std::fmt;
4
5use datumesh_core::ErrorCode;
6
7/// What a [`Store`](crate::Store) method or a migration can fail with.
8#[non_exhaustive]
9#[derive(Debug)]
10pub enum Error {
11    /// The core rejected the input: not canonical, not a receipt, a receipt that does not
12    /// verify against the stored key document, not an instant or hash, a key document that
13    /// does not satisfy SPEC 9.2, ... The core's error carries its code
14    /// ([`Error::code`]), JSON path and detail.
15    Invalid(datumesh_core::Error),
16    /// An append-only history refused a time that is not later than its latest entry:
17    /// `put_version` (`what` = `"version"`, `key` = the cell id), `bind_name` (`"name"`,
18    /// the function name) or `put_namespace` (`"namespace"`, the host; equal is allowed
19    /// there).
20    NotLater {
21        /// `"version"`, `"name"` or `"namespace"`.
22        what: &'static str,
23        /// The cell id, function name or host.
24        key: String,
25        /// The time given.
26        given: String,
27        /// The latest time already recorded.
28        latest: String,
29    },
30    /// Something the write refers to is not in the store: the receipt of a version, the
31    /// descriptor of a name binding.
32    NotFound {
33        /// `"receipt"` or `"descriptor"`.
34        what: &'static str,
35        /// Its hash.
36        key: String,
37    },
38    /// The receipt named by `put_version` is for another cell, or has another `tx`.
39    Mismatch {
40        /// `"cell_id"` or `"tx"`.
41        what: &'static str,
42        /// The value given to `put_version`.
43        expected: String,
44        /// The receipt's value.
45        found: String,
46    },
47    /// A receipt with this hash is stored with other bytes: the same payload with another
48    /// valid signature by the same key (the receipt hash does not cover `sig`; it covers
49    /// `computed_by`, so a rotated key gives another hash). Only a randomised signer makes
50    /// two valid signatures; RFC 8032 Ed25519 is deterministic. The stored receipt is kept.
51    Conflict {
52        /// The receipt hash.
53        hash: String,
54    },
55    /// The database refused or failed: the server's message and SQLSTATE (PostgreSQL) or
56    /// SQLite's message and result code, or the driver's description of a failure that is
57    /// not the database's (no connection, a URL that does not parse, a TLS handshake
58    /// refused, a certificate that did not verify).
59    Database(DatabaseError),
60    /// No connection became free within the pool's checkout timeout.
61    PoolTimeout,
62    /// A migration could not be applied or checked.
63    Migration(String),
64    /// A connection option is unusable: a root certificate bundle
65    /// ([`PoolOptions::tls_root_certificates_pem`](crate::PoolOptions::tls_root_certificates_pem))
66    /// with no certificate, or one that does not parse.
67    Config(String),
68}
69
70/// A database failure, [`Error::Database`]: what the database said (PostgreSQL: the
71/// server's message and SQLSTATE; SQLite: its message and result code), or the driver's
72/// description of a failure that is not the database's.
73///
74/// Its [`source`](std::error::Error::source) is the driver's error (type erased: the
75/// drivers' types are not part of this crate's API outside
76/// [`pg_unstable`](crate::pg_unstable)).
77#[derive(Debug)]
78pub struct DatabaseError {
79    message: String,
80    code: Option<String>,
81    source: Box<dyn std::error::Error + Send + Sync + 'static>,
82}
83
84impl DatabaseError {
85    pub(crate) fn new(e: postgres::Error) -> DatabaseError {
86        let (message, code) = match e.as_db_error() {
87            Some(d) => (d.message().to_string(), Some(d.code().code().to_string())),
88            None => (driver_text(&e), None),
89        };
90        DatabaseError { message, code, source: Box::new(e) }
91    }
92
93    /// A SQLite failure: SQLite's message and the name of its (extended) result code,
94    /// or, for a failure of the driver's own (a value of an unexpected type), its text.
95    #[cfg(feature = "sqlite")]
96    pub(crate) fn sqlite(e: rusqlite::Error) -> DatabaseError {
97        let (message, code) = match &e {
98            rusqlite::Error::SqliteFailure(f, msg) => {
99                (msg.clone().unwrap_or_else(|| f.to_string()), Some(crate::sqlite::result_code_name(f.extended_code)))
100            }
101            other => (other.to_string(), None),
102        };
103        DatabaseError { message, code, source: Box::new(e) }
104    }
105
106    /// A SQLite file that does not exist, for a call that must not create it
107    /// ([`SqliteStore::open_existing`](crate::SqliteStore::open_existing)).
108    #[cfg(feature = "sqlite")]
109    pub(crate) fn no_such_file(path: &std::path::Path) -> DatabaseError {
110        let message = format!("no such database file: {}", path.display());
111        let source = std::io::Error::new(std::io::ErrorKind::NotFound, message.clone());
112        DatabaseError { message, code: Some("SQLITE_CANTOPEN".into()), source: Box::new(source) }
113    }
114
115    /// The database's message (`relation "versions" does not exist`, `no such table:
116    /// versions`), or the driver's text and its causes when the database said nothing
117    /// (`error connecting to server: ...`, `error performing TLS handshake: invalid peer
118    /// certificate: ...`).
119    pub fn message(&self) -> &str {
120        &self.message
121    }
122
123    /// The code the database reported, if it was the database's error: PostgreSQL's
124    /// SQLSTATE (five characters, e.g. `42P01`), or (since 0.4.0) the name of SQLite's
125    /// extended result code (e.g. `SQLITE_ERROR`, `SQLITE_BUSY`,
126    /// `SQLITE_CONSTRAINT_TRIGGER`, `SQLITE_CANTOPEN`).
127    pub fn code(&self) -> Option<&str> {
128        self.code.as_deref()
129    }
130}
131
132/// The driver's error and its sources (`error performing TLS handshake: invalid peer
133/// certificate: UnknownIssuer`): the driver's own text often omits the cause.
134fn driver_text(e: &postgres::Error) -> String {
135    let mut text = e.to_string();
136    let mut source = std::error::Error::source(e);
137    while let Some(s) = source {
138        let s_text = s.to_string();
139        if !text.contains(&s_text) {
140            text.push_str(": ");
141            text.push_str(&s_text);
142        }
143        source = s.source();
144    }
145    text
146}
147
148impl fmt::Display for DatabaseError {
149    /// The message, then the SQLSTATE in parentheses when there is one.
150    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151        match &self.code {
152            Some(c) => write!(f, "{} ({c})", self.message),
153            None => f.write_str(&self.message),
154        }
155    }
156}
157
158impl std::error::Error for DatabaseError {
159    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
160        Some(&*self.source)
161    }
162}
163
164impl Error {
165    /// The core's error code, for [`Error::Invalid`].
166    pub fn code(&self) -> Option<ErrorCode> {
167        match self {
168            Error::Invalid(e) => Some(e.code()),
169            _ => None,
170        }
171    }
172}
173
174impl fmt::Display for Error {
175    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176        match self {
177            Error::Invalid(e) => write!(f, "invalid input: {e}"),
178            Error::NotLater { what, key, given, latest } => {
179                write!(f, "{what} {key}: {given} is not later than the latest, {latest}")
180            }
181            Error::NotFound { what, key } => write!(f, "no {what} {key} in the store"),
182            Error::Mismatch { what, expected, found } => {
183                write!(f, "the receipt's {what} is {found}, not {expected}")
184            }
185            Error::Conflict { hash } => {
186                write!(f, "receipt {hash} is stored with another valid signature; kept the stored one")
187            }
188            Error::Database(e) => write!(f, "database: {e}"),
189            Error::PoolTimeout => f.write_str("no database connection became free in time"),
190            Error::Migration(m) => write!(f, "migration: {m}"),
191            Error::Config(m) => write!(f, "configuration: {m}"),
192        }
193    }
194}
195
196impl std::error::Error for Error {
197    /// For [`Error::Database`], the driver's error (as in 0.1.0; the [`DatabaseError`] is
198    /// this error's own text, so a chain of sources does not repeat it).
199    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
200        match self {
201            Error::Invalid(e) => Some(e),
202            Error::Database(e) => std::error::Error::source(e),
203            _ => None,
204        }
205    }
206}
207
208/// `?` on the driver's results, inside the crate: `.db()?` makes an [`Error::Database`].
209pub(crate) trait Db<T> {
210    fn db(self) -> Result<T>;
211}
212
213impl<T> Db<T> for core::result::Result<T, postgres::Error> {
214    fn db(self) -> Result<T> {
215        self.map_err(|e| Error::Database(DatabaseError::new(e)))
216    }
217}
218
219#[cfg(feature = "sqlite")]
220impl<T> Db<T> for core::result::Result<T, rusqlite::Error> {
221    fn db(self) -> Result<T> {
222        self.map_err(|e| Error::Database(DatabaseError::sqlite(e)))
223    }
224}
225
226impl From<datumesh_core::Error> for Error {
227    fn from(e: datumesh_core::Error) -> Error {
228        Error::Invalid(e)
229    }
230}
231
232/// `Result` with the store's [`Error`].
233pub type Result<T> = core::result::Result<T, Error>;