spacedb_store/error.rs
1//! The store error taxonomy.
2//!
3//! Kept deliberately small in S1 — it covers the engine seam, the two codecs,
4//! and table addressing. Later slices extend it additively:
5//! - **S2** adds `Crypto` / `Cold` for the AEAD value boundary + the cold-gate.
6//! - **S3** adds `Schema` / `SchemaTooNew` for the `_meta` refuse-or-migrate gate.
7//!
8//! Every variant carries an owned `String` rather than borrowing the underlying
9//! engine error type, so `StoreError` stays engine-agnostic (a redb storage
10//! error and an in-memory poisoned-lock error both flatten to `Engine`).
11
12use thiserror::Error;
13
14/// The result type returned throughout `spacedb-store`.
15pub type StoreResult<T> = Result<T, StoreError>;
16
17#[derive(Debug, Error)]
18pub enum StoreError {
19 /// A failure originating in the underlying storage engine (redb I/O, a
20 /// transaction/commit failure, a poisoned in-memory lock). The string is
21 /// the engine's own message, preserved for diagnostics.
22 #[error("engine: {0}")]
23 Engine(String),
24
25 /// A value failed to (de)serialize through the `postcard` codec. A decode
26 /// failure here means stored bytes don't match the caller's value type —
27 /// either a schema mismatch or corruption.
28 #[error("value codec: {0}")]
29 ValueCodec(String),
30
31 /// A key failed to decode from its order-preserving byte encoding. Indicates
32 /// a malformed key on disk or a key/type mismatch at a `Table<K, V>` callsite.
33 #[error("key decode: {0}")]
34 KeyDecode(String),
35
36 /// The vault is locked, so no key material is available for the AEAD value
37 /// boundary. Surfaced from [`crate::crypto::CryptoError::Cold`]; callers treat
38 /// it as "unlock required", not data loss.
39 #[error("vault is cold; unlock required")]
40 Cold,
41
42 /// An AEAD operation failed — corrupt ciphertext, a wrong key, or a row/DEK
43 /// presented at the wrong location (AAD mismatch). Carries the underlying
44 /// [`crate::crypto::CryptoError`] message.
45 #[error("crypto: {0}")]
46 Crypto(String),
47
48 /// A collection was opened that has no DEK wrapping yet (use the
49 /// create-or-open path to provision one).
50 #[error("collection not found: {0}")]
51 CollectionNotFound(String),
52
53 /// A collection name collides with a reserved table (the `_`-prefixed names
54 /// the store uses internally, e.g. `_dek_wrappings`, `_meta`).
55 #[error("reserved collection name: {0}")]
56 ReservedName(String),
57
58 /// The on-disk store format is **newer** than this software supports. The
59 /// store refuses to open rather than risk misreading a future format — the
60 /// "never silently open a newer format" rule.
61 #[error("store format version {found} is newer than supported {supported}; upgrade the software")]
62 SchemaTooNew { found: u32, supported: u32 },
63
64 /// A schema/migration problem — e.g. an older store with no registered
65 /// migration to bring it to the current format version.
66 #[error("schema: {0}")]
67 Schema(String),
68
69 /// A prefixed row's format byte or compressed frame failed to decode. The
70 /// bytes were AEAD-authenticated, so this is corruption or a format-version
71 /// mix-up (e.g. a legacy binary's rows read as prefixed) — never attacker
72 /// input. Fails loudly rather than decoding garbage.
73 #[error("row compression: {0}")]
74 Compression(String),
75}
76
77impl StoreError {
78 pub(crate) fn engine(e: impl std::fmt::Display) -> Self {
79 StoreError::Engine(e.to_string())
80 }
81
82 pub(crate) fn value_codec(e: impl std::fmt::Display) -> Self {
83 StoreError::ValueCodec(e.to_string())
84 }
85
86 pub(crate) fn key_decode(msg: impl Into<String>) -> Self {
87 StoreError::KeyDecode(msg.into())
88 }
89}