Skip to main content

specta_serde/
error.rs

1use std::{borrow::Cow, error, fmt};
2
3/// Result type for `specta-serde` operations.
4pub type Result<T> = std::result::Result<T, Error>;
5
6/// Error type for serde transformation and validation failures.
7#[non_exhaustive]
8pub struct Error {
9    kind: ErrorKind,
10}
11
12#[derive(Debug)]
13enum ErrorKind {
14    InvalidUsageOfSkip {
15        path: String,
16        reason: Cow<'static, str>,
17    },
18    InvalidInternallyTaggedEnum {
19        path: String,
20        variant: String,
21        reason: Cow<'static, str>,
22    },
23    UnresolvedGenericReference {
24        path: String,
25        generic: String,
26    },
27    InvalidEnumRepresentation {
28        reason: Cow<'static, str>,
29    },
30    InvalidExternalTaggedVariant {
31        variant: String,
32    },
33    InvalidAdjacentTaggedVariant {
34        variant: String,
35    },
36    InvalidInternallyTaggedVariant {
37        variant: String,
38        reason: Cow<'static, str>,
39    },
40    IncompatibleRename {
41        context: Cow<'static, str>,
42        name: String,
43        serialize: Option<String>,
44        deserialize: Option<String>,
45    },
46    IncompatibleConversion {
47        context: Cow<'static, str>,
48        name: String,
49        serialize: Option<String>,
50        deserialize: Option<String>,
51    },
52    InvalidConversionUsage {
53        path: String,
54        reason: Cow<'static, str>,
55    },
56    UnsupportedSerdeCustomCodec {
57        path: String,
58        attribute: Cow<'static, str>,
59    },
60    InvalidPhasedTypeUsage {
61        path: String,
62        reason: Cow<'static, str>,
63    },
64    InvalidRenameRule {
65        attribute: Cow<'static, str>,
66        value: String,
67    },
68}
69
70impl Error {
71    pub(crate) fn invalid_usage_of_skip(
72        path: impl Into<String>,
73        reason: impl Into<Cow<'static, str>>,
74    ) -> Self {
75        Self {
76            kind: ErrorKind::InvalidUsageOfSkip {
77                path: path.into(),
78                reason: reason.into(),
79            },
80        }
81    }
82
83    pub(crate) fn invalid_internally_tagged_enum(
84        path: impl Into<String>,
85        variant: impl Into<String>,
86        reason: impl Into<Cow<'static, str>>,
87    ) -> Self {
88        Self {
89            kind: ErrorKind::InvalidInternallyTaggedEnum {
90                path: path.into(),
91                variant: variant.into(),
92                reason: reason.into(),
93            },
94        }
95    }
96
97    pub(crate) fn unresolved_generic_reference(
98        path: impl Into<String>,
99        generic: impl Into<String>,
100    ) -> Self {
101        Self {
102            kind: ErrorKind::UnresolvedGenericReference {
103                path: path.into(),
104                generic: generic.into(),
105            },
106        }
107    }
108
109    pub(crate) fn invalid_enum_representation(reason: impl Into<Cow<'static, str>>) -> Self {
110        Self {
111            kind: ErrorKind::InvalidEnumRepresentation {
112                reason: reason.into(),
113            },
114        }
115    }
116
117    pub(crate) fn invalid_external_tagged_variant(variant: impl Into<String>) -> Self {
118        Self {
119            kind: ErrorKind::InvalidExternalTaggedVariant {
120                variant: variant.into(),
121            },
122        }
123    }
124
125    pub(crate) fn invalid_adjacent_tagged_variant(variant: impl Into<String>) -> Self {
126        Self {
127            kind: ErrorKind::InvalidAdjacentTaggedVariant {
128                variant: variant.into(),
129            },
130        }
131    }
132
133    pub(crate) fn invalid_internally_tagged_variant(
134        variant: impl Into<String>,
135        reason: impl Into<Cow<'static, str>>,
136    ) -> Self {
137        Self {
138            kind: ErrorKind::InvalidInternallyTaggedVariant {
139                variant: variant.into(),
140                reason: reason.into(),
141            },
142        }
143    }
144
145    pub(crate) fn incompatible_rename(
146        context: impl Into<Cow<'static, str>>,
147        name: impl Into<String>,
148        serialize: Option<String>,
149        deserialize: Option<String>,
150    ) -> Self {
151        Self {
152            kind: ErrorKind::IncompatibleRename {
153                context: context.into(),
154                name: name.into(),
155                serialize,
156                deserialize,
157            },
158        }
159    }
160
161    pub(crate) fn incompatible_conversion(
162        context: impl Into<Cow<'static, str>>,
163        name: impl Into<String>,
164        serialize: Option<String>,
165        deserialize: Option<String>,
166    ) -> Self {
167        Self {
168            kind: ErrorKind::IncompatibleConversion {
169                context: context.into(),
170                name: name.into(),
171                serialize,
172                deserialize,
173            },
174        }
175    }
176
177    pub(crate) fn invalid_conversion_usage(
178        path: impl Into<String>,
179        reason: impl Into<Cow<'static, str>>,
180    ) -> Self {
181        Self {
182            kind: ErrorKind::InvalidConversionUsage {
183                path: path.into(),
184                reason: reason.into(),
185            },
186        }
187    }
188
189    pub(crate) fn unsupported_serde_custom_codec(
190        path: impl Into<String>,
191        attribute: impl Into<Cow<'static, str>>,
192    ) -> Self {
193        Self {
194            kind: ErrorKind::UnsupportedSerdeCustomCodec {
195                path: path.into(),
196                attribute: attribute.into(),
197            },
198        }
199    }
200
201    pub(crate) fn invalid_phased_type_usage(
202        path: impl Into<String>,
203        reason: impl Into<Cow<'static, str>>,
204    ) -> Self {
205        Self {
206            kind: ErrorKind::InvalidPhasedTypeUsage {
207                path: path.into(),
208                reason: reason.into(),
209            },
210        }
211    }
212
213    pub(crate) fn invalid_rename_rule(
214        attribute: impl Into<Cow<'static, str>>,
215        value: impl Into<String>,
216    ) -> Self {
217        Self {
218            kind: ErrorKind::InvalidRenameRule {
219                attribute: attribute.into(),
220                value: value.into(),
221            },
222        }
223    }
224}
225
226impl fmt::Display for Error {
227    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
228        match &self.kind {
229            ErrorKind::InvalidUsageOfSkip { path, reason } => {
230                write!(f, "Invalid usage of #[serde(skip)] at '{path}': {reason}")
231            }
232            ErrorKind::InvalidInternallyTaggedEnum {
233                path,
234                variant,
235                reason,
236            } => write!(
237                f,
238                "Invalid internally tagged enum at '{path}', variant '{variant}': {reason}"
239            ),
240            ErrorKind::UnresolvedGenericReference { path, generic } => write!(
241                f,
242                "Unresolved generic reference '{generic}' while validating '{path}'"
243            ),
244            ErrorKind::InvalidEnumRepresentation { reason } => {
245                write!(f, "Invalid serde enum representation: {reason}")
246            }
247            ErrorKind::InvalidExternalTaggedVariant { variant } => write!(
248                f,
249                "Invalid externally tagged enum variant '{variant}': variant payload is fully skipped"
250            ),
251            ErrorKind::InvalidAdjacentTaggedVariant { variant } => write!(
252                f,
253                "Invalid adjacently tagged enum variant '{variant}': variant payload is fully skipped"
254            ),
255            ErrorKind::InvalidInternallyTaggedVariant { variant, reason } => write!(
256                f,
257                "Invalid internally tagged enum variant '{variant}': {reason}"
258            ),
259            ErrorKind::IncompatibleRename {
260                context,
261                name,
262                serialize,
263                deserialize,
264            } => write!(
265                f,
266                "Incompatible {context} for '{name}' in unified mode: serialize={serialize:?}, deserialize={deserialize:?}"
267            ),
268            ErrorKind::IncompatibleConversion {
269                context,
270                name,
271                serialize,
272                deserialize,
273            } => write!(
274                f,
275                "Incompatible {context} for '{name}' in unified mode: serialize={serialize:?}, deserialize={deserialize:?}. Use apply_phases for asymmetric serde conversions"
276            ),
277            ErrorKind::InvalidConversionUsage { path, reason } => {
278                write!(
279                    f,
280                    "Invalid usage of serde conversion attributes at '{path}': {reason}"
281                )
282            }
283            ErrorKind::UnsupportedSerdeCustomCodec { path, attribute } => write!(
284                f,
285                "Unsupported serde attribute at '{path}': #[serde({attribute})] changes the wire type. Add #[specta(type = ...)] (or #[specta(type = specta_serde::Phased<Serialize, Deserialize>)])"
286            ),
287            ErrorKind::InvalidPhasedTypeUsage { path, reason } => {
288                write!(f, "Invalid phased type usage at '{path}': {reason}")
289            }
290            ErrorKind::InvalidRenameRule { attribute, value } => {
291                write!(f, "Invalid serde rename rule for '{attribute}': {value:?}")
292            }
293        }
294    }
295}
296
297impl fmt::Debug for Error {
298    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
299        fmt::Display::fmt(self, f)
300    }
301}
302
303impl error::Error for Error {}