facet_deserialize/
error.rs

1#[cfg(feature = "rich-diagnostics")]
2use ariadne::{Color, Config, IndexType, Label, Report, ReportKind, Source};
3
4use alloc::string::String;
5
6use facet_core::{Shape, Type, UserType};
7use facet_reflect::{ReflectError, VariantError};
8use owo_colors::OwoColorize;
9
10use crate::debug::InputDebug;
11use crate::{Outcome, Span};
12
13/// A JSON parse error, with context. Never would've guessed huh.
14#[derive(Debug)]
15pub struct DeserError<'input> {
16    /// The input associated with the error.
17    pub input: alloc::borrow::Cow<'input, [u8]>,
18
19    /// Where the error occured
20    pub span: Span,
21
22    /// The specific error that occurred while parsing the JSON.
23    pub kind: DeserErrorKind,
24
25    /// The source identifier for error reporting
26    pub source_id: &'static str,
27}
28
29impl DeserError<'_> {
30    /// Converts the error into an owned error.
31    pub fn into_owned(self) -> DeserError<'static> {
32        DeserError {
33            input: self.input.into_owned().into(),
34            span: self.span,
35            kind: self.kind,
36            source_id: self.source_id,
37        }
38    }
39
40    /// Sets the span of this error
41    pub fn with_span(mut self, span: Span) -> Self {
42        self.span = span;
43        self
44    }
45}
46
47/// An error kind for JSON parsing.
48#[derive(Debug, PartialEq, Clone)]
49pub enum DeserErrorKind {
50    /// An unexpected byte was encountered in the input.
51    UnexpectedByte {
52        /// The byte that was found.
53        got: u8,
54        /// The expected value as a string description.
55        wanted: &'static str,
56    },
57    /// An unexpected character was encountered in the input.
58    UnexpectedChar {
59        /// The character that was found.
60        got: char,
61        /// The expected value as a string description.
62        wanted: &'static str,
63    },
64    /// An unexpected outcome was encountered in the input.
65    UnexpectedOutcome {
66        /// The outcome that was found.
67        got: Outcome<'static>,
68        /// The expected value as a string description.
69        wanted: &'static str,
70    },
71    /// The input ended unexpectedly while parsing JSON.
72    UnexpectedEof {
73        /// The expected value as a string description.
74        wanted: &'static str,
75    },
76    /// Indicates a value was expected to follow an element in the input.
77    MissingValue {
78        /// Describes what type of value was expected.
79        expected: &'static str,
80        /// The element that requires the missing value.
81        field: String,
82    },
83    /// A required struct field was missing at the end of JSON input.
84    MissingField(&'static str),
85    /// A number is out of range.
86    NumberOutOfRange(f64),
87    /// An unexpected String was encountered in the input.
88    StringAsNumber(String),
89    /// An unexpected field name was encountered in the input.
90    UnknownField {
91        /// The name of the field that was not recognized
92        field_name: String,
93        /// The shape definition where the unknown field was encountered
94        shape: &'static Shape,
95    },
96    /// A string that could not be built into valid UTF-8 Unicode
97    InvalidUtf8(String),
98    /// An error occurred while reflecting a type.
99    ReflectError(ReflectError),
100    /// Some feature is not yet implemented (under development).
101    Unimplemented(&'static str),
102    /// An unsupported type was encountered.
103    UnsupportedType {
104        /// The shape we got
105        got: &'static Shape,
106
107        /// The shape we wanted
108        wanted: &'static str,
109    },
110    /// An enum variant name that doesn't exist in the enum definition.
111    NoSuchVariant {
112        /// The name of the variant that was not found
113        name: String,
114
115        /// The enum shape definition where the variant was looked up
116        enum_shape: &'static Shape,
117    },
118    /// An error occurred when reflecting an enum variant (index) from a user type.
119    VariantError(VariantError),
120}
121
122impl<'input> DeserError<'input> {
123    /// Creates a new deser error, preserving input and location context for accurate reporting.
124    pub fn new<I>(
125        kind: DeserErrorKind,
126        input: &'input I,
127        span: Span,
128        source_id: &'static str,
129    ) -> Self
130    where
131        I: ?Sized + 'input + InputDebug,
132    {
133        Self {
134            input: input.as_cow(),
135            span,
136            kind,
137            source_id,
138        }
139    }
140
141    // pub fn new<I>(kind: DeserErrorKind, input: &'input I, span: Span, source_id: &'static str) -> Self
142    // where
143    //     I: ?Sized + 'input + InputDebug,
144    // {
145    //     Self::with_source(kind, input, span, source_id)
146    // }
147
148    /// Constructs a reflection-related deser error, keeping contextual information intact.
149    pub(crate) fn new_reflect<I>(
150        e: ReflectError,
151        input: &'input I,
152        span: Span,
153        source_id: &'static str,
154    ) -> Self
155    where
156        I: ?Sized + 'input + InputDebug,
157    {
158        DeserError::new(DeserErrorKind::ReflectError(e), input, span, source_id)
159    }
160
161    // /// Give the caller full control
162    // pub fn with_source<I>(
163    //     kind: DeserErrorKind,
164    //     input: &'input I,
165    //     span: Span,
166    //     source_id: &'static str,
167    // ) -> Self
168    // where
169    //     I: ?Sized + 'input + InputDebug,
170    // {
171    //     Self {
172    //         input: input.as_cow(),
173    //         span,
174    //         kind,
175    //         source_id,
176    //     }
177    // }
178
179    /// Sets the source ID for this error
180    pub fn with_source_id(mut self, source_id: &'static str) -> Self {
181        self.source_id = source_id;
182        self
183    }
184
185    /// Provides a human-friendly message wrapper to improve error readability.
186    pub fn message(&self) -> DeserErrorMessage<'_> {
187        DeserErrorMessage(self)
188    }
189}
190
191/// A wrapper type for displaying deser error messages
192pub struct DeserErrorMessage<'a>(&'a DeserError<'a>);
193
194impl core::fmt::Display for DeserErrorMessage<'_> {
195    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
196        match &self.0.kind {
197            DeserErrorKind::UnexpectedByte { got, wanted } => write!(
198                f,
199                "Unexpected byte: got 0x{:02X}, wanted {}",
200                got.red(),
201                wanted.yellow()
202            ),
203            DeserErrorKind::UnexpectedChar { got, wanted } => write!(
204                f,
205                "Unexpected character: got '{}', wanted {}",
206                got.red(),
207                wanted.yellow()
208            ),
209            DeserErrorKind::UnexpectedOutcome { got, wanted } => {
210                write!(f, "Unexpected {}, wanted {}", got.red(), wanted.yellow())
211            }
212            DeserErrorKind::UnexpectedEof { wanted } => {
213                write!(f, "Unexpected end of file: wanted {}", wanted.red())
214            }
215            DeserErrorKind::MissingValue { expected, field } => {
216                write!(f, "Missing {} for {}", expected.red(), field.yellow())
217            }
218            DeserErrorKind::MissingField(fld) => write!(f, "Missing required field: {}", fld.red()),
219            DeserErrorKind::NumberOutOfRange(n) => {
220                write!(f, "Number out of range: {}", n.red())
221            }
222            DeserErrorKind::StringAsNumber(s) => {
223                write!(f, "Expected a string but got number: {}", s.red())
224            }
225            DeserErrorKind::UnknownField { field_name, shape } => {
226                write!(
227                    f,
228                    "Unknown field: {} for shape {}",
229                    field_name.red(),
230                    shape.yellow()
231                )
232            }
233            DeserErrorKind::InvalidUtf8(e) => write!(f, "Invalid UTF-8 encoding: {}", e.red()),
234            DeserErrorKind::ReflectError(e) => write!(f, "{e}"),
235            DeserErrorKind::Unimplemented(s) => {
236                write!(f, "Feature not yet implemented: {}", s.yellow())
237            }
238            DeserErrorKind::UnsupportedType { got, wanted } => {
239                write!(
240                    f,
241                    "Unsupported type: got {}, wanted {}",
242                    got.red(),
243                    wanted.green()
244                )
245            }
246            DeserErrorKind::NoSuchVariant { name, enum_shape } => {
247                if let Type::User(UserType::Enum(ed)) = enum_shape.ty {
248                    write!(
249                        f,
250                        "Enum variant not found: {} in enum {}. Available variants: [",
251                        name.red(),
252                        enum_shape.yellow()
253                    )?;
254
255                    let mut first = true;
256                    for variant in ed.variants.iter() {
257                        if !first {
258                            write!(f, ", ")?;
259                        }
260                        write!(f, "{}", variant.name.green())?;
261                        first = false;
262                    }
263
264                    write!(f, "]")?;
265                    Ok(())
266                } else {
267                    write!(
268                        f,
269                        "Enum variant not found: {} in non-enum type {}",
270                        name.red(),
271                        enum_shape.yellow()
272                    )?;
273                    Ok(())
274                }
275            }
276            DeserErrorKind::VariantError(e) => {
277                write!(f, "Variant error: {e}")
278            }
279        }
280    }
281}
282
283#[cfg(not(feature = "rich-diagnostics"))]
284impl core::fmt::Display for DeserError<'_> {
285    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
286        write!(f, "{} at byte {}", self.message(), self.span.start(),)
287    }
288}
289
290#[cfg(feature = "rich-diagnostics")]
291impl core::fmt::Display for DeserError<'_> {
292    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
293        let Ok(input_str) = core::str::from_utf8(&self.input[..]) else {
294            return write!(f, "(JSON input was invalid UTF-8)");
295        };
296
297        let source_id = self.source_id;
298        let span_start = self.span.start();
299        let span_end = self.span.end();
300
301        let mut report = Report::build(ReportKind::Error, (source_id, span_start..span_end))
302            .with_config(Config::new().with_index_type(IndexType::Byte));
303
304        let label = Label::new((source_id, span_start..span_end))
305            .with_message(self.message())
306            .with_color(Color::Red);
307
308        report = report.with_label(label);
309
310        let source = Source::from(input_str);
311
312        struct FmtWriter<'a, 'b: 'a> {
313            f: &'a mut core::fmt::Formatter<'b>,
314            error: Option<core::fmt::Error>,
315        }
316
317        impl core::fmt::Write for FmtWriter<'_, '_> {
318            fn write_str(&mut self, s: &str) -> core::fmt::Result {
319                if self.error.is_some() {
320                    // Already failed, do nothing
321                    return Err(core::fmt::Error);
322                }
323                if let Err(e) = self.f.write_str(s) {
324                    self.error = Some(e);
325                    Err(core::fmt::Error)
326                } else {
327                    Ok(())
328                }
329            }
330        }
331
332        struct IoWriter<'a, 'b: 'a> {
333            inner: FmtWriter<'a, 'b>,
334        }
335
336        impl std::io::Write for IoWriter<'_, '_> {
337            fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
338                match core::str::from_utf8(buf) {
339                    Ok(s) => match core::fmt::Write::write_str(&mut self.inner, s) {
340                        Ok(()) => Ok(buf.len()),
341                        Err(_) => Err(std::io::ErrorKind::Other.into()),
342                    },
343                    Err(_) => Err(std::io::ErrorKind::InvalidData.into()),
344                }
345            }
346            fn flush(&mut self) -> std::io::Result<()> {
347                Ok(())
348            }
349        }
350
351        let cache = (source_id, &source);
352
353        let fmt_writer = FmtWriter { f, error: None };
354        let mut io_writer = IoWriter { inner: fmt_writer };
355
356        if report.finish().write(cache, &mut io_writer).is_err() {
357            return write!(f, "Error formatting with ariadne");
358        }
359
360        // Check if our adapter ran into a formatting error
361        if io_writer.inner.error.is_some() {
362            return write!(f, "Error writing ariadne output to fmt::Formatter");
363        }
364
365        Ok(())
366    }
367}
368
369impl core::error::Error for DeserError<'_> {}