Skip to main content

cfgmatic_source/domain/
error.rs

1//! Error types for source operations.
2//!
3//! This module provides error types used throughout the crate:
4//!
5//! - [`SourceError`] - Main error type for all source operations
6//! - [`Result`] - Result type alias using [`SourceError`]
7
8use std::path::PathBuf;
9
10/// Error type for source operations.
11///
12/// This enum covers all possible errors that can occur
13/// during source loading, parsing, and processing.
14#[derive(Debug, thiserror::Error)]
15pub enum SourceError {
16    /// Source not found.
17    #[error("Source not found: {0}")]
18    NotFound(String),
19
20    /// Failed to read source.
21    #[error("Failed to read source: {0}")]
22    ReadFailed(String),
23
24    /// Failed to parse source content.
25    #[error("Parse error in {format} at {path}: {message}")]
26    ParseFailed {
27        /// Path or identifier of the source.
28        path: String,
29        /// Format that was being parsed.
30        format: String,
31        /// Error message.
32        message: String,
33    },
34
35    /// Invalid format detected.
36    #[error("Invalid format: expected {expected}, found {found}")]
37    InvalidFormat {
38        /// Expected format.
39        expected: String,
40        /// Found format.
41        found: String,
42    },
43
44    /// IO error.
45    #[error("IO error: {0}")]
46    Io(String),
47
48    /// Environment variable error.
49    #[error("Environment variable error: {0}")]
50    EnvVar(String),
51
52    /// Network error for remote sources.
53    #[error("Network error: {0}")]
54    Network(String),
55
56    /// Serialization/deserialization error.
57    #[error("Serialization error: {0}")]
58    Serialization(String),
59
60    /// Invalid source path.
61    #[error("Invalid path: {0}")]
62    InvalidPath(PathBuf),
63
64    /// Unsupported operation.
65    #[error("Unsupported operation: {0}")]
66    Unsupported(String),
67
68    /// Source validation failed.
69    #[error("Validation failed: {0}")]
70    Validation(String),
71
72    /// Custom error.
73    #[error("{0}")]
74    Custom(String),
75}
76
77/// Result type alias.
78pub type Result<T> = std::result::Result<T, SourceError>;
79
80impl SourceError {
81    /// Create a not found error.
82    #[must_use]
83    pub fn not_found(source: &str) -> Self {
84        Self::NotFound(source.to_string())
85    }
86
87    /// Create a read failed error.
88    #[must_use]
89    pub fn read_failed(source: &str) -> Self {
90        Self::ReadFailed(source.to_string())
91    }
92
93    /// Create a parse failed error.
94    #[must_use]
95    pub fn parse_failed(path: &str, format: &str, message: &str) -> Self {
96        Self::ParseFailed {
97            path: path.to_string(),
98            format: format.to_string(),
99            message: message.to_string(),
100        }
101    }
102
103    /// Create an invalid format error.
104    #[must_use]
105    pub fn invalid_format(expected: &str, found: &str) -> Self {
106        Self::InvalidFormat {
107            expected: expected.to_string(),
108            found: found.to_string(),
109        }
110    }
111
112    /// Create an IO error.
113    #[must_use]
114    pub fn io(message: &str) -> Self {
115        Self::Io(message.to_string())
116    }
117
118    /// Create an environment variable error.
119    #[must_use]
120    pub fn env_var(message: &str) -> Self {
121        Self::EnvVar(message.to_string())
122    }
123
124    /// Create a network error.
125    #[must_use]
126    pub fn network(message: &str) -> Self {
127        Self::Network(message.to_string())
128    }
129
130    /// Create a serialization error.
131    #[must_use]
132    pub fn serialization(message: &str) -> Self {
133        Self::Serialization(message.to_string())
134    }
135
136    /// Create an invalid path error.
137    #[must_use]
138    pub const fn invalid_path(path: PathBuf) -> Self {
139        Self::InvalidPath(path)
140    }
141
142    /// Create an unsupported operation error.
143    #[must_use]
144    pub fn unsupported(operation: &str) -> Self {
145        Self::Unsupported(operation.to_string())
146    }
147
148    /// Create a validation error.
149    #[must_use]
150    pub fn validation(message: &str) -> Self {
151        Self::Validation(message.to_string())
152    }
153
154    /// Create a custom error.
155    #[must_use]
156    pub fn custom(message: &str) -> Self {
157        Self::Custom(message.to_string())
158    }
159
160    /// Check if this is a not found error.
161    #[must_use]
162    pub const fn is_not_found(&self) -> bool {
163        matches!(self, Self::NotFound(_))
164    }
165
166    /// Check if this is a parse error.
167    #[must_use]
168    pub const fn is_parse_failed(&self) -> bool {
169        matches!(self, Self::ParseFailed { .. })
170    }
171
172    /// Check if this is a network error.
173    #[must_use]
174    pub const fn is_network(&self) -> bool {
175        matches!(self, Self::Network(_))
176    }
177
178    /// Check if this is an IO error.
179    #[must_use]
180    pub const fn is_io(&self) -> bool {
181        matches!(self, Self::Io(_))
182    }
183}
184
185impl From<std::io::Error> for SourceError {
186    fn from(err: std::io::Error) -> Self {
187        Self::Io(err.to_string())
188    }
189}
190
191#[cfg(feature = "json")]
192impl From<serde_json::Error> for SourceError {
193    fn from(err: serde_json::Error) -> Self {
194        Self::Serialization(err.to_string())
195    }
196}
197
198impl From<std::env::VarError> for SourceError {
199    fn from(err: std::env::VarError) -> Self {
200        Self::EnvVar(err.to_string())
201    }
202}
203
204#[cfg(test)]
205mod tests {
206    use super::*;
207    use std::path::PathBuf;
208
209    #[test]
210    fn test_error_not_found() {
211        let err = SourceError::not_found("config.toml");
212        assert!(err.is_not_found());
213        assert!(err.to_string().contains("not found"));
214    }
215
216    #[test]
217    fn test_error_parse_failed() {
218        let err = SourceError::parse_failed("/etc/config.toml", "toml", "invalid syntax");
219        assert!(err.is_parse_failed());
220        assert!(err.to_string().contains("Parse error"));
221    }
222
223    #[test]
224    fn test_error_invalid_format() {
225        let err = SourceError::invalid_format("toml", "yaml");
226        assert!(err.to_string().contains("Invalid format"));
227    }
228
229    #[test]
230    fn test_error_io() {
231        let err = SourceError::io("permission denied");
232        assert!(err.is_io());
233    }
234
235    #[test]
236    fn test_error_invalid_path() {
237        let path = PathBuf::from("/invalid/path");
238        let err = SourceError::invalid_path(path.clone());
239        assert!(matches!(err, SourceError::InvalidPath(p) if p == path));
240    }
241
242    #[test]
243    fn test_error_from_io() {
244        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
245        let err: SourceError = io_err.into();
246        assert!(err.is_io());
247    }
248
249    #[test]
250    fn test_error_from_var_error() {
251        let var_err = std::env::VarError::NotPresent;
252        let err: SourceError = var_err.into();
253        assert!(matches!(err, SourceError::EnvVar(_)));
254    }
255}