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::{Def, Shape};
7use facet_reflect::ReflectError;
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}
107
108impl<'input> DeserError<'input> {
109    /// Creates a new deser error, preserving input and location context for accurate reporting.
110    pub fn new(kind: DeserErrorKind, input: &'input [u8], span: Span) -> Self {
111        Self {
112            input: alloc::borrow::Cow::Borrowed(input),
113            span,
114            kind,
115        }
116    }
117
118    /// Constructs a reflection-related deser error, keeping contextual information intact.
119    pub(crate) fn new_reflect(e: ReflectError, input: &'input [u8], span: Span) -> Self {
120        DeserError::new(DeserErrorKind::ReflectError(e), input, span)
121    }
122
123    /// Provides a human-friendly message wrapper to improve error readability.
124    pub fn message(&self) -> DeserErrorMessage<'_> {
125        DeserErrorMessage(self)
126    }
127}
128
129/// A wrapper type for displaying deser error messages
130pub struct DeserErrorMessage<'a>(&'a DeserError<'a>);
131
132impl core::fmt::Display for DeserErrorMessage<'_> {
133    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
134        match &self.0.kind {
135            DeserErrorKind::UnexpectedByte { got, wanted } => write!(
136                f,
137                "Unexpected byte: got 0x{:02X}, wanted {}",
138                got.red(),
139                wanted.yellow()
140            ),
141            DeserErrorKind::UnexpectedChar { got, wanted } => write!(
142                f,
143                "Unexpected character: got '{}', wanted {}",
144                got.red(),
145                wanted.yellow()
146            ),
147            DeserErrorKind::UnexpectedOutcome { got, wanted } => {
148                write!(f, "Unexpected {}, wanted {}", got.red(), wanted.yellow())
149            }
150            DeserErrorKind::UnexpectedEof { wanted } => {
151                write!(f, "Unexpected end of file: wanted {}", wanted.red())
152            }
153            DeserErrorKind::MissingField(fld) => write!(f, "Missing required field: {}", fld.red()),
154            DeserErrorKind::NumberOutOfRange(n) => {
155                write!(f, "Number out of range: {}", n.red())
156            }
157            DeserErrorKind::StringAsNumber(s) => {
158                write!(f, "Expected a string but got number: {}", s.red())
159            }
160            DeserErrorKind::UnknownField { field_name, shape } => {
161                write!(
162                    f,
163                    "Unknown field: {} for shape {}",
164                    field_name.red(),
165                    shape.yellow()
166                )
167            }
168            DeserErrorKind::InvalidUtf8(e) => write!(f, "Invalid UTF-8 encoding: {}", e.red()),
169            DeserErrorKind::ReflectError(e) => write!(f, "{e}"),
170            DeserErrorKind::Unimplemented(s) => {
171                write!(f, "Feature not yet implemented: {}", s.yellow())
172            }
173            DeserErrorKind::UnsupportedType { got, wanted } => {
174                write!(
175                    f,
176                    "Unsupported type: got {}, wanted {}",
177                    got.red(),
178                    wanted.green()
179                )
180            }
181            DeserErrorKind::NoSuchVariant { name, enum_shape } => match enum_shape.def {
182                Def::Enum(ed) => {
183                    write!(
184                        f,
185                        "Enum variant not found: {} in enum {}. Available variants: [",
186                        name.red(),
187                        enum_shape.yellow()
188                    )?;
189
190                    let mut first = true;
191                    for variant in ed.variants.iter() {
192                        if !first {
193                            write!(f, ", ")?;
194                        }
195                        write!(f, "{}", variant.name.green())?;
196                        first = false;
197                    }
198
199                    write!(f, "]")
200                }
201                _ => {
202                    write!(
203                        f,
204                        "Enum variant not found: {} in enum {}. No variants available (not an enum)",
205                        name.red(),
206                        enum_shape.yellow()
207                    )
208                }
209            },
210        }
211    }
212}
213
214#[cfg(not(feature = "rich-diagnostics"))]
215impl core::fmt::Display for DeserError<'_> {
216    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
217        write!(f, "{} at byte {}", self.message(), self.span.start(),)
218    }
219}
220
221#[cfg(feature = "rich-diagnostics")]
222impl core::fmt::Display for DeserError<'_> {
223    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
224        let Ok(input_str) = core::str::from_utf8(&self.input[..]) else {
225            return write!(f, "(JSON input was invalid UTF-8)");
226        };
227
228        let source_id = "json";
229        let span_start = self.span.start();
230        let span_end = self.span.end();
231
232        let mut report = Report::build(ReportKind::Error, (source_id, span_start..span_end))
233            .with_config(Config::new().with_index_type(IndexType::Byte));
234
235        let label = Label::new((source_id, span_start..span_end))
236            .with_message(self.message())
237            .with_color(Color::Red);
238
239        report = report.with_label(label);
240
241        let source = Source::from(input_str);
242
243        struct FmtWriter<'a, 'b: 'a> {
244            f: &'a mut core::fmt::Formatter<'b>,
245            error: Option<core::fmt::Error>,
246        }
247
248        impl core::fmt::Write for FmtWriter<'_, '_> {
249            fn write_str(&mut self, s: &str) -> core::fmt::Result {
250                if self.error.is_some() {
251                    // Already failed, do nothing
252                    return Err(core::fmt::Error);
253                }
254                if let Err(e) = self.f.write_str(s) {
255                    self.error = Some(e);
256                    Err(core::fmt::Error)
257                } else {
258                    Ok(())
259                }
260            }
261        }
262
263        struct IoWriter<'a, 'b: 'a> {
264            inner: FmtWriter<'a, 'b>,
265        }
266
267        impl std::io::Write for IoWriter<'_, '_> {
268            fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
269                match core::str::from_utf8(buf) {
270                    Ok(s) => match core::fmt::Write::write_str(&mut self.inner, s) {
271                        Ok(()) => Ok(buf.len()),
272                        Err(_) => Err(std::io::ErrorKind::Other.into()),
273                    },
274                    Err(_) => Err(std::io::ErrorKind::InvalidData.into()),
275                }
276            }
277            fn flush(&mut self) -> std::io::Result<()> {
278                Ok(())
279            }
280        }
281
282        let cache = (source_id, &source);
283
284        let fmt_writer = FmtWriter { f, error: None };
285        let mut io_writer = IoWriter { inner: fmt_writer };
286
287        if report.finish().write(cache, &mut io_writer).is_err() {
288            return write!(f, "Error formatting with ariadne");
289        }
290
291        // Check if our adapter ran into a formatting error
292        if io_writer.inner.error.is_some() {
293            return write!(f, "Error writing ariadne output to fmt::Formatter");
294        }
295
296        Ok(())
297    }
298}
299
300impl core::error::Error for DeserError<'_> {}