1use std::{borrow::Cow, error, fmt, io, panic::Location, path::PathBuf};
2
3use specta::datatype::OpaqueReference;
4
5use crate::Layout;
6
7use super::legacy::ExportPath;
8
9#[non_exhaustive]
11pub struct Error {
12 kind: ErrorKind,
13}
14
15type FrameworkSource = Box<dyn error::Error + Send + Sync + 'static>;
16
17#[allow(dead_code)]
18enum ErrorKind {
19 BigIntForbidden {
21 path: String,
22 },
23 Serde(specta_serde::Error),
25 ForbiddenName {
27 path: String,
28 name: &'static str,
29 },
30 InvalidName {
32 path: String,
33 name: Cow<'static, str>,
34 },
35 DuplicateTypeName {
40 name: Cow<'static, str>,
41 first: String,
42 second: String,
43 },
44 Io(io::Error),
47 ReadDir {
49 path: PathBuf,
50 source: io::Error,
51 },
52 Metadata {
54 path: PathBuf,
55 source: io::Error,
56 },
57 RemoveFile {
59 path: PathBuf,
60 source: io::Error,
61 },
62 RemoveDir {
64 path: PathBuf,
65 source: io::Error,
66 },
67 UnsupportedOpaqueReference(OpaqueReference),
70 DanglingNamedReference {
72 reference: String,
73 },
74 Framework {
76 message: Cow<'static, str>,
77 source: FrameworkSource,
78 },
79
80 BigIntForbiddenLegacy(ExportPath),
86 ForbiddenNameLegacy(ExportPath, &'static str),
87 InvalidNameLegacy(ExportPath, String),
88 InvalidTaggedVariantContainingTupleStructLegacy(ExportPath),
89 FmtLegacy(std::fmt::Error),
90 UnableToExport(Layout),
91}
92
93impl Error {
94 pub fn framework(
96 message: impl Into<Cow<'static, str>>,
97 source: impl Into<Box<dyn std::error::Error + Send + Sync>>,
98 ) -> Self {
99 Self {
100 kind: ErrorKind::Framework {
101 message: message.into(),
102 source: source.into(),
103 },
104 }
105 }
106
107 pub(crate) fn bigint_forbidden(path: String) -> Self {
108 Self {
109 kind: ErrorKind::BigIntForbidden { path },
110 }
111 }
112
113 pub(crate) fn invalid_name(path: String, name: impl Into<Cow<'static, str>>) -> Self {
114 Self {
115 kind: ErrorKind::InvalidName {
116 path,
117 name: name.into(),
118 },
119 }
120 }
121
122 pub(crate) fn duplicate_type_name(
123 name: Cow<'static, str>,
124 first: Location<'static>,
125 second: Location<'static>,
126 ) -> Self {
127 Self {
128 kind: ErrorKind::DuplicateTypeName {
129 name,
130 first: format_location(first),
131 second: format_location(second),
132 },
133 }
134 }
135
136 pub(crate) fn read_dir(path: PathBuf, source: io::Error) -> Self {
137 Self {
138 kind: ErrorKind::ReadDir { path, source },
139 }
140 }
141
142 pub(crate) fn metadata(path: PathBuf, source: io::Error) -> Self {
143 Self {
144 kind: ErrorKind::Metadata { path, source },
145 }
146 }
147
148 pub(crate) fn remove_file(path: PathBuf, source: io::Error) -> Self {
149 Self {
150 kind: ErrorKind::RemoveFile { path, source },
151 }
152 }
153
154 pub(crate) fn remove_dir(path: PathBuf, source: io::Error) -> Self {
155 Self {
156 kind: ErrorKind::RemoveDir { path, source },
157 }
158 }
159
160 pub(crate) fn unsupported_opaque_reference(reference: OpaqueReference) -> Self {
161 Self {
162 kind: ErrorKind::UnsupportedOpaqueReference(reference),
163 }
164 }
165
166 pub(crate) fn dangling_named_reference(reference: String) -> Self {
167 Self {
168 kind: ErrorKind::DanglingNamedReference { reference },
169 }
170 }
171
172 pub(crate) fn forbidden_name_legacy(
173 path: ExportPath,
174 name: &'static str,
175 ) -> Self {
176 Self {
177 kind: ErrorKind::ForbiddenNameLegacy(path, name),
178 }
179 }
180
181 pub(crate) fn invalid_name_legacy(path: ExportPath, name: String) -> Self {
182 Self {
183 kind: ErrorKind::InvalidNameLegacy(path, name),
184 }
185 }
186
187 pub(crate) fn invalid_tagged_variant_containing_tuple_struct_legacy(path: ExportPath) -> Self {
188 Self {
189 kind: ErrorKind::InvalidTaggedVariantContainingTupleStructLegacy(path),
190 }
191 }
192
193 pub(crate) fn unable_to_export(layout: Layout) -> Self {
194 Self {
195 kind: ErrorKind::UnableToExport(layout),
196 }
197 }
198}
199
200impl From<io::Error> for Error {
201 fn from(error: io::Error) -> Self {
202 Self {
203 kind: ErrorKind::Io(error),
204 }
205 }
206}
207
208impl From<specta_serde::Error> for Error {
209 fn from(error: specta_serde::Error) -> Self {
210 Self {
211 kind: ErrorKind::Serde(error),
212 }
213 }
214}
215
216impl From<std::fmt::Error> for Error {
217 fn from(error: std::fmt::Error) -> Self {
218 Self {
219 kind: ErrorKind::FmtLegacy(error),
220 }
221 }
222}
223
224impl fmt::Display for Error {
225 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226 match &self.kind {
227 ErrorKind::BigIntForbidden { path } => write!(
228 f,
229 "Attempted to export {path:?} but Specta configuration forbids exporting BigInt types (i64, u64, i128, u128) because we don't know if your se/deserializer supports it. If your using a serializer/deserializer that natively has support for BigInt types you can disable this warning by editing your `ExportConfiguration`!"
230 ),
231 ErrorKind::Serde(err) => write!(f, "Detect invalid Serde type: {err}"),
232 ErrorKind::ForbiddenName { path, name } => write!(
233 f,
234 "Attempted to export {path:?} but was unable to due toname {name:?} conflicting with a reserved keyword in Typescript. Try renaming it or using `#[specta(rename = \"new name\")]`"
235 ),
236 ErrorKind::InvalidName { path, name } => write!(
237 f,
238 "Attempted to export {path:?} but was unable to due to name {name:?} containing an invalid character. Try renaming it or using `#[specta(rename = \"new name\")]`"
239 ),
240 ErrorKind::DuplicateTypeName {
241 name,
242 first,
243 second,
244 } => write!(
245 f,
246 "Detected multiple types with the same name: {name:?} at {first} and {second}"
247 ),
248 ErrorKind::Io(err) => write!(f, "IO error: {err}"),
249 ErrorKind::ReadDir { path, source } => {
250 write!(f, "Failed to read directory '{}': {source}", path.display())
251 }
252 ErrorKind::Metadata { path, source } => {
253 write!(
254 f,
255 "Failed to read metadata for '{}': {source}",
256 path.display()
257 )
258 }
259 ErrorKind::RemoveFile { path, source } => {
260 write!(f, "Failed to remove file '{}': {source}", path.display())
261 }
262 ErrorKind::RemoveDir { path, source } => {
263 write!(
264 f,
265 "Failed to remove directory '{}': {source}",
266 path.display()
267 )
268 }
269 ErrorKind::UnsupportedOpaqueReference(reference) => write!(
270 f,
271 "Found unsupported opaque reference '{}'. It is not supported by the Typescript exporter.",
272 reference.type_name()
273 ),
274 ErrorKind::DanglingNamedReference { reference } => write!(
275 f,
276 "Found dangling named reference {reference}. The referenced type is missing from `TypeCollection`."
277 ),
278 ErrorKind::Framework { message, source } => {
279 let source = source.to_string();
280 if message.is_empty() && source.is_empty() {
281 write!(f, "Framework error")
282 } else if source.is_empty() {
283 write!(f, "Framework error: {message}")
284 } else {
285 write!(f, "Framework error: {message}: {source}")
286 }
287 }
288 ErrorKind::BigIntForbiddenLegacy(path) => write!(
289 f,
290 "Attempted to export {path:?} but Specta configuration forbids exporting BigInt types (i64, u64, i128, u128) because we don't know if your se/deserializer supports it. You can change this behavior by editing your `ExportConfiguration`!"
291 ),
292 ErrorKind::ForbiddenNameLegacy(path, name) => write!(
293 f,
294 "Attempted to export {path:?} but was unable to due to name {name:?} conflicting with a reserved keyword in Typescript. Try renaming it or using `#[specta(rename = \"new name\")]`"
295 ),
296 ErrorKind::InvalidNameLegacy(path, name) => write!(
297 f,
298 "Attempted to export {path:?} but was unable to due to name {name:?} containing an invalid character. Try renaming it or using `#[specta(rename = \"new name\")]`"
299 ),
300 ErrorKind::InvalidTaggedVariantContainingTupleStructLegacy(path) => write!(
301 f,
302 "Attempted to export {path:?} with tagging but the variant is a tuple struct."
303 ),
304 ErrorKind::FmtLegacy(err) => write!(f, "formatter: {err:?}"),
305 ErrorKind::UnableToExport(layout) => write!(
306 f,
307 "Unable to export layout {layout} with the current configuration. Maybe try `Exporter::export_to` or switching to Typescript."
308 ),
309 }
310 }
311}
312
313impl fmt::Debug for Error {
314 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
315 fmt::Display::fmt(self, f)
316 }
317}
318
319impl error::Error for Error {
320 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
321 match &self.kind {
322 ErrorKind::Serde(error) => Some(error),
323 ErrorKind::Io(error) => Some(error),
324 ErrorKind::ReadDir { source, .. }
325 | ErrorKind::Metadata { source, .. }
326 | ErrorKind::RemoveFile { source, .. }
327 | ErrorKind::RemoveDir { source, .. } => Some(source),
328 ErrorKind::Framework { source, .. } => Some(source.as_ref()),
329 ErrorKind::FmtLegacy(error) => Some(error),
330 _ => None,
331 }
332 }
333}
334
335fn format_location(location: Location<'static>) -> String {
336 format!(
337 "{}:{}:{}",
338 location.file(),
339 location.line(),
340 location.column()
341 )
342}