facet_json/deserialize/
error.rs

1#[cfg(feature = "alloc")]
2use alloc::format;
3#[cfg(feature = "alloc")]
4use alloc::string::String;
5
6#[cfg(feature = "rich-diagnostics")]
7use ariadne::{Color, Config, IndexType, Label, Report, ReportKind, Source};
8use facet_reflect::ReflectError;
9use owo_colors::OwoColorize;
10
11use super::{Token, TokenErrorKind, tokenizer::Span};
12use facet_core::Def;
13use facet_core::Shape;
14
15/// A JSON parse error, with context. Never would've guessed huh.
16pub struct JsonError<'input> {
17    /// The input associated with the error.
18    pub input: alloc::borrow::Cow<'input, [u8]>,
19
20    /// Where the error occured
21    pub span: Span,
22
23    /// Where we were in the struct when the error occured
24    pub path: String,
25
26    /// The specific error that occurred while parsing the JSON.
27    pub kind: JsonErrorKind,
28}
29
30impl<'input> JsonError<'input> {
31    /// Creates a new `JsonParseErrorWithContext`.
32    ///
33    /// # Arguments
34    ///
35    /// * `kind` - The kind of JSON error encountered.
36    /// * `input` - The original input being parsed.
37    /// * `pos` - The position in the input where the error occurred.
38    pub fn new(kind: JsonErrorKind, input: &'input [u8], span: Span, path: String) -> Self {
39        Self {
40            input: alloc::borrow::Cow::Borrowed(input),
41            span,
42            kind,
43            path,
44        }
45    }
46
47    /// Returns a wrapper type that displays a human-readable error message for this JSON error.
48    pub fn message(&self) -> JsonErrorMessage<'_> {
49        JsonErrorMessage(self)
50    }
51}
52
53/// A wrapper type for displaying JSON error messages
54pub struct JsonErrorMessage<'a>(&'a JsonError<'a>);
55
56impl core::fmt::Display for JsonErrorMessage<'_> {
57    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
58        match &self.0.kind {
59            JsonErrorKind::UnexpectedEof(msg) => write!(f, "Unexpected end of file: {}", msg.red()),
60            JsonErrorKind::MissingField(fld) => write!(f, "Missing required field: {}", fld.red()),
61            JsonErrorKind::UnexpectedToken { got, wanted } => {
62                write!(
63                    f,
64                    "Unexpected token: got {}, wanted {}",
65                    got.red(),
66                    wanted.green()
67                )
68            }
69            JsonErrorKind::NumberOutOfRange(n) => {
70                write!(f, "Number out of range: {}", n.red())
71            }
72            JsonErrorKind::StringAsNumber(s) => {
73                write!(f, "Expected a string but got number: {}", s.red())
74            }
75            JsonErrorKind::UnknownField { field_name, shape } => {
76                write!(
77                    f,
78                    "Unknown field: {} for shape {}",
79                    field_name.red(),
80                    shape.yellow()
81                )
82            }
83            JsonErrorKind::InvalidUtf8(e) => write!(f, "Invalid UTF-8 encoding: {}", e.red()),
84            JsonErrorKind::ReflectError(e) => write!(f, "{e}"),
85            JsonErrorKind::SyntaxError(e) => write!(f, "{e}"),
86            JsonErrorKind::Unimplemented(s) => {
87                write!(f, "Feature not yet implemented: {}", s.yellow())
88            }
89            JsonErrorKind::UnsupportedType { got, wanted } => {
90                write!(
91                    f,
92                    "Unsupported type: got {}, wanted {}",
93                    got.red(),
94                    wanted.green()
95                )
96            }
97            JsonErrorKind::NoSuchVariant { name, enum_shape } => match enum_shape.def {
98                Def::Enum(ed) => {
99                    write!(
100                        f,
101                        "Enum variant not found: {} in enum {}. Available variants: [",
102                        name.red(),
103                        enum_shape.yellow()
104                    )?;
105
106                    let mut first = true;
107                    for variant in ed.variants.iter() {
108                        if !first {
109                            write!(f, ", ")?;
110                        }
111                        write!(f, "{}", variant.name.green())?;
112                        first = false;
113                    }
114
115                    write!(f, "]")
116                }
117                _ => {
118                    write!(
119                        f,
120                        "Enum variant not found: {} in enum {}. No variants available (not an enum)",
121                        name.red(),
122                        enum_shape.yellow()
123                    )
124                }
125            },
126        }
127    }
128}
129
130/// An error kind for JSON parsing.
131#[derive(Debug, PartialEq, Clone)]
132pub enum JsonErrorKind {
133    /// The input ended unexpectedly while parsing JSON.
134    UnexpectedEof(&'static str),
135    /// A required struct field was missing at the end of JSON input.
136    MissingField(&'static str),
137    /// An unexpected token was encountered in the input.
138    UnexpectedToken {
139        /// The hero we got
140        got: Token,
141
142        /// The hero we wanted
143        wanted: &'static str,
144    },
145    /// A number is out of range.
146    NumberOutOfRange(f64),
147    /// An unexpected String was encountered in the input.
148    StringAsNumber(String),
149    /// An unexpected field name was encountered in the input.
150    UnknownField {
151        /// The name of the field that was not recognized
152        field_name: String,
153        /// The shape definition where the unknown field was encountered
154        shape: &'static Shape,
155    },
156    /// A string that could not be built into valid UTF-8 Unicode
157    InvalidUtf8(String),
158    /// An error occurred while reflecting a type.
159    ReflectError(ReflectError),
160    /// An error occurred while parsing a token.
161    SyntaxError(TokenErrorKind),
162    /// Some feature is not yet implemented (under development).
163    Unimplemented(&'static str),
164    /// An unsupported type was encountered.
165    UnsupportedType {
166        /// The shape we got
167        got: &'static Shape,
168
169        /// The shape we wanted
170        wanted: &'static str,
171    },
172    /// An enum variant name that doesn't exist in the enum definition.
173    NoSuchVariant {
174        /// The name of the variant that was not found
175        name: String,
176
177        /// The enum shape definition where the variant was looked up
178        enum_shape: &'static Shape,
179    },
180}
181
182impl From<ReflectError> for JsonErrorKind {
183    fn from(err: ReflectError) -> Self {
184        JsonErrorKind::ReflectError(err)
185    }
186}
187
188#[cfg(not(feature = "rich-diagnostics"))]
189impl core::fmt::Display for JsonError<'_> {
190    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
191        write!(
192            f,
193            "{} at byte {} in path {}",
194            self.message(),
195            self.span.start,
196            self.path
197        )
198    }
199}
200
201#[cfg(feature = "rich-diagnostics")]
202impl core::fmt::Display for JsonError<'_> {
203    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
204        let Ok(input_str) = core::str::from_utf8(&self.input[..]) else {
205            return write!(f, "(JSON input was invalid UTF-8)");
206        };
207
208        let source_id = "json";
209        let span_start = self.span.start();
210        let span_end = self.span.end();
211
212        let mut report = Report::build(ReportKind::Error, (source_id, span_start..span_end))
213            .with_message(format!("Error at {}", self.path.yellow()))
214            .with_config(Config::new().with_index_type(IndexType::Byte));
215
216        let label = Label::new((source_id, span_start..span_end))
217            .with_message(self.message())
218            .with_color(Color::Red);
219
220        report = report.with_label(label);
221
222        let source = Source::from(input_str);
223
224        let mut writer = Vec::new();
225        let cache = (source_id, &source);
226
227        if report.finish().write(cache, &mut writer).is_err() {
228            return write!(f, "Error formatting with ariadne");
229        }
230
231        if let Ok(output) = String::from_utf8(writer) {
232            write!(f, "{}", output)
233        } else {
234            write!(f, "Error converting ariadne output to string")
235        }
236    }
237}
238
239impl core::fmt::Debug for JsonError<'_> {
240    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
241        core::fmt::Display::fmt(self, f)
242    }
243}
244
245impl core::error::Error for JsonError<'_> {}