Skip to main content

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