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(kind: HttpConfigErrorKind, path: impl Into<String>, message: impl Into<String>) -> Self {
44 Self {
45 kind,
46 path: path.into(),
47 message: message.into(),
48 }
49 }
50
51 /// Shorthand for [`HttpConfigErrorKind::MissingField`].
52 ///
53 /// # Parameters
54 /// - `path`: Configuration path of the missing field.
55 /// - `message`: Explanation of what is missing.
56 ///
57 /// # Returns
58 /// New [`HttpConfigError`].
59 pub fn missing(path: impl Into<String>, message: impl Into<String>) -> Self {
60 Self::new(HttpConfigErrorKind::MissingField, path, message)
61 }
62
63 /// Shorthand for [`HttpConfigErrorKind::TypeError`].
64 ///
65 /// # Parameters
66 /// - `path`: Configuration path where the type mismatch occurred.
67 /// - `message`: Details of the expected vs actual type.
68 ///
69 /// # Returns
70 /// New [`HttpConfigError`].
71 pub fn type_error(path: impl Into<String>, message: impl Into<String>) -> Self {
72 Self::new(HttpConfigErrorKind::TypeError, path, message)
73 }
74
75 /// Shorthand for [`HttpConfigErrorKind::InvalidValue`].
76 ///
77 /// # Parameters
78 /// - `path`: Configuration path of the invalid value.
79 /// - `message`: Why the value is not acceptable.
80 ///
81 /// # Returns
82 /// New [`HttpConfigError`].
83 pub fn invalid_value(path: impl Into<String>, message: impl Into<String>) -> Self {
84 Self::new(HttpConfigErrorKind::InvalidValue, path, message)
85 }
86
87 /// Shorthand for [`HttpConfigErrorKind::InvalidHeader`].
88 ///
89 /// # Parameters
90 /// - `path`: Configuration path related to the header map entry.
91 /// - `message`: Header name/value problem description.
92 ///
93 /// # Returns
94 /// New [`HttpConfigError`].
95 pub fn invalid_header(path: impl Into<String>, message: impl Into<String>) -> Self {
96 Self::new(HttpConfigErrorKind::InvalidHeader, path, message)
97 }
98
99 /// Shorthand for [`HttpConfigErrorKind::ConfigError`] (underlying `qubit-config` failure).
100 ///
101 /// # Parameters
102 /// - `path`: Configuration path if known; may be empty when not applicable.
103 /// - `message`: Error text from the config layer.
104 ///
105 /// # Returns
106 /// New [`HttpConfigError`].
107 pub fn config_error(path: impl Into<String>, message: impl Into<String>) -> Self {
108 Self::new(HttpConfigErrorKind::ConfigError, path, message)
109 }
110
111 /// Prepends `prefix` to a concrete nested validation path.
112 ///
113 /// # Parameters
114 /// - `prefix`: Segment such as `timeouts` or `retry`.
115 ///
116 /// # Returns
117 /// Updated error with `path` = `{prefix}.{path}`.
118 pub(crate) fn prepend_path_prefix(mut self, prefix: &str) -> Self {
119 self.path = format!("{prefix}.{}", self.path);
120 self
121 }
122}
123
124impl fmt::Display for HttpConfigError {
125 /// Formats as `[kind] path: message`.
126 ///
127 /// # Parameters
128 /// - `f`: Destination formatter.
129 ///
130 /// # Returns
131 /// [`fmt::Result`].
132 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133 write!(f, "[{}] {}: {}", self.kind, self.path, self.message)
134 }
135}
136
137impl std::error::Error for HttpConfigError {}
138
139impl From<qubit_config::ConfigError> for HttpConfigError {
140 /// Converts a `qubit_config::ConfigError`, mapping typed failures to
141 /// [`HttpConfigErrorKind::TypeError`] when the source carries a property key.
142 ///
143 /// # Parameters
144 /// - `e`: Source configuration error.
145 ///
146 /// # Returns
147 /// Equivalent [`HttpConfigError`].
148 fn from(e: qubit_config::ConfigError) -> Self {
149 use qubit_config::ConfigError;
150 let msg = e.to_string();
151 match e {
152 ConfigError::TypeMismatch { key, .. } | ConfigError::ConversionError { key, .. } => {
153 HttpConfigError::type_error(key, msg)
154 }
155 ConfigError::PropertyHasNoValue(key) => HttpConfigError::type_error(key, msg),
156 ConfigError::PropertyNotFound(key) => HttpConfigError::config_error(key, msg),
157 other => HttpConfigError::config_error("", other.to_string()),
158 }
159 }
160}