Skip to main content

qubit_config/
error.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025 - 2026.
4 *    Haixing Hu, Qubit Co. Ltd.
5 *
6 *    All rights reserved.
7 *
8 ******************************************************************************/
9//! # Configuration Error Types
10//!
11//! Defines all possible error types in the configuration system.
12//!
13//! # Author
14//!
15//! Haixing Hu
16
17use thiserror::Error;
18
19use qubit_common::DataType;
20use qubit_value::ValueError;
21
22/// Configuration error type
23///
24/// Defines all possible error scenarios in the configuration system.
25///
26/// # Examples
27///
28/// ```rust,ignore
29/// use qubit_config::{Config, ConfigError, ConfigResult};
30/// fn get_port(config: &Config) -> ConfigResult<i32> { unimplemented!() }
31/// ```
32///
33/// # Author
34///
35/// Haixing Hu
36///
37#[derive(Debug, Error)]
38pub enum ConfigError {
39    /// Property not found
40    #[error("Property not found: {0}")]
41    PropertyNotFound(String),
42
43    /// Property has no value
44    #[error("Property '{0}' has no value")]
45    PropertyHasNoValue(String),
46
47    /// Type mismatch at a specific key/path
48    #[error("Type mismatch at '{key}': expected {expected}, actual {actual}")]
49    TypeMismatch {
50        /// The configuration key/path where the mismatch occurred
51        key: String,
52        /// Expected type
53        expected: DataType,
54        /// Actual type
55        actual: DataType,
56    },
57
58    /// Type conversion failed at a specific key/path
59    #[error("Type conversion failed at '{key}': {message}")]
60    ConversionError {
61        /// The configuration key/path where the conversion failed
62        key: String,
63        /// Error message
64        message: String,
65    },
66
67    /// Index out of bounds
68    #[error("Index out of bounds: index {index}, length {len}")]
69    IndexOutOfBounds {
70        /// Index being accessed
71        index: usize,
72        /// Actual length
73        len: usize,
74    },
75
76    /// Variable substitution failed
77    #[error("Variable substitution failed: {0}")]
78    SubstitutionError(String),
79
80    /// Variable substitution depth exceeded
81    #[error("Variable substitution depth exceeded maximum limit: {0}")]
82    SubstitutionDepthExceeded(usize),
83
84    /// Configuration merge failed
85    #[error("Configuration merge failed: {0}")]
86    MergeError(String),
87
88    /// Property is final and cannot be overridden
89    #[error("Property '{0}' is final and cannot be overridden")]
90    PropertyIsFinal(String),
91
92    /// IO error
93    #[error("IO error: {0}")]
94    IoError(#[from] std::io::Error),
95
96    /// Parse error
97    #[error("Parse error: {0}")]
98    ParseError(String),
99
100    /// Deserialization error for structured config mapping
101    #[error("Deserialization error at '{path}': {message}")]
102    DeserializeError {
103        /// The config prefix/path being deserialized
104        path: String,
105        /// Error message
106        message: String,
107    },
108
109    /// Other error
110    #[error("Configuration error: {0}")]
111    Other(String),
112}
113
114/// Result type for configuration operations
115///
116/// Used for all operations in the configuration system that may return errors.
117pub type ConfigResult<T> = Result<T, ConfigError>;
118
119impl ConfigError {
120    /// Creates a `TypeMismatch` error with an empty key (for backward
121    /// compatibility with `ValueError` conversions that lack key context).
122    ///
123    /// # Parameters
124    ///
125    /// * `expected` - Expected [`DataType`].
126    /// * `actual` - Actual [`DataType`].
127    ///
128    /// # Returns
129    ///
130    /// A [`ConfigError::TypeMismatch`] with an empty `key`.
131    #[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    /// Creates a `TypeMismatch` error with a specific key.
141    ///
142    /// # Parameters
143    ///
144    /// * `key` - Configuration key or path.
145    /// * `expected` - Expected [`DataType`].
146    /// * `actual` - Actual [`DataType`].
147    ///
148    /// # Returns
149    ///
150    /// A [`ConfigError::TypeMismatch`].
151    #[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    /// Creates a `ConversionError` with an empty key.
161    ///
162    /// # Parameters
163    ///
164    /// * `message` - Human-readable conversion error message.
165    ///
166    /// # Returns
167    ///
168    /// A [`ConfigError::ConversionError`] with an empty `key`.
169    #[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    /// Creates a `ConversionError` with a specific key.
178    ///
179    /// # Parameters
180    ///
181    /// * `key` - Configuration key or path.
182    /// * `message` - Human-readable conversion error message.
183    ///
184    /// # Returns
185    ///
186    /// A [`ConfigError::ConversionError`].
187    #[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        match err {
228            ConfigError::ConversionError { key, message } => {
229                assert_eq!(key, "my.key");
230                assert_eq!(message, "test message");
231            }
232            _ => panic!("Expected ConversionError"),
233        }
234    }
235
236    #[test]
237    fn test_type_mismatch_at_creates_correct_error() {
238        use qubit_common::DataType;
239        let err = ConfigError::type_mismatch_at("a.b", DataType::Bool, DataType::Int32);
240        match err {
241            ConfigError::TypeMismatch {
242                key,
243                expected,
244                actual,
245            } => {
246                assert_eq!(key, "a.b");
247                assert_eq!(expected, DataType::Bool);
248                assert_eq!(actual, DataType::Int32);
249            }
250            _ => panic!("Expected TypeMismatch"),
251        }
252    }
253}