Skip to main content

qubit_config/
config_error.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025 - 2026 Haixing Hu.
4 *
5 *    SPDX-License-Identifier: Apache-2.0
6 *
7 *    Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10//! # Configuration Error Type
11//!
12//! Defines all possible error scenarios in the configuration system.
13//!
14
15use thiserror::Error;
16
17use qubit_datatype::DataConversionError;
18use qubit_datatype::DataType;
19use qubit_value::ValueError;
20
21/// Configuration error type
22///
23/// Defines all possible error scenarios in the configuration system.
24///
25/// # Examples
26///
27/// ```rust
28/// use qubit_config::{Config, ConfigError, ConfigResult};
29/// fn get_port(config: &Config) -> ConfigResult<i32> { unimplemented!() }
30/// ```
31///
32///
33#[derive(Debug, Error)]
34pub enum ConfigError {
35    /// Property not found
36    #[error("Property not found: {0}")]
37    PropertyNotFound(String),
38
39    /// Property has no value
40    #[error("Property '{0}' has no value")]
41    PropertyHasNoValue(String),
42
43    /// Type mismatch at a specific key/path
44    #[error("Type mismatch at '{key}': expected {expected}, actual {actual}")]
45    TypeMismatch {
46        /// The configuration key/path where the mismatch occurred
47        key: String,
48        /// Expected type
49        expected: DataType,
50        /// Actual type
51        actual: DataType,
52    },
53
54    /// Type conversion failed at a specific key/path
55    #[error("Type conversion failed at '{key}': {message}")]
56    ConversionError {
57        /// The configuration key/path where the conversion failed
58        key: String,
59        /// Error message
60        message: String,
61    },
62
63    /// Index out of bounds
64    #[error("Index out of bounds: index {index}, length {len}")]
65    IndexOutOfBounds {
66        /// Index being accessed
67        index: usize,
68        /// Actual length
69        len: usize,
70    },
71
72    /// Variable substitution failed
73    #[error("Variable substitution failed: {0}")]
74    SubstitutionError(String),
75
76    /// Variable substitution depth exceeded
77    #[error("Variable substitution depth exceeded maximum limit: {0}")]
78    SubstitutionDepthExceeded(usize),
79
80    /// Configuration merge failed
81    #[error("Configuration merge failed: {0}")]
82    MergeError(String),
83
84    /// Property is final and cannot be overridden
85    #[error("Property '{0}' is final and cannot be overridden")]
86    PropertyIsFinal(String),
87
88    /// IO error
89    #[error("IO error: {0}")]
90    IoError(#[from] std::io::Error),
91
92    /// Parse error
93    #[error("Parse error: {0}")]
94    ParseError(String),
95
96    /// Deserialization error for structured config mapping
97    #[error("Deserialization error at '{path}': {message}")]
98    DeserializeError {
99        /// The config prefix/path being deserialized
100        path: String,
101        /// Error message
102        message: String,
103    },
104
105    /// Other error
106    #[error("Configuration error: {0}")]
107    Other(String),
108}
109
110impl ConfigError {
111    /// Creates a `TypeMismatch` error with an empty key (for backward
112    /// compatibility with `ValueError` conversions that lack key context).
113    ///
114    /// # Parameters
115    ///
116    /// * `expected` - Expected [`DataType`].
117    /// * `actual` - Actual [`DataType`].
118    ///
119    /// # Returns
120    ///
121    /// A [`ConfigError::TypeMismatch`] with an empty `key`.
122    #[inline]
123    pub(crate) fn type_mismatch_no_key(expected: DataType, actual: DataType) -> Self {
124        ConfigError::TypeMismatch {
125            key: String::new(),
126            expected,
127            actual,
128        }
129    }
130
131    /// Creates a `ConversionError` with an empty key.
132    ///
133    /// # Parameters
134    ///
135    /// * `message` - Human-readable conversion error message.
136    ///
137    /// # Returns
138    ///
139    /// A [`ConfigError::ConversionError`] with an empty `key`.
140    #[inline]
141    pub(crate) fn conversion_error_no_key(message: impl Into<String>) -> Self {
142        ConfigError::ConversionError {
143            key: String::new(),
144            message: message.into(),
145        }
146    }
147
148    /// Maps a common data conversion error to a keyed configuration error.
149    ///
150    /// # Parameters
151    ///
152    /// * `key` - Configuration key that was being parsed.
153    /// * `err` - Error returned by the common conversion layer.
154    ///
155    /// # Returns
156    ///
157    /// A [`ConfigError`] carrying the supplied key.
158    pub fn from_data_conversion_error(key: &str, err: DataConversionError) -> Self {
159        match err {
160            DataConversionError::NoValue => ConfigError::PropertyHasNoValue(key.to_string()),
161            DataConversionError::ConversionFailed { from, to } => ConfigError::ConversionError {
162                key: key.to_string(),
163                message: format!("From {from} to {to}"),
164            },
165            DataConversionError::ConversionError(message) => ConfigError::ConversionError {
166                key: key.to_string(),
167                message,
168            },
169            DataConversionError::JsonSerializationError(message) => ConfigError::ConversionError {
170                key: key.to_string(),
171                message: format!("JSON serialization error: {message}"),
172            },
173            DataConversionError::JsonDeserializationError(message) => {
174                ConfigError::ConversionError {
175                    key: key.to_string(),
176                    message: format!("JSON deserialization error: {message}"),
177                }
178            }
179        }
180    }
181}
182
183impl From<ValueError> for ConfigError {
184    fn from(err: ValueError) -> Self {
185        match err {
186            ValueError::NoValue => ConfigError::PropertyHasNoValue(String::new()),
187            ValueError::TypeMismatch { expected, actual } => {
188                ConfigError::type_mismatch_no_key(expected, actual)
189            }
190            ValueError::ConversionFailed { from, to } => {
191                ConfigError::conversion_error_no_key(format!("From {from} to {to}"))
192            }
193            ValueError::ConversionError(msg) => ConfigError::conversion_error_no_key(msg),
194            ValueError::IndexOutOfBounds { index, len } => {
195                ConfigError::IndexOutOfBounds { index, len }
196            }
197            ValueError::JsonSerializationError(msg) => {
198                ConfigError::conversion_error_no_key(format!("JSON serialization error: {msg}"))
199            }
200            ValueError::JsonDeserializationError(msg) => {
201                ConfigError::conversion_error_no_key(format!("JSON deserialization error: {msg}"))
202            }
203        }
204    }
205}
206
207impl From<(&str, ValueError)> for ConfigError {
208    fn from((key, err): (&str, ValueError)) -> Self {
209        match err {
210            ValueError::NoValue => ConfigError::PropertyHasNoValue(key.to_string()),
211            ValueError::TypeMismatch { expected, actual } => ConfigError::TypeMismatch {
212                key: key.to_string(),
213                expected,
214                actual,
215            },
216            ValueError::ConversionFailed { from, to } => ConfigError::ConversionError {
217                key: key.to_string(),
218                message: format!("From {from} to {to}"),
219            },
220            ValueError::ConversionError(message) => ConfigError::ConversionError {
221                key: key.to_string(),
222                message,
223            },
224            ValueError::IndexOutOfBounds { index, len } => {
225                ConfigError::IndexOutOfBounds { index, len }
226            }
227            ValueError::JsonSerializationError(message) => ConfigError::ConversionError {
228                key: key.to_string(),
229                message: format!("JSON serialization error: {message}"),
230            },
231            ValueError::JsonDeserializationError(message) => ConfigError::ConversionError {
232                key: key.to_string(),
233                message: format!("JSON deserialization error: {message}"),
234            },
235        }
236    }
237}