Skip to main content

qubit_http/options/
http_config_error.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025 - 2026.
4 *    Haixing Hu, Qubit Co. Ltd.
5 *
6 *    All rights reserved.
7 *
8 ******************************************************************************/
9//! # HTTP configuration error
10//!
11//! Error type for configuration-to-options conversion failures.
12//!
13//! # Author
14//!
15//! Haixing Hu
16
17use std::fmt;
18
19use super::HttpConfigErrorKind;
20
21/// Error type for HTTP configuration conversion failures.
22///
23/// Carries the failing configuration path and a human-readable message so that
24/// callers can report exactly which key caused the problem.
25#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct HttpConfigError {
27    /// The configuration path that triggered the error, e.g. `http.proxy.port`.
28    pub path: String,
29    /// Human-readable description of the problem.
30    pub message: String,
31    /// Error category.
32    pub kind: HttpConfigErrorKind,
33}
34
35impl HttpConfigError {
36    /// Builds a configuration error with the given classification and message.
37    ///
38    /// # Parameters
39    /// - `kind`: Error category.
40    /// - `path`: Configuration key path (e.g. `http.proxy.port`).
41    /// - `message`: Human-readable explanation.
42    ///
43    /// # Returns
44    /// New [`HttpConfigError`].
45    pub fn new(
46        kind: HttpConfigErrorKind,
47        path: impl Into<String>,
48        message: impl Into<String>,
49    ) -> Self {
50        Self {
51            kind,
52            path: path.into(),
53            message: message.into(),
54        }
55    }
56
57    /// Shorthand for [`HttpConfigErrorKind::MissingField`].
58    ///
59    /// # Parameters
60    /// - `path`: Configuration path of the missing field.
61    /// - `message`: Explanation of what is missing.
62    ///
63    /// # Returns
64    /// New [`HttpConfigError`].
65    pub fn missing(path: impl Into<String>, message: impl Into<String>) -> Self {
66        Self::new(HttpConfigErrorKind::MissingField, path, message)
67    }
68
69    /// Shorthand for [`HttpConfigErrorKind::TypeError`].
70    ///
71    /// # Parameters
72    /// - `path`: Configuration path where the type mismatch occurred.
73    /// - `message`: Details of the expected vs actual type.
74    ///
75    /// # Returns
76    /// New [`HttpConfigError`].
77    pub fn type_error(path: impl Into<String>, message: impl Into<String>) -> Self {
78        Self::new(HttpConfigErrorKind::TypeError, path, message)
79    }
80
81    /// Shorthand for [`HttpConfigErrorKind::InvalidValue`].
82    ///
83    /// # Parameters
84    /// - `path`: Configuration path of the invalid value.
85    /// - `message`: Why the value is not acceptable.
86    ///
87    /// # Returns
88    /// New [`HttpConfigError`].
89    pub fn invalid_value(path: impl Into<String>, message: impl Into<String>) -> Self {
90        Self::new(HttpConfigErrorKind::InvalidValue, path, message)
91    }
92
93    /// Shorthand for [`HttpConfigErrorKind::InvalidHeader`].
94    ///
95    /// # Parameters
96    /// - `path`: Configuration path related to the header map entry.
97    /// - `message`: Header name/value problem description.
98    ///
99    /// # Returns
100    /// New [`HttpConfigError`].
101    pub fn invalid_header(path: impl Into<String>, message: impl Into<String>) -> Self {
102        Self::new(HttpConfigErrorKind::InvalidHeader, path, message)
103    }
104
105    /// Shorthand for [`HttpConfigErrorKind::ConfigError`] (underlying `qubit-config` failure).
106    ///
107    /// # Parameters
108    /// - `path`: Configuration path if known; may be empty when not applicable.
109    /// - `message`: Error text from the config layer.
110    ///
111    /// # Returns
112    /// New [`HttpConfigError`].
113    pub fn config_error(path: impl Into<String>, message: impl Into<String>) -> Self {
114        Self::new(HttpConfigErrorKind::ConfigError, path, message)
115    }
116
117    /// Prepends `prefix` to [`Self::path`] (for composing subsection parsers under a logical key).
118    ///
119    /// # Parameters
120    /// - `prefix`: Segment such as `timeouts` or `proxy`; empty leaves `self` unchanged.
121    ///
122    /// # Returns
123    /// Updated error with `path` = `prefix` or `{prefix}.{path}`.
124    pub(crate) fn prepend_path_prefix(mut self, prefix: &str) -> Self {
125        let prefix_with_dot = format!("{prefix}.");
126        let already_prefixed = self.path == prefix || self.path.starts_with(&prefix_with_dot);
127        if !already_prefixed {
128            self.path = self
129                .path
130                .find(&prefix_with_dot)
131                .map(|index| self.path[index..].to_string())
132                .unwrap_or_else(|| {
133                    [prefix, self.path.as_str()]
134                        .into_iter()
135                        .filter(|part| !part.is_empty())
136                        .collect::<Vec<_>>()
137                        .join(".")
138                });
139        }
140        self
141    }
142}
143
144impl fmt::Display for HttpConfigError {
145    /// Formats as `[kind] path: message`.
146    ///
147    /// # Parameters
148    /// - `f`: Destination formatter.
149    ///
150    /// # Returns
151    /// [`fmt::Result`].
152    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153        write!(f, "[{}] {}: {}", self.kind, self.path, self.message)
154    }
155}
156
157impl std::error::Error for HttpConfigError {}
158
159impl From<qubit_config::ConfigError> for HttpConfigError {
160    /// Converts a `qubit_config::ConfigError`, mapping typed failures to
161    /// [`HttpConfigErrorKind::TypeError`] when the source carries a property key.
162    ///
163    /// # Parameters
164    /// - `e`: Source configuration error.
165    ///
166    /// # Returns
167    /// Equivalent [`HttpConfigError`].
168    fn from(e: qubit_config::ConfigError) -> Self {
169        use qubit_config::ConfigError;
170        let msg = e.to_string();
171        match e {
172            ConfigError::TypeMismatch { key, .. } | ConfigError::ConversionError { key, .. } => {
173                HttpConfigError::type_error(key, msg)
174            }
175            ConfigError::PropertyHasNoValue(key) => HttpConfigError::type_error(key, msg),
176            ConfigError::PropertyNotFound(key) => HttpConfigError::config_error(key, msg),
177            other => HttpConfigError::config_error("", other.to_string()),
178        }
179    }
180}