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