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    /// Variable substitution cycle detected
81    #[error("Variable substitution cycle detected: {}", chain.join(" -> "))]
82    SubstitutionCycle {
83        /// Variable chain that forms the cycle
84        chain: Vec<String>,
85    },
86
87    /// Configuration merge failed
88    #[error("Configuration merge failed: {0}")]
89    MergeError(String),
90
91    /// Property is final and cannot be overridden
92    #[error("Property '{0}' is final and cannot be overridden")]
93    PropertyIsFinal(String),
94
95    /// Configuration key path cannot be represented without ambiguity
96    #[error("Configuration key conflict at '{path}': existing {existing}, incoming {incoming}")]
97    KeyConflict {
98        /// The conflicting configuration key/path
99        path: String,
100        /// Existing value or path shape
101        existing: String,
102        /// Incoming value or path shape
103        incoming: String,
104    },
105
106    /// IO error
107    #[error("IO error: {0}")]
108    IoError(#[from] std::io::Error),
109
110    /// Parse error
111    #[error("Parse error: {0}")]
112    ParseError(String),
113
114    /// Deserialization error for structured config mapping
115    #[error("Deserialization error at '{path}': {message}")]
116    DeserializeError {
117        /// The config prefix/path being deserialized
118        path: String,
119        /// Error message
120        message: String,
121        /// Original structured error when it came from config parsing
122        #[source]
123        source: Option<Box<ConfigError>>,
124    },
125
126    /// Other error
127    #[error("Configuration error: {0}")]
128    Other(String),
129}
130
131impl ConfigError {
132    /// Creates a `TypeMismatch` error with an empty key (for backward
133    /// compatibility with `ValueError` conversions that lack key context).
134    ///
135    /// # Parameters
136    ///
137    /// * `expected` - Expected [`DataType`].
138    /// * `actual` - Actual [`DataType`].
139    ///
140    /// # Returns
141    ///
142    /// A [`ConfigError::TypeMismatch`] with an empty `key`.
143    #[inline]
144    pub(crate) fn type_mismatch_no_key(expected: DataType, actual: DataType) -> Self {
145        ConfigError::TypeMismatch {
146            key: String::new(),
147            expected,
148            actual,
149        }
150    }
151
152    /// Creates a `ConversionError` with an empty key.
153    ///
154    /// # Parameters
155    ///
156    /// * `message` - Human-readable conversion error message.
157    ///
158    /// # Returns
159    ///
160    /// A [`ConfigError::ConversionError`] with an empty `key`.
161    #[inline]
162    pub(crate) fn conversion_error_no_key(message: impl Into<String>) -> Self {
163        ConfigError::ConversionError {
164            key: String::new(),
165            message: message.into(),
166        }
167    }
168
169    /// Maps a common data conversion error to a keyed configuration error.
170    ///
171    /// # Parameters
172    ///
173    /// * `key` - Configuration key that was being parsed.
174    /// * `err` - Error returned by the common conversion layer.
175    ///
176    /// # Returns
177    ///
178    /// A [`ConfigError`] carrying the supplied key.
179    pub fn from_data_conversion_error(key: &str, err: DataConversionError) -> Self {
180        match err {
181            DataConversionError::NoValue => ConfigError::PropertyHasNoValue(key.to_string()),
182            DataConversionError::ConversionFailed { from, to } => ConfigError::ConversionError {
183                key: key.to_string(),
184                message: format!("From {from} to {to}"),
185            },
186            DataConversionError::ConversionError(message) => ConfigError::ConversionError {
187                key: key.to_string(),
188                message,
189            },
190            DataConversionError::JsonSerializationError(message) => ConfigError::ConversionError {
191                key: key.to_string(),
192                message: format!("JSON serialization error: {message}"),
193            },
194            DataConversionError::JsonDeserializationError(message) => {
195                ConfigError::ConversionError {
196                    key: key.to_string(),
197                    message: format!("JSON deserialization error: {message}"),
198                }
199            }
200        }
201    }
202}
203
204impl From<ValueError> for ConfigError {
205    fn from(err: ValueError) -> Self {
206        match err {
207            ValueError::NoValue => ConfigError::PropertyHasNoValue(String::new()),
208            ValueError::TypeMismatch { expected, actual } => {
209                ConfigError::type_mismatch_no_key(expected, actual)
210            }
211            ValueError::ConversionFailed { from, to } => {
212                ConfigError::conversion_error_no_key(format!("From {from} to {to}"))
213            }
214            ValueError::ConversionError(msg) => ConfigError::conversion_error_no_key(msg),
215            ValueError::IndexOutOfBounds { index, len } => {
216                ConfigError::IndexOutOfBounds { index, len }
217            }
218            ValueError::JsonSerializationError(msg) => {
219                ConfigError::conversion_error_no_key(format!("JSON serialization error: {msg}"))
220            }
221            ValueError::JsonDeserializationError(msg) => {
222                ConfigError::conversion_error_no_key(format!("JSON deserialization error: {msg}"))
223            }
224        }
225    }
226}
227
228impl From<(&str, ValueError)> for ConfigError {
229    fn from((key, err): (&str, ValueError)) -> Self {
230        match err {
231            ValueError::NoValue => ConfigError::PropertyHasNoValue(key.to_string()),
232            ValueError::TypeMismatch { expected, actual } => ConfigError::TypeMismatch {
233                key: key.to_string(),
234                expected,
235                actual,
236            },
237            ValueError::ConversionFailed { from, to } => ConfigError::ConversionError {
238                key: key.to_string(),
239                message: format!("From {from} to {to}"),
240            },
241            ValueError::ConversionError(message) => ConfigError::ConversionError {
242                key: key.to_string(),
243                message,
244            },
245            ValueError::IndexOutOfBounds { index, len } => {
246                ConfigError::IndexOutOfBounds { index, len }
247            }
248            ValueError::JsonSerializationError(message) => ConfigError::ConversionError {
249                key: key.to_string(),
250                message: format!("JSON serialization error: {message}"),
251            },
252            ValueError::JsonDeserializationError(message) => ConfigError::ConversionError {
253                key: key.to_string(),
254                message: format!("JSON deserialization error: {message}"),
255            },
256        }
257    }
258}