1use std::{borrow::Cow, error, fmt};
2
3pub type Result<T> = std::result::Result<T, Error>;
5
6#[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 {}