facet_json/deserialize/
error.rs

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