Skip to main content

gproxy_protocol/transform/
error.rs

1use std::{error::Error, fmt};
2
3/// A recognized upstream failure, retaining its declared vendor error payload.
4/// Hosts can render a client error event without parsing a diagnostic string.
5#[derive(Debug)]
6pub struct UpstreamError<T>(pub T);
7
8impl<T: serde::Serialize> fmt::Display for UpstreamError<T> {
9    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
10        let json = serde_json::to_string(&self.0).map_err(|_| fmt::Error)?;
11        f.write_str(&json)
12    }
13}
14
15impl<T: fmt::Debug + serde::Serialize> Error for UpstreamError<T> {}
16
17/// Why a request, result, or required host operation could not be converted.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
20pub enum TransformErrorKind {
21    InvalidInput,
22    InvalidResult,
23    Unsupported,
24    MissingMetadata,
25    MissingState,
26    Conflict,
27    Limit,
28    Host,
29}
30
31/// A conversion failure with an explicit field/operation context and cause.
32#[derive(Debug)]
33pub struct TransformError {
34    kind: TransformErrorKind,
35    context: String,
36    detail: String,
37    source: Option<Box<dyn Error + Send + Sync + 'static>>,
38}
39
40impl TransformError {
41    pub fn new(
42        kind: TransformErrorKind,
43        context: impl Into<String>,
44        detail: impl Into<String>,
45    ) -> Self {
46        Self {
47            kind,
48            context: context.into(),
49            detail: detail.into(),
50            source: None,
51        }
52    }
53
54    pub fn with_source(
55        kind: TransformErrorKind,
56        context: impl Into<String>,
57        detail: impl Into<String>,
58        source: impl Into<Box<dyn Error + Send + Sync + 'static>>,
59    ) -> Self {
60        Self {
61            kind,
62            context: context.into(),
63            detail: detail.into(),
64            source: Some(source.into()),
65        }
66    }
67
68    pub fn shape(context: impl Into<String>, detail: impl Into<String>) -> Self {
69        Self::new(TransformErrorKind::InvalidInput, context, detail)
70    }
71
72    pub fn unsupported(context: impl Into<String>, detail: impl Into<String>) -> Self {
73        Self::new(TransformErrorKind::Unsupported, context, detail)
74    }
75
76    pub fn invalid_result(context: impl Into<String>, detail: impl Into<String>) -> Self {
77        Self::new(TransformErrorKind::InvalidResult, context, detail)
78    }
79
80    /// Retain a declared upstream error for HTTP and streaming renderers.
81    pub fn upstream<T>(context: impl Into<String>, payload: T) -> Self
82    where
83        T: fmt::Debug + serde::Serialize + Send + Sync + 'static,
84    {
85        let error = UpstreamError(payload);
86        Self::with_source(
87            TransformErrorKind::InvalidResult,
88            context,
89            error.to_string(),
90            error,
91        )
92    }
93
94    pub fn missing_metadata(field: impl Into<String>) -> Self {
95        Self::new(
96            TransformErrorKind::MissingMetadata,
97            field,
98            "required factual metadata is unavailable",
99        )
100    }
101
102    pub fn kind(&self) -> TransformErrorKind {
103        self.kind
104    }
105    pub fn context(&self) -> &str {
106        &self.context
107    }
108    pub fn detail(&self) -> &str {
109        &self.detail
110    }
111}
112
113impl fmt::Display for TransformError {
114    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115        write!(f, "{}: {}", self.context, self.detail)
116    }
117}
118
119impl Error for TransformError {
120    fn source(&self) -> Option<&(dyn Error + 'static)> {
121        self.source
122            .as_deref()
123            .map(|source| source as &(dyn Error + 'static))
124    }
125}
126
127impl From<serde_json::Error> for TransformError {
128    fn from(error: serde_json::Error) -> Self {
129        let message = error.to_string();
130        Self::with_source(TransformErrorKind::InvalidInput, "JSON", message, error)
131    }
132}
133
134impl From<crate::capability::CapabilityError> for TransformError {
135    fn from(error: crate::capability::CapabilityError) -> Self {
136        use crate::capability::CapabilityErrorKind;
137        let kind = match error.kind() {
138            CapabilityErrorKind::Unsupported => TransformErrorKind::Unsupported,
139            CapabilityErrorKind::NotFound | CapabilityErrorKind::Expired => {
140                TransformErrorKind::Host
141            }
142            CapabilityErrorKind::Conflict => TransformErrorKind::Conflict,
143            CapabilityErrorKind::Limit => TransformErrorKind::Limit,
144            CapabilityErrorKind::Invalid => TransformErrorKind::InvalidInput,
145            CapabilityErrorKind::Transport | CapabilityErrorKind::Storage => {
146                TransformErrorKind::Host
147            }
148        };
149        let message = error.to_string();
150        Self::with_source(kind, "host capability", message, error)
151    }
152}
153
154/// A declared field whose representation changes or cannot be retained.
155#[derive(Debug, Clone, Copy, PartialEq, Eq)]
156#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
157pub enum DiagnosticKind {
158    /// A source field or content block omitted because it has no target
159    /// representation. Other supported parts of the conversion are retained.
160    OmittedOptional,
161    RepresentationChanged,
162}
163
164#[derive(Debug, Clone, PartialEq, Eq)]
165pub struct Diagnostic {
166    pub field: String,
167    pub kind: DiagnosticKind,
168    pub message: String,
169}
170
171/// Owned diagnostics for one conversion, never serialized into vendor `rest`.
172#[derive(Debug, Default, Clone, PartialEq, Eq)]
173pub struct Report {
174    pub diagnostics: Vec<Diagnostic>,
175}
176
177impl Report {
178    pub fn omitted(&mut self, field: impl Into<String>, message: impl Into<String>) {
179        self.diagnostics.push(Diagnostic {
180            field: field.into(),
181            kind: DiagnosticKind::OmittedOptional,
182            message: message.into(),
183        });
184    }
185
186    pub fn changed(&mut self, field: impl Into<String>, message: impl Into<String>) {
187        self.diagnostics.push(Diagnostic {
188            field: field.into(),
189            kind: DiagnosticKind::RepresentationChanged,
190            message: message.into(),
191        });
192    }
193}