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}