use thiserror::Error;
use qubit_common::DataType;
use qubit_value::ValueError;
#[derive(Debug, Error)]
pub enum ConfigError {
#[error("Property not found: {0}")]
PropertyNotFound(String),
#[error("Property '{0}' has no value")]
PropertyHasNoValue(String),
#[error("Type mismatch at '{key}': expected {expected}, actual {actual}")]
TypeMismatch {
key: String,
expected: DataType,
actual: DataType,
},
#[error("Type conversion failed at '{key}': {message}")]
ConversionError {
key: String,
message: String,
},
#[error("Index out of bounds: index {index}, length {len}")]
IndexOutOfBounds {
index: usize,
len: usize,
},
#[error("Variable substitution failed: {0}")]
SubstitutionError(String),
#[error("Variable substitution depth exceeded maximum limit: {0}")]
SubstitutionDepthExceeded(usize),
#[error("Configuration merge failed: {0}")]
MergeError(String),
#[error("Property '{0}' is final and cannot be overridden")]
PropertyIsFinal(String),
#[error("IO error: {0}")]
IoError(#[from] std::io::Error),
#[error("Parse error: {0}")]
ParseError(String),
#[error("Deserialization error at '{path}': {message}")]
DeserializeError {
path: String,
message: String,
},
#[error("Configuration error: {0}")]
Other(String),
}
pub type ConfigResult<T> = Result<T, ConfigError>;
impl ConfigError {
pub(crate) fn type_mismatch_no_key(expected: DataType, actual: DataType) -> Self {
ConfigError::TypeMismatch {
key: String::new(),
expected,
actual,
}
}
pub(crate) fn type_mismatch_at(key: &str, expected: DataType, actual: DataType) -> Self {
ConfigError::TypeMismatch {
key: key.to_string(),
expected,
actual,
}
}
pub(crate) fn conversion_error_no_key(message: impl Into<String>) -> Self {
ConfigError::ConversionError {
key: String::new(),
message: message.into(),
}
}
pub(crate) fn conversion_error_at(key: &str, message: impl Into<String>) -> Self {
ConfigError::ConversionError {
key: key.to_string(),
message: message.into(),
}
}
}
impl From<ValueError> for ConfigError {
fn from(err: ValueError) -> Self {
match err {
ValueError::NoValue => ConfigError::PropertyHasNoValue(String::new()),
ValueError::TypeMismatch { expected, actual } => {
ConfigError::type_mismatch_no_key(expected, actual)
}
ValueError::ConversionFailed { from, to } => {
ConfigError::conversion_error_no_key(format!("From {from} to {to}"))
}
ValueError::ConversionError(msg) => ConfigError::conversion_error_no_key(msg),
ValueError::IndexOutOfBounds { index, len } => {
ConfigError::IndexOutOfBounds { index, len }
}
ValueError::JsonSerializationError(msg) => {
ConfigError::conversion_error_no_key(format!("JSON serialization error: {msg}"))
}
ValueError::JsonDeserializationError(msg) => {
ConfigError::conversion_error_no_key(format!("JSON deserialization error: {msg}"))
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_conversion_error_at_creates_correct_error() {
let err = ConfigError::conversion_error_at("my.key", "test message");
match err {
ConfigError::ConversionError { key, message } => {
assert_eq!(key, "my.key");
assert_eq!(message, "test message");
}
_ => panic!("Expected ConversionError"),
}
}
#[test]
fn test_type_mismatch_at_creates_correct_error() {
use qubit_common::DataType;
let err = ConfigError::type_mismatch_at("a.b", DataType::Bool, DataType::Int32);
match err {
ConfigError::TypeMismatch {
key,
expected,
actual,
} => {
assert_eq!(key, "a.b");
assert_eq!(expected, DataType::Bool);
assert_eq!(actual, DataType::Int32);
}
_ => panic!("Expected TypeMismatch"),
}
}
}