Skip to main content

spectra_core/
error.rs

1use std::error::Error as StdError;
2
3use thiserror::Error;
4
5/// Result type returned by Spectra operations.
6///
7/// # Examples
8///
9/// ```
10/// use spectra_core::Result;
11///
12/// fn validate_name(name: &str) -> Result<()> {
13///     if name.is_empty() {
14///         return Err(spectra_core::Error::Internal("metric name is empty".into()));
15///     }
16///     Ok(())
17/// }
18///
19/// assert!(validate_name("cache_hits").is_ok());
20/// ```
21pub type Result<T> = std::result::Result<T, Error>;
22
23/// Errors returned by Spectra storage, routing, serialization, and I/O paths.
24///
25/// Backend-specific failures that do not map to I/O or JSON are reported as
26/// [`Storage`](Self::Storage) (with an optional [`Error::source`] chain) or
27/// [`Config`](Self::Config) for builder/wiring mistakes. [`Internal`](Self::Internal)
28/// is reserved for invariant violations and parse bugs.
29///
30/// # Examples
31///
32/// ```
33/// use spectra_core::Error;
34///
35/// let error = Error::config("metrics backend is required");
36/// assert_eq!(error.to_string(), "config error: metrics backend is required");
37///
38/// match error {
39///     Error::Config(message) => assert!(message.contains("backend")),
40///     Error::Io(_)
41///     | Error::Json(_)
42///     | Error::Storage { .. }
43///     | Error::Internal(_)
44///     | Error::PersistQueueClosed
45///     | _ => {
46///         unreachable!()
47///     }
48/// }
49/// ```
50#[non_exhaustive]
51#[derive(Debug, Error)]
52pub enum Error {
53    /// Underlying filesystem or stream I/O failure.
54    #[error("io error: {0}")]
55    Io(#[from] std::io::Error),
56    /// JSON serialization or deserialization failure.
57    #[error("json error: {0}")]
58    Json(#[from] serde_json::Error),
59    /// Storage backend failure (SQLite, remote engine, etc.).
60    #[error("storage error: {message}")]
61    Storage {
62        /// Human-readable summary (stable for logs and hosts).
63        message: String,
64        /// Optional underlying backend error for `Error::source` chains.
65        #[source]
66        source: Option<Box<dyn StdError + Send + Sync>>,
67    },
68    /// Builder, wiring, or configuration mistake.
69    #[error("config error: {0}")]
70    Config(String),
71    /// Catch-all for invariant violations and unexpected parse bugs.
72    #[error("{0}")]
73    Internal(String),
74    /// Persist worker channel closed before a flush or enqueue ack completed.
75    #[error("persist queue closed")]
76    PersistQueueClosed,
77}
78
79impl Error {
80    /// Storage failure without an underlying source.
81    pub fn storage(message: impl Into<String>) -> Self {
82        Self::Storage {
83            message: message.into(),
84            source: None,
85        }
86    }
87
88    /// Storage failure wrapping an underlying error.
89    pub fn storage_source(
90        message: impl Into<String>,
91        source: impl StdError + Send + Sync + 'static,
92    ) -> Self {
93        Self::Storage {
94            message: message.into(),
95            source: Some(Box::new(source)),
96        }
97    }
98
99    /// Configuration / builder failure.
100    pub fn config(message: impl Into<String>) -> Self {
101        Self::Config(message.into())
102    }
103
104    /// Invariant or unexpected internal failure.
105    pub fn internal(message: impl Into<String>) -> Self {
106        Self::Internal(message.into())
107    }
108}
109
110#[cfg(test)]
111mod tests {
112    use std::error::Error as StdError;
113
114    use super::Error;
115
116    #[test]
117    fn storage_source_preserves_chain() {
118        let err = Error::storage_source("sqlite open failed", std::io::Error::other("disk full"));
119        match &err {
120            Error::Storage {
121                message,
122                source: Some(_),
123            } => assert!(message.contains("sqlite")),
124            _ => panic!("expected Storage with source"),
125        }
126        assert!(err.source().is_some());
127    }
128
129    #[test]
130    fn config_display() {
131        let err = Error::config("metrics_backend is required");
132        assert_eq!(err.to_string(), "config error: metrics_backend is required");
133    }
134}