Skip to main content

dscode_core/
error.rs

1use thiserror::Error;
2
3/// The unified error type for all fallible operations in `dscode-core`.
4///
5/// Every public function in this crate that can fail returns a
6/// `Result<T, CoreError>`. The variants cover configuration errors,
7/// path resolution failures, and I/O errors from the underlying filesystem.
8///
9/// # Display
10///
11/// Each variant implements [`std::fmt::Display`] via `thiserror`, so calling
12/// `.to_string()` on a `CoreError` produces a human-readable message suitable
13/// for logging or displaying to the user.
14///
15/// # Conversion
16///
17/// `CoreError` implements `From<std::io::Error>` so I/O errors can be
18/// propagated with the `?` operator. It also implements `Into<String>` for
19/// ergonomic string conversion.
20#[derive(Error, Debug)]
21pub enum CoreError {
22    /// A configuration error, such as an invalid setting value or a missing
23    /// required field in a configuration file.
24    ///
25    /// The contained `String` describes the specific configuration problem.
26    #[error("Configuration error: {0}")]
27    Config(String),
28
29    /// A platform-specific directory path could not be resolved.
30    ///
31    /// This typically occurs when the home directory is unavailable or a
32    /// user-configured path (e.g. `~` expansion) cannot be expanded.
33    ///
34    /// The contained `String` describes which path failed and why.
35    #[error("Path resolution failed: {0}")]
36    PathResolution(String),
37
38    /// An I/O error occurred during a file or directory operation.
39    ///
40    /// This variant wraps [`std::io::Error`] and is automatically created via
41    /// the `?` operator on any I/O call that fails (e.g. creating directories,
42    /// reading configuration files).
43    #[error("I/O error: {0}")]
44    Io(#[from] std::io::Error),
45}
46
47/// Allows converting a [`CoreError`] into a [`String`] for ergonomic error
48/// reporting.
49///
50/// This is useful when a string representation of the error is needed without
51/// preserving the typed error value.
52impl From<CoreError> for String {
53    fn from(err: CoreError) -> Self {
54        err.to_string()
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61
62    #[test]
63    fn test_core_error_config_display() {
64        let err = CoreError::Config("invalid setting".to_string());
65        let msg = err.to_string();
66        assert!(msg.contains("Configuration error"), "Display should contain variant label");
67        assert!(msg.contains("invalid setting"), "Display should contain the inner message");
68    }
69
70    #[test]
71    fn test_core_error_path_resolution_display() {
72        let err = CoreError::PathResolution("home dir unavailable".to_string());
73        let msg = err.to_string();
74        assert!(msg.contains("Path resolution failed"), "Display should contain variant label");
75        assert!(msg.contains("home dir unavailable"), "Display should contain the inner message");
76    }
77
78    #[test]
79    fn test_core_error_io_display() {
80        let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "access denied");
81        let err = CoreError::Io(io_err);
82        let msg = err.to_string();
83        assert!(msg.contains("I/O error"), "Display should contain variant label");
84        assert!(msg.contains("access denied"), "Display should contain the inner io message");
85    }
86
87    #[test]
88    fn test_core_error_into_string() {
89        let err = CoreError::Config("bad config".to_string());
90        let s: String = err.into();
91        assert!(s.contains("Configuration error"));
92        assert!(s.contains("bad config"));
93    }
94
95    #[test]
96    fn test_core_error_from_io_error() {
97        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
98        let core_err: CoreError = io_err.into();
99        match core_err {
100            CoreError::Io(_) => {} // expected
101            other => panic!("Expected Io variant, got {:?}", other),
102        }
103    }
104
105    #[test]
106    fn test_core_error_debug_format() {
107        // Ensure Debug formatting works without panic
108        let err = CoreError::Config("test".to_string());
109        let debug_str = format!("{:?}", err);
110        assert!(debug_str.contains("Config"));
111    }
112}