dig_store/error.rs
1//! The `dig-store` error taxonomy (SPEC §6).
2//!
3//! Every fallible operation returns [`DigStoreResult`]. The variants are deliberately coarse and
4//! stable — a consumer matches on the kind, not on a message string — and each carries a
5//! human-readable detail for logs. New variants are added additively; existing ones never change
6//! meaning (CLAUDE.md §5.1 back-compat discipline applies to the public error surface too).
7
8use thiserror::Error;
9
10/// The result type every `dig-store` operation returns.
11pub type DigStoreResult<T> = Result<T, DigStoreError>;
12
13/// Everything that can go wrong composing, spending, or reading a DIG store.
14#[derive(Debug, Error)]
15pub enum DigStoreError {
16 /// A size value is outside the valid ladder (`k ∈ 0..=10`, i.e. 1 MB..1 GB) — see
17 /// [`crate::SizeBucket`]. Mirrors `dig_merkle::MerkleError::InvalidSize`.
18 #[error("invalid store size: {0}")]
19 InvalidSize(String),
20
21 /// A downloaded `.dig`'s real size does not match the size the store anchored on chain, so it
22 /// MUST be discarded rather than stored or served (SPEC §4, NC-9). Carries the anchored bucket
23 /// and the observed byte length for diagnostics.
24 #[error("size-proof mismatch: .dig is {actual_bytes} bytes (bucket {actual_k}) but the store anchored bucket {anchored_k}; discarding")]
25 SizeProofMismatch {
26 /// The power-of-2 exponent the store anchored on chain.
27 anchored_k: u8,
28 /// The power-of-2 exponent the downloaded `.dig`'s real byte length falls into.
29 actual_k: u8,
30 /// The downloaded `.dig`'s real byte length.
31 actual_bytes: u64,
32 },
33
34 /// A URN string could not be parsed or a store id / root hash was malformed.
35 #[error("invalid URN or identifier: {0}")]
36 InvalidUrn(String),
37
38 /// An on-chain read could not prove the store's integrity/validity against the chain (NC-9):
39 /// the coin was absent, the lineage did not verify, or the chain source failed.
40 #[error("on-chain proof failed: {0}")]
41 Proof(String),
42
43 /// The underlying `.dig` capsule (`dig-capsule`) could not be opened, parsed, or read, or its
44 /// declared `store_id` did not match a trusted anchor. Returned by the off-chain capsule getters
45 /// ([`crate::get_capsule_identity`] / [`crate::open_capsule`], SPEC §5/§11): unreadable/tampered
46 /// module bytes and a `store_id`-vs-anchor mismatch both surface here (fail-closed).
47 #[error("capsule error: {0}")]
48 Capsule(String),
49
50 /// A spend could not be constructed by the on-chain anchor (`dig-merkle`).
51 #[error("spend-build error: {0}")]
52 Spend(String),
53}
54
55impl From<dig_merkle::MerkleError> for DigStoreError {
56 /// Maps a `dig-merkle` failure into the store taxonomy. An out-of-range size stays an
57 /// [`DigStoreError::InvalidSize`]; every other `dig-merkle` error is a spend-construction failure
58 /// ([`DigStoreError::Spend`]). Read paths that need the [`DigStoreError::Proof`] framing map
59 /// explicitly at the call site rather than through this conversion.
60 fn from(error: dig_merkle::MerkleError) -> Self {
61 match error {
62 dig_merkle::MerkleError::InvalidSize(message) => DigStoreError::InvalidSize(message),
63 other => DigStoreError::Spend(other.to_string()),
64 }
65 }
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71
72 /// An out-of-range `dig-merkle` size maps to [`DigStoreError::InvalidSize`], preserving the detail.
73 #[test]
74 fn merkle_invalid_size_maps_to_invalid_size() {
75 let err: DigStoreError = dig_merkle::MerkleError::InvalidSize("too big".into()).into();
76 match err {
77 DigStoreError::InvalidSize(message) => assert_eq!(message, "too big"),
78 other => panic!("expected InvalidSize, got {other:?}"),
79 }
80 }
81
82 /// Every other `dig-merkle` failure maps to [`DigStoreError::Spend`] (a spend-construction error).
83 #[test]
84 fn other_merkle_errors_map_to_spend() {
85 let err: DigStoreError = dig_merkle::MerkleError::NotDataStore.into();
86 assert!(matches!(err, DigStoreError::Spend(_)));
87 }
88
89 /// The `Display` rendering carries the mismatch detail for logs.
90 #[test]
91 fn size_proof_mismatch_displays_detail() {
92 let err = DigStoreError::SizeProofMismatch {
93 anchored_k: 7,
94 actual_k: 6,
95 actual_bytes: 64,
96 };
97 assert!(err.to_string().contains("size-proof mismatch"));
98 }
99}