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
use thiserror::Error;
#[derive(Debug, Error)]
pub enum TopoError {
#[error("storage error: {0}")]
Storage(Box<redb::Error>),
/// The database file is exclusively locked by another live handle
/// (usually another process). Transient by nature — callers may retry
/// with backoff; every other error variant is not retryable this way.
#[error("database is held by another process (lock not acquired)")]
Busy,
#[error("encoding error: {0}")]
Encoding(String),
/// An invalid request, rejected before anything was committed. Raised by
/// both write paths (a bad op in a batch) and read paths (e.g. querying a
/// prop that isn't equality-indexed), so the message stays neutral — the
/// inner string says what was actually wrong.
#[error("rejected: {0}")]
Rejected(String),
#[error("op log compacted; oldest retained seq is {oldest}")]
Compacted { oldest: u64 },
#[error("database closed")]
Closed,
#[error("unsupported format version {found} (this build supports up to {supported})")]
UnsupportedFormat { found: u32, supported: u32 },
}
impl From<redb::Error> for TopoError {
fn from(e: redb::Error) -> Self {
TopoError::Storage(Box::new(e))
}
}
/// Converts any redb sub-error (`TableError`, `TransactionError`,
/// `StorageError`, ...) into a boxed [`TopoError::Storage`] in one step —
/// the call-site replacement for the old two-hop
/// `.map_err(redb::Error::from)?` (redb-suberror → `redb::Error` → `?`
/// via `#[from]`). `redb::Error` itself already implements `Into<redb::Error>`
/// (identity), so this also covers sites that already had a `redb::Error`.
pub(crate) fn storage_err(e: impl Into<redb::Error>) -> TopoError {
TopoError::Storage(Box::new(e.into()))
}
/// Maps a redb open failure: lock contention becomes the typed, retryable
/// [`TopoError::Busy`]; everything else stays a storage error. Every call
/// site that opens the underlying redb database must use this (there are
/// two: `open_with_options` and `read_persisted_index_spec`) so a held
/// file is `Busy` no matter which open path reaches it first.
pub(crate) fn open_err(e: redb::DatabaseError) -> TopoError {
match e {
redb::DatabaseError::DatabaseAlreadyOpen => TopoError::Busy,
other => storage_err(other),
}
}