serde_json_pythonic/
error.rs

1//! When serializing or deserializing JSON goes wrong.
2
3use crate::io;
4use alloc::boxed::Box;
5use alloc::string::{String, ToString};
6use core::fmt::{self, Debug, Display};
7use core::result;
8use core::str::FromStr;
9use serde::{de, ser};
10#[cfg(feature = "std")]
11use std::error;
12#[cfg(feature = "std")]
13use std::io::ErrorKind;
14
15/// This type represents all possible errors that can occur when serializing or
16/// deserializing JSON data.
17pub struct Error {
18    /// This `Box` allows us to keep the size of `Error` as small as possible. A
19    /// larger `Error` type was substantially slower due to all the functions
20    /// that pass around `Result<T, Error>`.
21    err: Box<ErrorImpl>,
22}
23
24/// Alias for a `Result` with the error type `serde_json_pythonic::Error`.
25pub type Result<T> = result::Result<T, Error>;
26
27impl Error {
28    /// One-based line number at which the error was detected.
29    ///
30    /// Characters in the first line of the input (before the first newline
31    /// character) are in line 1.
32    pub fn line(&self) -> usize {
33        self.err.line
34    }
35
36    /// One-based column number at which the error was detected.
37    ///
38    /// The first character in the input and any characters immediately
39    /// following a newline character are in column 1.
40    ///
41    /// Note that errors may occur in column 0, for example if a read from an
42    /// I/O stream fails immediately following a previously read newline
43    /// character.
44    pub fn column(&self) -> usize {
45        self.err.column
46    }
47
48    /// Categorizes the cause of this error.
49    ///
50    /// - `Category::Io` - failure to read or write bytes on an I/O stream
51    /// - `Category::Syntax` - input that is not syntactically valid JSON
52    /// - `Category::Data` - input data that is semantically incorrect
53    /// - `Category::Eof` - unexpected end of the input data
54    pub fn classify(&self) -> Category {
55        match self.err.code {
56            ErrorCode::Message(_) => Category::Data,
57            ErrorCode::Io(_) => Category::Io,
58            ErrorCode::EofWhileParsingList
59            | ErrorCode::EofWhileParsingObject
60            | ErrorCode::EofWhileParsingString
61            | ErrorCode::EofWhileParsingValue => Category::Eof,
62            ErrorCode::ExpectedColon
63            | ErrorCode::ExpectedListCommaOrEnd
64            | ErrorCode::ExpectedObjectCommaOrEnd
65            | ErrorCode::ExpectedSomeIdent
66            | ErrorCode::ExpectedSomeValue
67            | ErrorCode::InvalidEscape
68            | ErrorCode::InvalidNumber
69            | ErrorCode::NumberOutOfRange
70            | ErrorCode::InvalidUnicodeCodePoint
71            | ErrorCode::ControlCharacterWhileParsingString
72            | ErrorCode::KeyMustBeAString
73            | ErrorCode::LoneLeadingSurrogateInHexEscape
74            | ErrorCode::TrailingComma
75            | ErrorCode::TrailingCharacters
76            | ErrorCode::UnexpectedEndOfHexEscape
77            | ErrorCode::RecursionLimitExceeded => Category::Syntax,
78        }
79    }
80
81    /// Returns true if this error was caused by a failure to read or write
82    /// bytes on an I/O stream.
83    pub fn is_io(&self) -> bool {
84        self.classify() == Category::Io
85    }
86
87    /// Returns true if this error was caused by input that was not
88    /// syntactically valid JSON.
89    pub fn is_syntax(&self) -> bool {
90        self.classify() == Category::Syntax
91    }
92
93    /// Returns true if this error was caused by input data that was
94    /// semantically incorrect.
95    ///
96    /// For example, JSON containing a number is semantically incorrect when the
97    /// type being deserialized into holds a String.
98    pub fn is_data(&self) -> bool {
99        self.classify() == Category::Data
100    }
101
102    /// Returns true if this error was caused by prematurely reaching the end of
103    /// the input data.
104    ///
105    /// Callers that process streaming input may be interested in retrying the
106    /// deserialization once more data is available.
107    pub fn is_eof(&self) -> bool {
108        self.classify() == Category::Eof
109    }
110
111    /// The kind reported by the underlying standard library I/O error, if this
112    /// error was caused by a failure to read or write bytes on an I/O stream.
113    ///
114    /// # Example
115    ///
116    /// ```
117    /// use serde_json_pythonic::Value;
118    /// use std::io::{self, ErrorKind, Read};
119    /// use std::process;
120    ///
121    /// struct ReaderThatWillTimeOut<'a>(&'a [u8]);
122    ///
123    /// impl<'a> Read for ReaderThatWillTimeOut<'a> {
124    ///     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
125    ///         if self.0.is_empty() {
126    ///             Err(io::Error::new(ErrorKind::TimedOut, "timed out"))
127    ///         } else {
128    ///             self.0.read(buf)
129    ///         }
130    ///     }
131    /// }
132    ///
133    /// fn main() {
134    ///     let reader = ReaderThatWillTimeOut(br#" {"k": "#);
135    ///
136    ///     let _: Value = match serde_json_pythonic::from_reader(reader) {
137    ///         Ok(value) => value,
138    ///         Err(error) => {
139    ///             if error.io_error_kind() == Some(ErrorKind::TimedOut) {
140    ///                 // Maybe this application needs to retry certain kinds of errors.
141    ///
142    ///                 # return;
143    ///             } else {
144    ///                 eprintln!("error: {}", error);
145    ///                 process::exit(1);
146    ///             }
147    ///         }
148    ///     };
149    /// }
150    /// ```
151    #[cfg(feature = "std")]
152    pub fn io_error_kind(&self) -> Option<ErrorKind> {
153        if let ErrorCode::Io(io_error) = &self.err.code {
154            Some(io_error.kind())
155        } else {
156            None
157        }
158    }
159}
160
161/// Categorizes the cause of a `serde_json_pythonic::Error`.
162#[derive(Copy, Clone, PartialEq, Eq, Debug)]
163pub enum Category {
164    /// The error was caused by a failure to read or write bytes on an I/O
165    /// stream.
166    Io,
167
168    /// The error was caused by input that was not syntactically valid JSON.
169    Syntax,
170
171    /// The error was caused by input data that was semantically incorrect.
172    ///
173    /// For example, JSON containing a number is semantically incorrect when the
174    /// type being deserialized into holds a String.
175    Data,
176
177    /// The error was caused by prematurely reaching the end of the input data.
178    ///
179    /// Callers that process streaming input may be interested in retrying the
180    /// deserialization once more data is available.
181    Eof,
182}
183
184#[cfg(feature = "std")]
185#[allow(clippy::fallible_impl_from)]
186impl From<Error> for io::Error {
187    /// Convert a `serde_json_pythonic::Error` into an `io::Error`.
188    ///
189    /// JSON syntax and data errors are turned into `InvalidData` I/O errors.
190    /// EOF errors are turned into `UnexpectedEof` I/O errors.
191    ///
192    /// ```
193    /// use std::io;
194    ///
195    /// enum MyError {
196    ///     Io(io::Error),
197    ///     Json(serde_json_pythonic::Error),
198    /// }
199    ///
200    /// impl From<serde_json_pythonic::Error> for MyError {
201    ///     fn from(err: serde_json_pythonic::Error) -> MyError {
202    ///         use serde_json_pythonic::error::Category;
203    ///         match err.classify() {
204    ///             Category::Io => {
205    ///                 MyError::Io(err.into())
206    ///             }
207    ///             Category::Syntax | Category::Data | Category::Eof => {
208    ///                 MyError::Json(err)
209    ///             }
210    ///         }
211    ///     }
212    /// }
213    /// ```
214    fn from(j: Error) -> Self {
215        if let ErrorCode::Io(err) = j.err.code {
216            err
217        } else {
218            match j.classify() {
219                Category::Io => unreachable!(),
220                Category::Syntax | Category::Data => io::Error::new(ErrorKind::InvalidData, j),
221                Category::Eof => io::Error::new(ErrorKind::UnexpectedEof, j),
222            }
223        }
224    }
225}
226
227struct ErrorImpl {
228    code: ErrorCode,
229    line: usize,
230    column: usize,
231}
232
233pub(crate) enum ErrorCode {
234    /// Catchall for syntax error messages
235    Message(Box<str>),
236
237    /// Some I/O error occurred while serializing or deserializing.
238    Io(io::Error),
239
240    /// EOF while parsing a list.
241    EofWhileParsingList,
242
243    /// EOF while parsing an object.
244    EofWhileParsingObject,
245
246    /// EOF while parsing a string.
247    EofWhileParsingString,
248
249    /// EOF while parsing a JSON value.
250    EofWhileParsingValue,
251
252    /// Expected this character to be a `':'`.
253    ExpectedColon,
254
255    /// Expected this character to be either a `','` or a `']'`.
256    ExpectedListCommaOrEnd,
257
258    /// Expected this character to be either a `','` or a `'}'`.
259    ExpectedObjectCommaOrEnd,
260
261    /// Expected to parse either a `true`, `false`, or a `null`.
262    ExpectedSomeIdent,
263
264    /// Expected this character to start a JSON value.
265    ExpectedSomeValue,
266
267    /// Invalid hex escape code.
268    InvalidEscape,
269
270    /// Invalid number.
271    InvalidNumber,
272
273    /// Number is bigger than the maximum value of its type.
274    NumberOutOfRange,
275
276    /// Invalid unicode code point.
277    InvalidUnicodeCodePoint,
278
279    /// Control character found while parsing a string.
280    ControlCharacterWhileParsingString,
281
282    /// Object key is not a string.
283    KeyMustBeAString,
284
285    /// Lone leading surrogate in hex escape.
286    LoneLeadingSurrogateInHexEscape,
287
288    /// JSON has a comma after the last value in an array or map.
289    TrailingComma,
290
291    /// JSON has non-whitespace trailing characters after the value.
292    TrailingCharacters,
293
294    /// Unexpected end of hex escape.
295    UnexpectedEndOfHexEscape,
296
297    /// Encountered nesting of JSON maps and arrays more than 128 layers deep.
298    RecursionLimitExceeded,
299}
300
301impl Error {
302    #[cold]
303    pub(crate) fn syntax(code: ErrorCode, line: usize, column: usize) -> Self {
304        Error {
305            err: Box::new(ErrorImpl { code, line, column }),
306        }
307    }
308
309    // Not public API. Should be pub(crate).
310    //
311    // Update `eager_json` crate when this function changes.
312    #[doc(hidden)]
313    #[cold]
314    pub fn io(error: io::Error) -> Self {
315        Error {
316            err: Box::new(ErrorImpl {
317                code: ErrorCode::Io(error),
318                line: 0,
319                column: 0,
320            }),
321        }
322    }
323
324    #[cold]
325    pub(crate) fn fix_position<F>(self, f: F) -> Self
326    where
327        F: FnOnce(ErrorCode) -> Error,
328    {
329        if self.err.line == 0 {
330            f(self.err.code)
331        } else {
332            self
333        }
334    }
335}
336
337impl Display for ErrorCode {
338    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
339        match self {
340            ErrorCode::Message(msg) => f.write_str(msg),
341            ErrorCode::Io(err) => Display::fmt(err, f),
342            ErrorCode::EofWhileParsingList => f.write_str("EOF while parsing a list"),
343            ErrorCode::EofWhileParsingObject => f.write_str("EOF while parsing an object"),
344            ErrorCode::EofWhileParsingString => f.write_str("EOF while parsing a string"),
345            ErrorCode::EofWhileParsingValue => f.write_str("EOF while parsing a value"),
346            ErrorCode::ExpectedColon => f.write_str("expected `:`"),
347            ErrorCode::ExpectedListCommaOrEnd => f.write_str("expected `,` or `]`"),
348            ErrorCode::ExpectedObjectCommaOrEnd => f.write_str("expected `,` or `}`"),
349            ErrorCode::ExpectedSomeIdent => f.write_str("expected ident"),
350            ErrorCode::ExpectedSomeValue => f.write_str("expected value"),
351            ErrorCode::InvalidEscape => f.write_str("invalid escape"),
352            ErrorCode::InvalidNumber => f.write_str("invalid number"),
353            ErrorCode::NumberOutOfRange => f.write_str("number out of range"),
354            ErrorCode::InvalidUnicodeCodePoint => f.write_str("invalid unicode code point"),
355            ErrorCode::ControlCharacterWhileParsingString => {
356                f.write_str("control character (\\u0000-\\u001F) found while parsing a string")
357            }
358            ErrorCode::KeyMustBeAString => f.write_str("key must be a string"),
359            ErrorCode::LoneLeadingSurrogateInHexEscape => {
360                f.write_str("lone leading surrogate in hex escape")
361            }
362            ErrorCode::TrailingComma => f.write_str("trailing comma"),
363            ErrorCode::TrailingCharacters => f.write_str("trailing characters"),
364            ErrorCode::UnexpectedEndOfHexEscape => f.write_str("unexpected end of hex escape"),
365            ErrorCode::RecursionLimitExceeded => f.write_str("recursion limit exceeded"),
366        }
367    }
368}
369
370impl serde::de::StdError for Error {
371    #[cfg(feature = "std")]
372    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
373        match &self.err.code {
374            ErrorCode::Io(err) => err.source(),
375            _ => None,
376        }
377    }
378}
379
380impl Display for Error {
381    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
382        Display::fmt(&*self.err, f)
383    }
384}
385
386impl Display for ErrorImpl {
387    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
388        if self.line == 0 {
389            Display::fmt(&self.code, f)
390        } else {
391            write!(
392                f,
393                "{} at line {} column {}",
394                self.code, self.line, self.column
395            )
396        }
397    }
398}
399
400// Remove two layers of verbosity from the debug representation. Humans often
401// end up seeing this representation because it is what unwrap() shows.
402impl Debug for Error {
403    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
404        write!(
405            f,
406            "Error({:?}, line: {}, column: {})",
407            self.err.code.to_string(),
408            self.err.line,
409            self.err.column
410        )
411    }
412}
413
414impl de::Error for Error {
415    #[cold]
416    fn custom<T: Display>(msg: T) -> Error {
417        make_error(msg.to_string())
418    }
419
420    #[cold]
421    fn invalid_type(unexp: de::Unexpected, exp: &dyn de::Expected) -> Self {
422        if let de::Unexpected::Unit = unexp {
423            Error::custom(format_args!("invalid type: null, expected {}", exp))
424        } else {
425            Error::custom(format_args!("invalid type: {}, expected {}", unexp, exp))
426        }
427    }
428}
429
430impl ser::Error for Error {
431    #[cold]
432    fn custom<T: Display>(msg: T) -> Error {
433        make_error(msg.to_string())
434    }
435}
436
437// Parse our own error message that looks like "{} at line {} column {}" to work
438// around erased-serde round-tripping the error through de::Error::custom.
439fn make_error(mut msg: String) -> Error {
440    let (line, column) = parse_line_col(&mut msg).unwrap_or((0, 0));
441    Error {
442        err: Box::new(ErrorImpl {
443            code: ErrorCode::Message(msg.into_boxed_str()),
444            line,
445            column,
446        }),
447    }
448}
449
450fn parse_line_col(msg: &mut String) -> Option<(usize, usize)> {
451    let start_of_suffix = match msg.rfind(" at line ") {
452        Some(index) => index,
453        None => return None,
454    };
455
456    // Find start and end of line number.
457    let start_of_line = start_of_suffix + " at line ".len();
458    let mut end_of_line = start_of_line;
459    while starts_with_digit(&msg[end_of_line..]) {
460        end_of_line += 1;
461    }
462
463    if !msg[end_of_line..].starts_with(" column ") {
464        return None;
465    }
466
467    // Find start and end of column number.
468    let start_of_column = end_of_line + " column ".len();
469    let mut end_of_column = start_of_column;
470    while starts_with_digit(&msg[end_of_column..]) {
471        end_of_column += 1;
472    }
473
474    if end_of_column < msg.len() {
475        return None;
476    }
477
478    // Parse numbers.
479    let line = match usize::from_str(&msg[start_of_line..end_of_line]) {
480        Ok(line) => line,
481        Err(_) => return None,
482    };
483    let column = match usize::from_str(&msg[start_of_column..end_of_column]) {
484        Ok(column) => column,
485        Err(_) => return None,
486    };
487
488    msg.truncate(start_of_suffix);
489    Some((line, column))
490}
491
492fn starts_with_digit(slice: &str) -> bool {
493    match slice.as_bytes().first() {
494        None => false,
495        Some(&byte) => byte >= b'0' && byte <= b'9',
496    }
497}