use std::path::PathBuf;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ConfigError {
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("Parse error: {0}")]
Parse(String),
#[error("Validation error: {0}")]
Validation(String),
#[error("Missing required property: {0}")]
MissingProperty(String),
#[error("Type conversion error for '{key}': expected {expected}, got {value}")]
TypeConversion {
key: String,
expected: String,
value: String,
},
#[error("Configuration file not found: {0}")]
FileNotFound(PathBuf),
#[error("Invalid configuration format: {0}")]
InvalidFormat(String),
#[error("Cycle detected in configuration: {0}")]
CycleDetected(String),
#[error("Override not allowed for property: {0}")]
OverrideNotAllowed(String),
#[error("Unknown profile: {0}")]
UnknownProfile(String),
#[error("Deserialize error: {0}")]
Deserialize(String),
}
pub type ConfigResult<T> = Result<T, ConfigError>;
impl From<config::ConfigError> for ConfigError {
fn from(err: config::ConfigError) -> Self {
ConfigError::Parse(err.to_string())
}
}
impl From<serde_json::Error> for ConfigError {
fn from(err: serde_json::Error) -> Self {
ConfigError::Deserialize(err.to_string())
}
}
impl From<toml::de::Error> for ConfigError {
fn from(err: toml::de::Error) -> Self {
ConfigError::Parse(err.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_io() {
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file gone");
let err = ConfigError::Io(io_err);
assert!(err.to_string().contains("I/O error"));
}
#[test]
fn test_error_parse() {
let err = ConfigError::Parse("bad syntax".to_string());
assert!(err.to_string().contains("Parse error"));
assert!(err.to_string().contains("bad syntax"));
}
#[test]
fn test_error_validation() {
let err = ConfigError::Validation("invalid port".to_string());
assert!(err.to_string().contains("Validation error"));
}
#[test]
fn test_error_missing_property() {
let err = ConfigError::MissingProperty("server.port".to_string());
assert!(err.to_string().contains("server.port"));
assert!(err.to_string().contains("Missing"));
}
#[test]
fn test_error_type_conversion() {
let err = ConfigError::TypeConversion {
key: "port".to_string(),
expected: "i32".to_string(),
value: "abc".to_string(),
};
let msg = err.to_string();
assert!(msg.contains("port"));
assert!(msg.contains("i32"));
assert!(msg.contains("abc"));
}
#[test]
fn test_error_file_not_found() {
let err = ConfigError::FileNotFound(PathBuf::from("/etc/hiver/app.yaml"));
assert!(err.to_string().contains("not found"));
}
#[test]
fn test_error_invalid_format() {
let err = ConfigError::InvalidFormat("not yaml".to_string());
assert!(err.to_string().contains("Invalid"));
}
#[test]
fn test_error_cycle_detected() {
let err = ConfigError::CycleDetected("a -> b -> a".to_string());
assert!(err.to_string().contains("Cycle"));
}
#[test]
fn test_error_override_not_allowed() {
let err = ConfigError::OverrideNotAllowed("locked.key".to_string());
assert!(err.to_string().contains("Override not allowed"));
}
#[test]
fn test_error_unknown_profile() {
let err = ConfigError::UnknownProfile("nonexistent".to_string());
assert!(err.to_string().contains("Unknown profile"));
}
#[test]
fn test_error_deserialize() {
let err = ConfigError::Deserialize("expected string".to_string());
assert!(err.to_string().contains("Deserialize"));
}
#[test]
fn test_from_io_error() {
let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "denied");
let config_err: ConfigError = io_err.into();
match config_err {
ConfigError::Io(_) => {},
_ => panic!("Expected Io variant"),
}
}
#[test]
fn test_from_serde_json_error() {
let json_err: serde_json::Error = serde_json::from_str::<i32>("not a number").unwrap_err();
let config_err: ConfigError = json_err.into();
match config_err {
ConfigError::Deserialize(_) => {},
_ => panic!("Expected Deserialize variant"),
}
}
#[test]
fn test_from_toml_error() {
let toml_err = toml::from_str::<toml::Value>("{invalid").unwrap_err();
let config_err: ConfigError = toml_err.into();
match config_err {
ConfigError::Parse(_) => {},
_ => panic!("Expected Parse variant"),
}
}
#[test]
fn test_config_result_ok() {
let result: ConfigResult<i32> = Ok(42);
assert_eq!(result.unwrap(), 42);
}
#[test]
fn test_config_result_err() {
let result: ConfigResult<()> = Err(ConfigError::Parse("err".to_string()));
assert!(result.is_err());
}
}