1use thiserror::Error;
18
19use qubit_common::DataType;
20use qubit_value::ValueError;
21
22#[derive(Debug, Error)]
38pub enum ConfigError {
39 #[error("Property not found: {0}")]
41 PropertyNotFound(String),
42
43 #[error("Property '{0}' has no value")]
45 PropertyHasNoValue(String),
46
47 #[error("Type mismatch at '{key}': expected {expected}, actual {actual}")]
49 TypeMismatch {
50 key: String,
52 expected: DataType,
54 actual: DataType,
56 },
57
58 #[error("Type conversion failed at '{key}': {message}")]
60 ConversionError {
61 key: String,
63 message: String,
65 },
66
67 #[error("Index out of bounds: index {index}, length {len}")]
69 IndexOutOfBounds {
70 index: usize,
72 len: usize,
74 },
75
76 #[error("Variable substitution failed: {0}")]
78 SubstitutionError(String),
79
80 #[error("Variable substitution depth exceeded maximum limit: {0}")]
82 SubstitutionDepthExceeded(usize),
83
84 #[error("Configuration merge failed: {0}")]
86 MergeError(String),
87
88 #[error("Property '{0}' is final and cannot be overridden")]
90 PropertyIsFinal(String),
91
92 #[error("IO error: {0}")]
94 IoError(#[from] std::io::Error),
95
96 #[error("Parse error: {0}")]
98 ParseError(String),
99
100 #[error("Deserialization error at '{path}': {message}")]
102 DeserializeError {
103 path: String,
105 message: String,
107 },
108
109 #[error("Configuration error: {0}")]
111 Other(String),
112}
113
114pub type ConfigResult<T> = Result<T, ConfigError>;
118
119impl ConfigError {
120 #[inline]
132 pub(crate) fn type_mismatch_no_key(expected: DataType, actual: DataType) -> Self {
133 ConfigError::TypeMismatch {
134 key: String::new(),
135 expected,
136 actual,
137 }
138 }
139
140 #[inline]
152 pub(crate) fn type_mismatch_at(key: &str, expected: DataType, actual: DataType) -> Self {
153 ConfigError::TypeMismatch {
154 key: key.to_string(),
155 expected,
156 actual,
157 }
158 }
159
160 #[inline]
170 pub(crate) fn conversion_error_no_key(message: impl Into<String>) -> Self {
171 ConfigError::ConversionError {
172 key: String::new(),
173 message: message.into(),
174 }
175 }
176
177 #[inline]
188 pub(crate) fn conversion_error_at(key: &str, message: impl Into<String>) -> Self {
189 ConfigError::ConversionError {
190 key: key.to_string(),
191 message: message.into(),
192 }
193 }
194}
195
196impl From<ValueError> for ConfigError {
197 fn from(err: ValueError) -> Self {
198 match err {
199 ValueError::NoValue => ConfigError::PropertyHasNoValue(String::new()),
200 ValueError::TypeMismatch { expected, actual } => {
201 ConfigError::type_mismatch_no_key(expected, actual)
202 }
203 ValueError::ConversionFailed { from, to } => {
204 ConfigError::conversion_error_no_key(format!("From {from} to {to}"))
205 }
206 ValueError::ConversionError(msg) => ConfigError::conversion_error_no_key(msg),
207 ValueError::IndexOutOfBounds { index, len } => {
208 ConfigError::IndexOutOfBounds { index, len }
209 }
210 ValueError::JsonSerializationError(msg) => {
211 ConfigError::conversion_error_no_key(format!("JSON serialization error: {msg}"))
212 }
213 ValueError::JsonDeserializationError(msg) => {
214 ConfigError::conversion_error_no_key(format!("JSON deserialization error: {msg}"))
215 }
216 }
217 }
218}
219
220#[cfg(test)]
221mod tests {
222 use super::*;
223
224 #[test]
225 fn test_conversion_error_at_creates_correct_error() {
226 let err = ConfigError::conversion_error_at("my.key", "test message");
227 assert!(matches!(
228 err,
229 ConfigError::ConversionError { ref key, ref message }
230 if key == "my.key" && message == "test message"
231 ));
232 }
233
234 #[test]
235 fn test_type_mismatch_at_creates_correct_error() {
236 use qubit_common::DataType;
237 let err = ConfigError::type_mismatch_at("a.b", DataType::Bool, DataType::Int32);
238 assert!(matches!(
239 err,
240 ConfigError::TypeMismatch {
241 ref key,
242 expected: DataType::Bool,
243 actual: DataType::Int32,
244 } if key == "a.b"
245 ));
246 }
247}