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.
56 Database(postgres::Error),
57 /// No connection became free within the pool's checkout timeout.
58 PoolTimeout,
59 /// A migration could not be applied or checked.
60 Migration(String),
61}
62
63impl Error {
64 /// The core's error code, for [`Error::Invalid`].
65 pub fn code(&self) -> Option<ErrorCode> {
66 match self {
67 Error::Invalid(e) => Some(e.code()),
68 _ => None,
69 }
70 }
71}
72
73impl fmt::Display for Error {
74 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75 match self {
76 Error::Invalid(e) => write!(f, "invalid input: {e}"),
77 Error::NotLater { what, key, given, latest } => {
78 write!(f, "{what} {key}: {given} is not later than the latest, {latest}")
79 }
80 Error::NotFound { what, key } => write!(f, "no {what} {key} in the store"),
81 Error::Mismatch { what, expected, found } => {
82 write!(f, "the receipt's {what} is {found}, not {expected}")
83 }
84 Error::Conflict { hash } => {
85 write!(f, "receipt {hash} is stored with another valid signature; kept the stored one")
86 }
87 Error::Database(e) => match e.as_db_error() {
88 Some(d) => write!(f, "database: {} ({})", d.message(), d.code().code()),
89 None => write!(f, "database: {e}"),
90 },
91 Error::PoolTimeout => f.write_str("no database connection became free in time"),
92 Error::Migration(m) => write!(f, "migration: {m}"),
93 }
94 }
95}
96
97impl std::error::Error for Error {
98 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
99 match self {
100 Error::Invalid(e) => Some(e),
101 Error::Database(e) => Some(e),
102 _ => None,
103 }
104 }
105}
106
107impl From<postgres::Error> for Error {
108 fn from(e: postgres::Error) -> Error {
109 Error::Database(e)
110 }
111}
112
113impl From<datumesh_core::Error> for Error {
114 fn from(e: datumesh_core::Error) -> Error {
115 Error::Invalid(e)
116 }
117}
118
119/// `Result` with the store's [`Error`].
120pub type Result<T> = core::result::Result<T, Error>;