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}