use std::path::PathBuf;
use thiserror::Error;
pub type ConfigResult<T> = Result<T, ConfigError>;
#[derive(Debug, Error)]
pub enum ConfigError {
#[error("Configuration file not found: {path}")]
FileNotFound { path: PathBuf },
#[error("Failed to read configuration file {path}: {source}")]
ReadError {
path: PathBuf,
#[source]
source: std::io::Error,
},
#[error("Failed to parse YAML configuration: {0}")]
YamlParseError(#[from] serde_yaml::Error),
#[error("Failed to parse JSON configuration: {0}")]
JsonParseError(#[from] serde_json::Error),
#[error("Failed to parse TOML configuration: {0}")]
TomlParseError(#[from] toml::de::Error),
#[error("Unsupported configuration file format: {extension}")]
UnsupportedFormat { extension: String },
#[error("Configuration validation error: {message}")]
ValidationError { message: String },
#[error("Required configuration field missing: {field}")]
RequiredField { field: String },
#[error("Invalid configuration value for {field}: {value} - {reason}")]
InvalidValue {
field: String,
value: String,
reason: String,
},
#[error("Environment variable error for {var}")]
EnvVarError { var: String },
#[error("Failed to substitute environment variable {var}: {reason}")]
SubstitutionError { var: String, reason: String },
#[error("Module '{module}' configuration error: {message}")]
ModuleError { module: String, message: String },
}
impl ConfigError {
pub fn file_not_found<P: Into<PathBuf>>(path: P) -> Self {
Self::FileNotFound { path: path.into() }
}
pub fn read_error<P: Into<PathBuf>>(path: P, source: std::io::Error) -> Self {
Self::ReadError {
path: path.into(),
source,
}
}
pub fn validation<S: Into<String>>(message: S) -> Self {
Self::ValidationError {
message: message.into(),
}
}
pub fn required_field<S: Into<String>>(field: S) -> Self {
Self::RequiredField {
field: field.into(),
}
}
pub fn invalid_value<S: Into<String>>(field: S, value: S, reason: S) -> Self {
Self::InvalidValue {
field: field.into(),
value: value.into(),
reason: reason.into(),
}
}
pub fn env_var<S: Into<String>>(var: S) -> Self {
Self::EnvVarError {
var: var.into(),
}
}
pub fn substitution<S: Into<String>>(var: S, reason: S) -> Self {
Self::SubstitutionError {
var: var.into(),
reason: reason.into(),
}
}
pub fn module<S: Into<String>>(module: S, message: S) -> Self {
Self::ModuleError {
module: module.into(),
message: message.into(),
}
}
pub fn unsupported_format<S: Into<String>>(extension: S) -> Self {
Self::UnsupportedFormat {
extension: extension.into(),
}
}
}
impl From<std::env::VarError> for ConfigError {
fn from(_err: std::env::VarError) -> Self {
Self::EnvVarError {
var: "unknown".to_string(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_display() {
let err = ConfigError::file_not_found("test.yml");
assert!(err.to_string().contains("test.yml"));
let err = ConfigError::validation("Invalid port");
assert!(err.to_string().contains("Invalid port"));
let err = ConfigError::required_field("database.url");
assert!(err.to_string().contains("database.url"));
let err = ConfigError::invalid_value("port", "abc", "must be a number");
assert!(err.to_string().contains("port"));
assert!(err.to_string().contains("abc"));
}
}