Skip to main content

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