facet_json/deserialize/
error.rs

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