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    pub fn missing_metadata(field: impl Into<String>) -> Self {
81        Self::new(
82            TransformErrorKind::MissingMetadata,
83            field,
84            "required factual metadata is unavailable",
85        )
86    }
87
88    pub fn kind(&self) -> TransformErrorKind {
89        self.kind
90    }
91    pub fn context(&self) -> &str {
92        &self.context
93    }
94    pub fn detail(&self) -> &str {
95        &self.detail
96    }
97}
98
99impl fmt::Display for TransformError {
100    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101        write!(f, "{}: {}", self.context, self.detail)
102    }
103}
104
105impl Error for TransformError {
106    fn source(&self) -> Option<&(dyn Error + 'static)> {
107        self.source
108            .as_deref()
109            .map(|source| source as &(dyn Error + 'static))
110    }
111}
112
113impl From<serde_json::Error> for TransformError {
114    fn from(error: serde_json::Error) -> Self {
115        let message = error.to_string();
116        Self::with_source(TransformErrorKind::InvalidInput, "JSON", message, error)
117    }
118}
119
120impl From<crate::capability::CapabilityError> for TransformError {
121    fn from(error: crate::capability::CapabilityError) -> Self {
122        use crate::capability::CapabilityErrorKind;
123        let kind = match error.kind() {
124            CapabilityErrorKind::Unsupported => TransformErrorKind::Unsupported,
125            CapabilityErrorKind::NotFound | CapabilityErrorKind::Expired => {
126                TransformErrorKind::Host
127            }
128            CapabilityErrorKind::Conflict => TransformErrorKind::Conflict,
129            CapabilityErrorKind::Limit => TransformErrorKind::Limit,
130            CapabilityErrorKind::Invalid => TransformErrorKind::InvalidInput,
131            CapabilityErrorKind::Transport | CapabilityErrorKind::Storage => {
132                TransformErrorKind::Host
133            }
134        };
135        let message = error.to_string();
136        Self::with_source(kind, "host capability", message, error)
137    }
138}
139
140/// A declared field whose representation changes or cannot be retained.
141#[derive(Debug, Clone, Copy, PartialEq, Eq)]
142#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
143pub enum DiagnosticKind {
144    /// A source field or content block omitted because it has no target
145    /// representation. Other supported parts of the conversion are retained.
146    OmittedOptional,
147    RepresentationChanged,
148}
149
150#[derive(Debug, Clone, PartialEq, Eq)]
151pub struct Diagnostic {
152    pub field: String,
153    pub kind: DiagnosticKind,
154    pub message: String,
155}
156
157/// Owned diagnostics for one conversion, never serialized into vendor `rest`.
158#[derive(Debug, Default, Clone, PartialEq, Eq)]
159pub struct Report {
160    pub diagnostics: Vec<Diagnostic>,
161}
162
163impl Report {
164    pub fn omitted(&mut self, field: impl Into<String>, message: impl Into<String>) {
165        self.diagnostics.push(Diagnostic {
166            field: field.into(),
167            kind: DiagnosticKind::OmittedOptional,
168            message: message.into(),
169        });
170    }
171
172    pub fn changed(&mut self, field: impl Into<String>, message: impl Into<String>) {
173        self.diagnostics.push(Diagnostic {
174            field: field.into(),
175            kind: DiagnosticKind::RepresentationChanged,
176            message: message.into(),
177        });
178    }
179}