Skip to main content

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}