Skip to main content

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}