Skip to main content

qubit_retry/error/
retry_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 validation errors.
10//!
11//! This module keeps retry configuration failures independent from executor
12//! execution failures so callers can distinguish setup errors from operation
13//! errors.
14
15use std::error::Error;
16use std::fmt;
17
18use qubit_config::ConfigError;
19
20/// Invalid retry configuration.
21///
22/// `path` stores the configuration key that failed when such context is
23/// available. `message` stores the human-readable reason.
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct RetryConfigError {
26    path: String,
27    message: String,
28}
29
30impl RetryConfigError {
31    /// Creates a validation error for a retry option.
32    ///
33    /// # Parameters
34    /// - `path`: Configuration key or option name associated with the failure.
35    /// - `message`: Human-readable validation message.
36    ///
37    /// # Returns
38    /// A new [`RetryConfigError`].
39    ///
40    /// # Errors
41    /// This function does not return errors.
42    #[inline]
43    pub fn invalid_value(path: impl Into<String>, message: impl Into<String>) -> Self {
44        Self {
45            path: path.into(),
46            message: message.into(),
47        }
48    }
49
50    /// Wraps an error returned by `qubit-config`.
51    ///
52    /// # Parameters
53    /// - `path`: Configuration key that was being read.
54    /// - `source`: Error returned by `qubit-config`.
55    ///
56    /// # Returns
57    /// A new [`RetryConfigError`] that preserves the key and source message.
58    ///
59    /// # Errors
60    /// This function does not return errors.
61    #[inline]
62    pub fn from_config(path: impl Into<String>, source: ConfigError) -> Self {
63        Self {
64            path: path.into(),
65            message: source.to_string(),
66        }
67    }
68
69    /// Returns the configuration path associated with this error.
70    ///
71    /// # Parameters
72    /// This method has no parameters.
73    ///
74    /// # Returns
75    /// The configuration path, or an empty string when the error was not tied
76    /// to a specific key.
77    ///
78    /// # Errors
79    /// This method does not return errors.
80    #[inline]
81    pub fn path(&self) -> &str {
82        &self.path
83    }
84
85    /// Returns the error message.
86    ///
87    /// # Parameters
88    /// This method has no parameters.
89    ///
90    /// # Returns
91    /// The human-readable validation or configuration read message.
92    ///
93    /// # Errors
94    /// This method does not return errors.
95    #[inline]
96    pub fn message(&self) -> &str {
97        &self.message
98    }
99}
100
101impl fmt::Display for RetryConfigError {
102    /// Formats the configuration error for diagnostics.
103    ///
104    /// # Parameters
105    /// - `f`: Formatter provided by the standard formatting machinery.
106    ///
107    /// # Returns
108    /// `fmt::Result` from the formatter.
109    ///
110    /// # Errors
111    /// Returns a formatting error if the underlying formatter fails.
112    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113        if self.path.is_empty() {
114            write!(f, "invalid retry configuration: {}", self.message)
115        } else {
116            write!(
117                f,
118                "invalid retry configuration at '{}': {}",
119                self.path, self.message
120            )
121        }
122    }
123}
124
125impl Error for RetryConfigError {}
126
127impl From<ConfigError> for RetryConfigError {
128    /// Converts a `qubit-config` error into a retry configuration error.
129    ///
130    /// # Parameters
131    /// - `source`: Error returned by `qubit-config`.
132    ///
133    /// # Returns
134    /// A [`RetryConfigError`] with the path carried by `source` when
135    /// available, or an empty path for config errors that do not include key
136    /// context.
137    ///
138    /// # Errors
139    /// This function does not return errors.
140    #[inline]
141    fn from(source: ConfigError) -> Self {
142        let path = match &source {
143            ConfigError::PropertyNotFound(path)
144            | ConfigError::PropertyHasNoValue(path)
145            | ConfigError::PropertyIsFinal(path) => path.clone(),
146            ConfigError::TypeMismatch { key, .. } | ConfigError::ConversionError { key, .. } => {
147                key.clone()
148            }
149            ConfigError::DeserializeError { path, .. } => path.clone(),
150            ConfigError::IndexOutOfBounds { .. }
151            | ConfigError::SubstitutionError(_)
152            | ConfigError::SubstitutionDepthExceeded(_)
153            | ConfigError::MergeError(_)
154            | ConfigError::IoError(_)
155            | ConfigError::ParseError(_)
156            | ConfigError::Other(_) => String::new(),
157        };
158        Self::from_config(path, source)
159    }
160}