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, or the driver's
56 /// description of a failure that is not the server's (no connection, a URL that does
57 /// not parse, a TLS handshake refused, a certificate that did not verify).
58 Database(DatabaseError),
59 /// No connection became free within the pool's checkout timeout.
60 PoolTimeout,
61 /// A migration could not be applied or checked.
62 Migration(String),
63 /// A connection option is unusable: a root certificate bundle
64 /// ([`PoolOptions::tls_root_certificates_pem`](crate::PoolOptions::tls_root_certificates_pem))
65 /// with no certificate, or one that does not parse.
66 Config(String),
67}
68
69/// A database failure, [`Error::Database`]: what the server said (its message and
70/// SQLSTATE), or the driver's description of a failure that is not the server's.
71///
72/// Its [`source`](std::error::Error::source) is the driver's error (type erased: the
73/// driver's types are not part of this crate's API outside
74/// [`pg_unstable`](crate::pg_unstable)).
75#[derive(Debug)]
76pub struct DatabaseError {
77 message: String,
78 code: Option<String>,
79 source: postgres::Error,
80}
81
82impl DatabaseError {
83 pub(crate) fn new(e: postgres::Error) -> DatabaseError {
84 let (message, code) = match e.as_db_error() {
85 Some(d) => (d.message().to_string(), Some(d.code().code().to_string())),
86 None => (driver_text(&e), None),
87 };
88 DatabaseError { message, code, source: e }
89 }
90
91 /// The server's message (`relation "versions" does not exist`), or the driver's text
92 /// and its causes when the server said nothing (`error connecting to server: ...`,
93 /// `error performing TLS handshake: invalid peer certificate: ...`).
94 pub fn message(&self) -> &str {
95 &self.message
96 }
97
98 /// The SQLSTATE the server reported (five characters, e.g. `42P01`), if it was the
99 /// server's error.
100 pub fn code(&self) -> Option<&str> {
101 self.code.as_deref()
102 }
103}
104
105/// The driver's error and its sources (`error performing TLS handshake: invalid peer
106/// certificate: UnknownIssuer`): the driver's own text often omits the cause.
107fn driver_text(e: &postgres::Error) -> String {
108 let mut text = e.to_string();
109 let mut source = std::error::Error::source(e);
110 while let Some(s) = source {
111 let s_text = s.to_string();
112 if !text.contains(&s_text) {
113 text.push_str(": ");
114 text.push_str(&s_text);
115 }
116 source = s.source();
117 }
118 text
119}
120
121impl fmt::Display for DatabaseError {
122 /// The message, then the SQLSTATE in parentheses when there is one.
123 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124 match &self.code {
125 Some(c) => write!(f, "{} ({c})", self.message),
126 None => f.write_str(&self.message),
127 }
128 }
129}
130
131impl std::error::Error for DatabaseError {
132 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
133 Some(&self.source)
134 }
135}
136
137impl Error {
138 /// The core's error code, for [`Error::Invalid`].
139 pub fn code(&self) -> Option<ErrorCode> {
140 match self {
141 Error::Invalid(e) => Some(e.code()),
142 _ => None,
143 }
144 }
145}
146
147impl fmt::Display for Error {
148 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149 match self {
150 Error::Invalid(e) => write!(f, "invalid input: {e}"),
151 Error::NotLater { what, key, given, latest } => {
152 write!(f, "{what} {key}: {given} is not later than the latest, {latest}")
153 }
154 Error::NotFound { what, key } => write!(f, "no {what} {key} in the store"),
155 Error::Mismatch { what, expected, found } => {
156 write!(f, "the receipt's {what} is {found}, not {expected}")
157 }
158 Error::Conflict { hash } => {
159 write!(f, "receipt {hash} is stored with another valid signature; kept the stored one")
160 }
161 Error::Database(e) => write!(f, "database: {e}"),
162 Error::PoolTimeout => f.write_str("no database connection became free in time"),
163 Error::Migration(m) => write!(f, "migration: {m}"),
164 Error::Config(m) => write!(f, "configuration: {m}"),
165 }
166 }
167}
168
169impl std::error::Error for Error {
170 /// For [`Error::Database`], the driver's error (as in 0.1.0; the [`DatabaseError`] is
171 /// this error's own text, so a chain of sources does not repeat it).
172 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
173 match self {
174 Error::Invalid(e) => Some(e),
175 Error::Database(e) => std::error::Error::source(e),
176 _ => None,
177 }
178 }
179}
180
181/// `?` on the driver's results, inside the crate: `.db()?` makes an [`Error::Database`].
182pub(crate) trait Db<T> {
183 fn db(self) -> Result<T>;
184}
185
186impl<T> Db<T> for core::result::Result<T, postgres::Error> {
187 fn db(self) -> Result<T> {
188 self.map_err(|e| Error::Database(DatabaseError::new(e)))
189 }
190}
191
192impl From<datumesh_core::Error> for Error {
193 fn from(e: datumesh_core::Error) -> Error {
194 Error::Invalid(e)
195 }
196}
197
198/// `Result` with the store's [`Error`].
199pub type Result<T> = core::result::Result<T, Error>;