1use std::path::PathBuf;
5use thiserror::Error;
6
7#[derive(Error, Debug)]
13pub enum ConfigError {
14 #[error("I/O error: {0}")]
17 Io(#[from] std::io::Error),
18
19 #[error("Parse error: {0}")]
22 Parse(String),
23
24 #[error("Validation error: {0}")]
27 Validation(String),
28
29 #[error("Missing required property: {0}")]
32 MissingProperty(String),
33
34 #[error("Type conversion error for '{key}': expected {expected}, got {value}")]
37 TypeConversion {
38 key: String,
41 expected: String,
44 value: String,
47 },
48
49 #[error("Configuration file not found: {0}")]
52 FileNotFound(PathBuf),
53
54 #[error("Invalid configuration format: {0}")]
57 InvalidFormat(String),
58
59 #[error("Cycle detected in configuration: {0}")]
62 CycleDetected(String),
63
64 #[error("Override not allowed for property: {0}")]
67 OverrideNotAllowed(String),
68
69 #[error("Unknown profile: {0}")]
72 UnknownProfile(String),
73
74 #[error("Deserialize error: {0}")]
77 Deserialize(String),
78}
79
80pub type ConfigResult<T> = Result<T, ConfigError>;
83
84impl From<config::ConfigError> for ConfigError {
85 fn from(err: config::ConfigError) -> Self {
86 ConfigError::Parse(err.to_string())
87 }
88}
89
90impl From<serde_json::Error> for ConfigError {
91 fn from(err: serde_json::Error) -> Self {
92 ConfigError::Deserialize(err.to_string())
93 }
94}
95
96impl From<toml::de::Error> for ConfigError {
97 fn from(err: toml::de::Error) -> Self {
98 ConfigError::Parse(err.to_string())
99 }
100}
101
102#[cfg(test)]
108mod tests {
109 use super::*;
110
111 #[test]
114 fn test_error_io() {
115 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file gone");
116 let err = ConfigError::Io(io_err);
117 assert!(err.to_string().contains("I/O error"));
118 }
119
120 #[test]
123 fn test_error_parse() {
124 let err = ConfigError::Parse("bad syntax".to_string());
125 assert!(err.to_string().contains("Parse error"));
126 assert!(err.to_string().contains("bad syntax"));
127 }
128
129 #[test]
132 fn test_error_validation() {
133 let err = ConfigError::Validation("invalid port".to_string());
134 assert!(err.to_string().contains("Validation error"));
135 }
136
137 #[test]
140 fn test_error_missing_property() {
141 let err = ConfigError::MissingProperty("server.port".to_string());
142 assert!(err.to_string().contains("server.port"));
143 assert!(err.to_string().contains("Missing"));
144 }
145
146 #[test]
149 fn test_error_type_conversion() {
150 let err = ConfigError::TypeConversion {
151 key: "port".to_string(),
152 expected: "i32".to_string(),
153 value: "abc".to_string(),
154 };
155 let msg = err.to_string();
156 assert!(msg.contains("port"));
157 assert!(msg.contains("i32"));
158 assert!(msg.contains("abc"));
159 }
160
161 #[test]
164 fn test_error_file_not_found() {
165 let err = ConfigError::FileNotFound(PathBuf::from("/etc/hiver/app.yaml"));
166 assert!(err.to_string().contains("not found"));
167 }
168
169 #[test]
172 fn test_error_invalid_format() {
173 let err = ConfigError::InvalidFormat("not yaml".to_string());
174 assert!(err.to_string().contains("Invalid"));
175 }
176
177 #[test]
180 fn test_error_cycle_detected() {
181 let err = ConfigError::CycleDetected("a -> b -> a".to_string());
182 assert!(err.to_string().contains("Cycle"));
183 }
184
185 #[test]
188 fn test_error_override_not_allowed() {
189 let err = ConfigError::OverrideNotAllowed("locked.key".to_string());
190 assert!(err.to_string().contains("Override not allowed"));
191 }
192
193 #[test]
196 fn test_error_unknown_profile() {
197 let err = ConfigError::UnknownProfile("nonexistent".to_string());
198 assert!(err.to_string().contains("Unknown profile"));
199 }
200
201 #[test]
204 fn test_error_deserialize() {
205 let err = ConfigError::Deserialize("expected string".to_string());
206 assert!(err.to_string().contains("Deserialize"));
207 }
208
209 #[test]
212 fn test_from_io_error() {
213 let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "denied");
214 let config_err: ConfigError = io_err.into();
215 match config_err {
216 ConfigError::Io(_) => {},
217 _ => panic!("Expected Io variant"),
218 }
219 }
220
221 #[test]
224 fn test_from_serde_json_error() {
225 let json_err: serde_json::Error = serde_json::from_str::<i32>("not a number").unwrap_err();
226 let config_err: ConfigError = json_err.into();
227 match config_err {
228 ConfigError::Deserialize(_) => {},
229 _ => panic!("Expected Deserialize variant"),
230 }
231 }
232
233 #[test]
236 fn test_from_toml_error() {
237 let toml_err = toml::from_str::<toml::Value>("{invalid").unwrap_err();
238 let config_err: ConfigError = toml_err.into();
239 match config_err {
240 ConfigError::Parse(_) => {},
241 _ => panic!("Expected Parse variant"),
242 }
243 }
244
245 #[test]
248 fn test_config_result_ok() {
249 let result: ConfigResult<i32> = Ok(42);
250 assert_eq!(result.unwrap(), 42);
251 }
252
253 #[test]
256 fn test_config_result_err() {
257 let result: ConfigResult<()> = Err(ConfigError::Parse("err".to_string()));
258 assert!(result.is_err());
259 }
260}