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::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::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::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::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::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::Error),
198 /// }
199 ///
200 /// impl From<serde_json::Error> for MyError {
201 /// fn from(err: serde_json::Error) -> MyError {
202 /// use serde_json::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: &Error) => 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: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(msg:format_args!("invalid type: null, expected {}", exp))
424 } else {
425 Error::custom(msg: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: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: usize, column: usize) = 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: u8) => byte >= b'0' && byte <= b'9',
496 }
497}
498