Skip to main content

cuenv_cas/
error.rs

1//! Error types for the content-addressed store.
2
3use miette::Diagnostic;
4use std::path::Path;
5use thiserror::Error;
6
7/// Error type for CAS and action cache operations.
8#[derive(Error, Debug, Diagnostic)]
9pub enum Error {
10    /// I/O error while reading or writing a blob.
11    #[error("I/O {operation} failed{}", path.as_ref().map_or(String::new(), |p| format!(": {}", p.display())))]
12    #[diagnostic(
13        code(cuenv::cas::io),
14        help("Check file permissions and that the cache directory is writable")
15    )]
16    Io {
17        /// Underlying I/O error.
18        #[source]
19        source: std::io::Error,
20        /// Path involved in the failing operation, if any.
21        path: Option<Box<Path>>,
22        /// Short label describing the operation (e.g. "read", "rename").
23        operation: String,
24    },
25
26    /// Configuration or validation error.
27    #[error("CAS configuration error: {message}")]
28    #[diagnostic(code(cuenv::cas::config))]
29    Configuration {
30        /// Human-readable description.
31        message: String,
32    },
33
34    /// Requested digest was not present in the store.
35    #[error("digest not found in CAS: {digest}")]
36    #[diagnostic(
37        code(cuenv::cas::not_found),
38        help("The blob may have been garbage collected or never written")
39    )]
40    NotFound {
41        /// Digest that was looked up.
42        digest: String,
43    },
44
45    /// Digest mismatch during verification (corruption or wrong digest provided).
46    #[error("digest mismatch: expected {expected}, got {actual}")]
47    #[diagnostic(code(cuenv::cas::digest_mismatch))]
48    DigestMismatch {
49        /// The digest that was claimed.
50        expected: String,
51        /// The digest that was computed from the bytes.
52        actual: String,
53    },
54
55    /// JSON encode/decode failure for a CAS-persisted message.
56    #[error("serialization error: {message}")]
57    #[diagnostic(code(cuenv::cas::serialization))]
58    Serialization {
59        /// Human-readable description.
60        message: String,
61    },
62}
63
64impl Error {
65    /// Build a configuration error.
66    #[must_use]
67    pub fn configuration(msg: impl Into<String>) -> Self {
68        Self::Configuration {
69            message: msg.into(),
70        }
71    }
72
73    /// Build an I/O error with path context.
74    #[must_use]
75    pub fn io(
76        source: std::io::Error,
77        path: impl AsRef<Path>,
78        operation: impl Into<String>,
79    ) -> Self {
80        Self::Io {
81            source,
82            path: Some(path.as_ref().into()),
83            operation: operation.into(),
84        }
85    }
86
87    /// Build an I/O error without path context.
88    #[must_use]
89    pub fn io_no_path(source: std::io::Error, operation: impl Into<String>) -> Self {
90        Self::Io {
91            source,
92            path: None,
93            operation: operation.into(),
94        }
95    }
96
97    /// Build a not-found error.
98    #[must_use]
99    pub fn not_found(digest: impl Into<String>) -> Self {
100        Self::NotFound {
101            digest: digest.into(),
102        }
103    }
104
105    /// Build a digest-mismatch error.
106    #[must_use]
107    pub fn digest_mismatch(expected: impl Into<String>, actual: impl Into<String>) -> Self {
108        Self::DigestMismatch {
109            expected: expected.into(),
110            actual: actual.into(),
111        }
112    }
113
114    /// Build a serialization error.
115    #[must_use]
116    pub fn serialization(msg: impl Into<String>) -> Self {
117        Self::Serialization {
118            message: msg.into(),
119        }
120    }
121}
122
123/// Convenience alias.
124pub type Result<T> = std::result::Result<T, Error>;