dig_store_cache/error.rs
1//! The cache's error taxonomy.
2//!
3//! Every fallible cache operation returns [`CacheError`]. The variants are stable, catalogued, and
4//! carry the identity/path context a caller (or an operator reading a log) needs to act — the DIG
5//! agent-friendly contract (§6.2): no stringly-typed failures, no scraping prose to learn what broke.
6
7use dig_store::CapsuleIdentity;
8use std::path::PathBuf;
9
10/// A failure of a cache operation.
11#[derive(Debug, thiserror::Error)]
12pub enum CacheError {
13 /// A filesystem operation failed. Carries the path it was acting on so the fault is locatable.
14 #[error("i/o error at {path}: {source}")]
15 Io {
16 /// The path the failing operation touched.
17 path: PathBuf,
18 /// The underlying OS error.
19 #[source]
20 source: std::io::Error,
21 },
22
23 /// A single capsule is larger than the whole cache capacity. Admitting it would require evicting
24 /// everything and STILL not fit, so it is rejected outright — nothing is evicted for a doomed put.
25 #[error("capsule {id:?} is {size} bytes, larger than the cache capacity of {capacity} bytes")]
26 EntryTooLarge {
27 /// The capsule that could never fit.
28 // Boxed to keep `CacheError` (and every `Result<_, CacheError>`) small: a bare
29 // `CapsuleIdentity` is 64 bytes, which would otherwise bloat the common `Result` on the hot
30 // admit path (clippy::result_large_err).
31 id: Box<CapsuleIdentity>,
32 /// Its size in bytes.
33 size: u64,
34 /// The cache's total capacity in bytes.
35 capacity: u64,
36 },
37
38 /// `PutOptions::check_identity` was set and the bytes' recovered `(store_id, root_hash)` did not
39 /// equal the claimed identity. The bytes are NOT admitted.
40 #[error("capsule bytes declare {recovered:?} but the caller claimed {claimed:?}")]
41 IdentityMismatch {
42 /// The identity the caller passed to `put` (boxed — see [`CacheError::EntryTooLarge`]).
43 claimed: Box<CapsuleIdentity>,
44 /// The identity recovered from the bytes.
45 recovered: Box<CapsuleIdentity>,
46 },
47
48 /// A cached `.dig` file could not be read back as a capsule (during `open` orphan recovery, or a
49 /// `check_identity` read of unparseable bytes). Disk stays authoritative — the entry is skipped.
50 #[error("cached capsule at {path} is corrupt: {reason}")]
51 CorruptEntry {
52 /// The offending file.
53 path: PathBuf,
54 /// Why it could not be read as a capsule.
55 reason: String,
56 },
57
58 /// The cache root cannot be created or written to (bad path, permissions, read-only volume).
59 #[error("cache root {root} is not writable: {source}")]
60 RootNotWritable {
61 /// The root directory the caller passed to [`Cache::open`](crate::Cache::open).
62 root: PathBuf,
63 /// The underlying OS error.
64 #[source]
65 source: std::io::Error,
66 },
67}
68
69impl CacheError {
70 /// Build an [`CacheError::Io`] bound to the path the operation touched.
71 pub(crate) fn io(path: impl Into<PathBuf>, source: std::io::Error) -> Self {
72 Self::Io {
73 path: path.into(),
74 source,
75 }
76 }
77}