Skip to main content

attune_core/
error.rs

1use std::path::PathBuf;
2
3/// Errors returned by storage backends.
4///
5/// Backend-specific errors are converted into strings so `attune-core` does not
6/// depend on a concrete backend implementation's error types.
7#[derive(Debug, thiserror::Error)]
8#[non_exhaustive]
9pub enum BackendError {
10    #[error("Failed to open backend: {0}")]
11    Open(String),
12    #[error("Failed to read from backend: {0}")]
13    Read(String),
14    #[error("Failed to write to backend: {0}")]
15    Write(String),
16}
17
18/// Errors returned by generated settings loaders, field setters, and config helpers.
19///
20/// This enum is structured so callers can match on known failure categories.
21/// It is marked `non_exhaustive`, so downstream matches should include a
22/// wildcard arm to remain compatible with future versions.
23#[derive(Debug, thiserror::Error)]
24#[non_exhaustive]
25pub enum SettingsError {
26    #[error(transparent)]
27    Backend(#[from] BackendError),
28    #[error("Failed to serialize persisted setting value: {source}")]
29    Serialize { source: serde_json::Error },
30    #[error("No configuration directory found.")]
31    NoConfigDir,
32    #[error("Failed to read config file `{path}`: {source}")]
33    ConfigRead {
34        path: PathBuf,
35        source: std::io::Error,
36    },
37    #[error("Failed to parse config file `{path}`: {source}")]
38    ConfigParse {
39        path: PathBuf,
40        source: toml::de::Error,
41    },
42    #[error("Failed to parse config value `{key}`: {source}")]
43    ConfigValueParse {
44        key: String,
45        source: toml::de::Error,
46    },
47    #[error("Failed to parse environment variable `{name}`: {source}")]
48    EnvParse {
49        name: String,
50        source: toml::de::Error,
51    },
52    #[error("Failed to parse persisted setting `{key}`: {source}")]
53    PersistValueParse {
54        key: String,
55        source: serde_json::Error,
56    },
57    #[error("Failed to parse persisted setting `{key}` with deserialize fallback: {error}")]
58    PersistFallbackParse { key: String, error: String },
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64    use serde::{Serialize, Serializer};
65
66    struct FailingSerialize;
67
68    impl Serialize for FailingSerialize {
69        fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
70        where
71            S: Serializer,
72        {
73            Err(serde::ser::Error::custom("intentional serialize failure"))
74        }
75    }
76
77    #[test]
78    fn test_backend_error_open_message_is_correct() {
79        let msg = String::from("test_message");
80        let error = BackendError::Open(msg.clone());
81        assert_eq!(
82            error.to_string(),
83            format!("Failed to open backend: {}", msg)
84        );
85    }
86
87    #[test]
88    fn test_backend_error_read_message_is_correct() {
89        let msg = String::from("test_message");
90        let error = BackendError::Read(msg.clone());
91        assert_eq!(
92            error.to_string(),
93            format!("Failed to read from backend: {}", msg)
94        );
95    }
96
97    #[test]
98    fn test_backend_error_write_message_is_correct() {
99        let msg = String::from("test_message");
100        let error = BackendError::Write(msg.clone());
101        assert_eq!(
102            error.to_string(),
103            format!("Failed to write to backend: {}", msg)
104        );
105    }
106
107    #[test]
108    fn test_settings_error_backend_message_is_correct() {
109        let msg = String::from("test_message");
110        let b_error = BackendError::Open(msg.clone());
111        let s_error = SettingsError::from(b_error);
112
113        assert_eq!(
114            s_error.to_string(),
115            format!("Failed to open backend: {}", msg)
116        );
117    }
118
119    #[test]
120    fn test_settings_error_serialize_message_is_correct() {
121        let source = serde_json::to_string(&FailingSerialize).unwrap_err();
122        let set_error = SettingsError::Serialize { source };
123
124        assert!(
125            set_error
126                .to_string()
127                .contains("Failed to serialize persisted setting value:")
128        )
129    }
130
131    #[test]
132    fn test_settings_error_noconfigdir_message_is_correct() {
133        let s_error = SettingsError::NoConfigDir;
134
135        assert_eq!(s_error.to_string(), "No configuration directory found.")
136    }
137}