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