Skip to main content

gproxy_protocol/transform/
error.rs

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