1 | //! When serializing or deserializing JSON goes wrong. |
2 | |
3 | use crate::io; |
4 | use alloc::boxed::Box; |
5 | use alloc::string::{String, ToString}; |
6 | use core::fmt::{self, Debug, Display}; |
7 | use core::result; |
8 | use core::str::FromStr; |
9 | use serde::{de, ser}; |
10 | #[cfg (feature = "std" )] |
11 | use std::error; |
12 | #[cfg (feature = "std" )] |
13 | use std::io::ErrorKind; |
14 | |
15 | /// This type represents all possible errors that can occur when serializing or |
16 | /// deserializing JSON data. |
17 | pub 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`. |
25 | pub type Result<T> = result::Result<T, Error>; |
26 | |
27 | impl 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)] |
163 | pub 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)] |
186 | impl 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 | |
227 | struct ErrorImpl { |
228 | code: ErrorCode, |
229 | line: usize, |
230 | column: usize, |
231 | } |
232 | |
233 | pub(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 | |
301 | impl 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 | |
337 | impl 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 | |
370 | impl 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 | |
380 | impl Display for Error { |
381 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
382 | Display::fmt(&*self.err, f) |
383 | } |
384 | } |
385 | |
386 | impl 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. |
402 | impl 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 | |
414 | impl 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 | |
430 | impl 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. |
439 | fn 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 | |
450 | fn 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 | |
492 | fn 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 | |