]>
Commit | Line | Data |
---|---|---|
041b39d2 XL |
1 | // Copyright 2017 Serde Developers |
2 | // | |
3 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | |
4 | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | |
5 | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | |
6 | // option. This file may not be copied, modified, or distributed | |
7 | // except according to those terms. | |
8 | ||
7cac9316 XL |
9 | //! When serializing or deserializing JSON goes wrong. |
10 | ||
11 | use std::error; | |
12 | use std::fmt::{self, Debug, Display}; | |
13 | use std::io; | |
14 | use std::result; | |
15 | ||
16 | use serde::de; | |
17 | use serde::ser; | |
18 | ||
19 | /// This type represents all possible errors that can occur when serializing or | |
20 | /// deserializing JSON data. | |
21 | pub struct Error { | |
22 | /// This `Box` allows us to keep the size of `Error` as small as possible. A | |
23 | /// larger `Error` type was substantially slower due to all the functions | |
24 | /// that pass around `Result<T, Error>`. | |
25 | err: Box<ErrorImpl>, | |
26 | } | |
27 | ||
28 | /// Alias for a `Result` with the error type `serde_json::Error`. | |
29 | pub type Result<T> = result::Result<T, Error>; | |
30 | ||
31 | impl Error { | |
32 | /// One-based line number at which the error was detected. | |
33 | /// | |
34 | /// Characters in the first line of the input (before the first newline | |
35 | /// character) are in line 1. | |
36 | pub fn line(&self) -> usize { | |
37 | self.err.line | |
38 | } | |
39 | ||
40 | /// One-based column number at which the error was detected. | |
41 | /// | |
42 | /// The first character in the input and any characters immediately | |
43 | /// following a newline character are in column 1. | |
44 | /// | |
45 | /// Note that errors may occur in column 0, for example if a read from an IO | |
46 | /// stream fails immediately following a previously read newline character. | |
47 | pub fn column(&self) -> usize { | |
48 | self.err.column | |
49 | } | |
50 | ||
51 | /// Categorizes the cause of this error. | |
52 | /// | |
53 | /// - `Category::Io` - failure to read or write bytes on an IO stream | |
54 | /// - `Category::Syntax` - input that is not syntactically valid JSON | |
55 | /// - `Category::Data` - input data that is semantically incorrect | |
56 | /// - `Category::Eof` - unexpected end of the input data | |
57 | pub fn classify(&self) -> Category { | |
58 | match self.err.code { | |
59 | ErrorCode::Message(_) => Category::Data, | |
60 | ErrorCode::Io(_) => Category::Io, | |
61 | ErrorCode::EofWhileParsingList | | |
62 | ErrorCode::EofWhileParsingObject | | |
63 | ErrorCode::EofWhileParsingString | | |
64 | ErrorCode::EofWhileParsingValue => Category::Eof, | |
65 | ErrorCode::ExpectedColon | | |
66 | ErrorCode::ExpectedListCommaOrEnd | | |
67 | ErrorCode::ExpectedObjectCommaOrEnd | | |
041b39d2 | 68 | ErrorCode::ExpectedObjectOrArray | |
7cac9316 XL |
69 | ErrorCode::ExpectedSomeIdent | |
70 | ErrorCode::ExpectedSomeValue | | |
71 | ErrorCode::ExpectedSomeString | | |
72 | ErrorCode::InvalidEscape | | |
73 | ErrorCode::InvalidNumber | | |
74 | ErrorCode::NumberOutOfRange | | |
75 | ErrorCode::InvalidUnicodeCodePoint | | |
76 | ErrorCode::KeyMustBeAString | | |
77 | ErrorCode::LoneLeadingSurrogateInHexEscape | | |
abe05a73 | 78 | ErrorCode::TrailingComma | |
7cac9316 XL |
79 | ErrorCode::TrailingCharacters | |
80 | ErrorCode::UnexpectedEndOfHexEscape | | |
81 | ErrorCode::RecursionLimitExceeded => Category::Syntax, | |
82 | } | |
83 | } | |
84 | ||
85 | /// Returns true if this error was caused by a failure to read or write | |
86 | /// bytes on an IO stream. | |
87 | pub fn is_io(&self) -> bool { | |
88 | self.classify() == Category::Io | |
89 | } | |
90 | ||
91 | /// Returns true if this error was caused by input that was not | |
92 | /// syntactically valid JSON. | |
93 | pub fn is_syntax(&self) -> bool { | |
94 | self.classify() == Category::Syntax | |
95 | } | |
96 | ||
97 | /// Returns true if this error was caused by input data that was | |
98 | /// semantically incorrect. | |
99 | /// | |
100 | /// For example, JSON containing a number is semantically incorrect when the | |
101 | /// type being deserialized into holds a String. | |
102 | pub fn is_data(&self) -> bool { | |
103 | self.classify() == Category::Data | |
104 | } | |
105 | ||
106 | /// Returns true if this error was caused by prematurely reaching the end of | |
107 | /// the input data. | |
108 | /// | |
109 | /// Callers that process streaming input may be interested in retrying the | |
110 | /// deserialization once more data is available. | |
111 | pub fn is_eof(&self) -> bool { | |
112 | self.classify() == Category::Eof | |
113 | } | |
114 | } | |
115 | ||
116 | /// Categorizes the cause of a `serde_json::Error`. | |
117 | #[derive(Copy, Clone, PartialEq, Eq, Debug)] | |
118 | pub enum Category { | |
119 | /// The error was caused by a failure to read or write bytes on an IO | |
120 | /// stream. | |
121 | Io, | |
122 | ||
123 | /// The error was caused by input that was not syntactically valid JSON. | |
124 | Syntax, | |
125 | ||
126 | /// The error was caused by input data that was semantically incorrect. | |
127 | /// | |
128 | /// For example, JSON containing a number is semantically incorrect when the | |
129 | /// type being deserialized into holds a String. | |
130 | Data, | |
131 | ||
132 | /// The error was caused by prematurely reaching the end of the input data. | |
133 | /// | |
134 | /// Callers that process streaming input may be interested in retrying the | |
135 | /// deserialization once more data is available. | |
136 | Eof, | |
137 | } | |
138 | ||
abe05a73 | 139 | #[cfg_attr(feature = "cargo-clippy", allow(fallible_impl_from))] |
041b39d2 XL |
140 | impl From<Error> for io::Error { |
141 | /// Convert a `serde_json::Error` into an `io::Error`. | |
142 | /// | |
143 | /// JSON syntax and data errors are turned into `InvalidData` IO errors. | |
144 | /// EOF errors are turned into `UnexpectedEof` IO errors. | |
145 | /// | |
146 | /// ```rust | |
147 | /// use std::io; | |
148 | /// | |
149 | /// enum MyError { | |
150 | /// Io(io::Error), | |
151 | /// Json(serde_json::Error), | |
152 | /// } | |
153 | /// | |
154 | /// impl From<serde_json::Error> for MyError { | |
155 | /// fn from(err: serde_json::Error) -> MyError { | |
156 | /// use serde_json::error::Category; | |
157 | /// match err.classify() { | |
158 | /// Category::Io => { | |
159 | /// MyError::Io(err.into()) | |
160 | /// } | |
161 | /// Category::Syntax | Category::Data | Category::Eof => { | |
162 | /// MyError::Json(err) | |
163 | /// } | |
164 | /// } | |
165 | /// } | |
166 | /// } | |
167 | /// ``` | |
168 | fn from(j: Error) -> Self { | |
169 | if let ErrorCode::Io(err) = j.err.code { | |
170 | err | |
171 | } else { | |
172 | match j.classify() { | |
173 | Category::Io => unreachable!(), | |
174 | Category::Syntax | Category::Data => io::Error::new(io::ErrorKind::InvalidData, j), | |
175 | Category::Eof => io::Error::new(io::ErrorKind::UnexpectedEof, j), | |
176 | } | |
177 | } | |
178 | } | |
179 | } | |
180 | ||
7cac9316 XL |
181 | #[derive(Debug)] |
182 | struct ErrorImpl { | |
183 | code: ErrorCode, | |
184 | line: usize, | |
185 | column: usize, | |
186 | } | |
187 | ||
188 | // Not public API. Should be pub(crate). | |
189 | #[doc(hidden)] | |
190 | #[derive(Debug)] | |
191 | pub enum ErrorCode { | |
192 | /// Catchall for syntax error messages | |
193 | Message(String), | |
194 | ||
195 | /// Some IO error occurred while serializing or deserializing. | |
196 | Io(io::Error), | |
197 | ||
198 | /// EOF while parsing a list. | |
199 | EofWhileParsingList, | |
200 | ||
201 | /// EOF while parsing an object. | |
202 | EofWhileParsingObject, | |
203 | ||
204 | /// EOF while parsing a string. | |
205 | EofWhileParsingString, | |
206 | ||
207 | /// EOF while parsing a JSON value. | |
208 | EofWhileParsingValue, | |
209 | ||
210 | /// Expected this character to be a `':'`. | |
211 | ExpectedColon, | |
212 | ||
041b39d2 | 213 | /// Expected this character to be either a `','` or a `']'`. |
7cac9316 XL |
214 | ExpectedListCommaOrEnd, |
215 | ||
041b39d2 | 216 | /// Expected this character to be either a `','` or a `'}'`. |
7cac9316 XL |
217 | ExpectedObjectCommaOrEnd, |
218 | ||
041b39d2 XL |
219 | /// Expected this character to be either a `'{'` or a `'['`. |
220 | ExpectedObjectOrArray, | |
221 | ||
7cac9316 XL |
222 | /// Expected to parse either a `true`, `false`, or a `null`. |
223 | ExpectedSomeIdent, | |
224 | ||
225 | /// Expected this character to start a JSON value. | |
226 | ExpectedSomeValue, | |
227 | ||
228 | /// Expected this character to start a JSON string. | |
229 | ExpectedSomeString, | |
230 | ||
231 | /// Invalid hex escape code. | |
232 | InvalidEscape, | |
233 | ||
234 | /// Invalid number. | |
235 | InvalidNumber, | |
236 | ||
237 | /// Number is bigger than the maximum value of its type. | |
238 | NumberOutOfRange, | |
239 | ||
240 | /// Invalid unicode code point. | |
241 | InvalidUnicodeCodePoint, | |
242 | ||
243 | /// Object key is not a string. | |
244 | KeyMustBeAString, | |
245 | ||
246 | /// Lone leading surrogate in hex escape. | |
247 | LoneLeadingSurrogateInHexEscape, | |
248 | ||
abe05a73 XL |
249 | /// JSON has a comma after the last value in an array or map. |
250 | TrailingComma, | |
251 | ||
7cac9316 XL |
252 | /// JSON has non-whitespace trailing characters after the value. |
253 | TrailingCharacters, | |
254 | ||
255 | /// Unexpected end of hex excape. | |
256 | UnexpectedEndOfHexEscape, | |
257 | ||
258 | /// Encountered nesting of JSON maps and arrays more than 128 layers deep. | |
259 | RecursionLimitExceeded, | |
260 | } | |
261 | ||
262 | impl Error { | |
263 | // Not public API. Should be pub(crate). | |
264 | #[doc(hidden)] | |
265 | pub fn syntax(code: ErrorCode, line: usize, column: usize) -> Self { | |
266 | Error { | |
041b39d2 XL |
267 | err: Box::new( |
268 | ErrorImpl { | |
269 | code: code, | |
270 | line: line, | |
271 | column: column, | |
272 | }, | |
273 | ), | |
274 | } | |
275 | } | |
276 | ||
277 | // Not public API. Should be pub(crate). | |
278 | #[doc(hidden)] | |
279 | pub fn io(error: io::Error) -> Self { | |
280 | Error { | |
281 | err: Box::new( | |
282 | ErrorImpl { | |
283 | code: ErrorCode::Io(error), | |
284 | line: 0, | |
285 | column: 0, | |
286 | }, | |
287 | ), | |
7cac9316 XL |
288 | } |
289 | } | |
290 | ||
291 | // Not public API. Should be pub(crate). | |
292 | #[doc(hidden)] | |
293 | pub fn fix_position<F>(self, f: F) -> Self | |
041b39d2 XL |
294 | where |
295 | F: FnOnce(ErrorCode) -> Error, | |
7cac9316 XL |
296 | { |
297 | if self.err.line == 0 { | |
298 | f(self.err.code) | |
299 | } else { | |
300 | self | |
301 | } | |
302 | } | |
303 | } | |
304 | ||
305 | impl Display for ErrorCode { | |
306 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
307 | match *self { | |
308 | ErrorCode::Message(ref msg) => f.write_str(msg), | |
309 | ErrorCode::Io(ref err) => Display::fmt(err, f), | |
041b39d2 XL |
310 | ErrorCode::EofWhileParsingList => f.write_str("EOF while parsing a list"), |
311 | ErrorCode::EofWhileParsingObject => f.write_str("EOF while parsing an object"), | |
312 | ErrorCode::EofWhileParsingString => f.write_str("EOF while parsing a string"), | |
313 | ErrorCode::EofWhileParsingValue => f.write_str("EOF while parsing a value"), | |
314 | ErrorCode::ExpectedColon => f.write_str("expected `:`"), | |
315 | ErrorCode::ExpectedListCommaOrEnd => f.write_str("expected `,` or `]`"), | |
316 | ErrorCode::ExpectedObjectCommaOrEnd => f.write_str("expected `,` or `}`"), | |
317 | ErrorCode::ExpectedObjectOrArray => f.write_str("expected `{` or `[`"), | |
318 | ErrorCode::ExpectedSomeIdent => f.write_str("expected ident"), | |
319 | ErrorCode::ExpectedSomeValue => f.write_str("expected value"), | |
320 | ErrorCode::ExpectedSomeString => f.write_str("expected string"), | |
321 | ErrorCode::InvalidEscape => f.write_str("invalid escape"), | |
322 | ErrorCode::InvalidNumber => f.write_str("invalid number"), | |
323 | ErrorCode::NumberOutOfRange => f.write_str("number out of range"), | |
324 | ErrorCode::InvalidUnicodeCodePoint => f.write_str("invalid unicode code point"), | |
325 | ErrorCode::KeyMustBeAString => f.write_str("key must be a string"), | |
7cac9316 XL |
326 | ErrorCode::LoneLeadingSurrogateInHexEscape => { |
327 | f.write_str("lone leading surrogate in hex escape") | |
328 | } | |
abe05a73 | 329 | ErrorCode::TrailingComma => f.write_str("trailing comma"), |
041b39d2 XL |
330 | ErrorCode::TrailingCharacters => f.write_str("trailing characters"), |
331 | ErrorCode::UnexpectedEndOfHexEscape => f.write_str("unexpected end of hex escape"), | |
332 | ErrorCode::RecursionLimitExceeded => f.write_str("recursion limit exceeded"), | |
7cac9316 XL |
333 | } |
334 | } | |
335 | } | |
336 | ||
337 | impl error::Error for Error { | |
338 | fn description(&self) -> &str { | |
339 | match self.err.code { | |
340 | ErrorCode::Io(ref err) => error::Error::description(err), | |
341 | _ => { | |
342 | // If you want a better message, use Display::fmt or to_string(). | |
343 | "JSON error" | |
344 | } | |
345 | } | |
346 | } | |
347 | ||
348 | fn cause(&self) -> Option<&error::Error> { | |
349 | match self.err.code { | |
350 | ErrorCode::Io(ref err) => Some(err), | |
351 | _ => None, | |
352 | } | |
353 | } | |
354 | } | |
355 | ||
356 | impl Display for Error { | |
357 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
358 | Display::fmt(&*self.err, f) | |
359 | } | |
360 | } | |
361 | ||
362 | impl Display for ErrorImpl { | |
363 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
364 | if self.line == 0 { | |
365 | Display::fmt(&self.code, f) | |
366 | } else { | |
041b39d2 XL |
367 | write!( |
368 | f, | |
369 | "{} at line {} column {}", | |
370 | self.code, | |
371 | self.line, | |
372 | self.column | |
373 | ) | |
7cac9316 XL |
374 | } |
375 | } | |
376 | } | |
377 | ||
378 | // Remove two layers of verbosity from the debug representation. Humans often | |
379 | // end up seeing this representation because it is what unwrap() shows. | |
380 | impl Debug for Error { | |
381 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
382 | Debug::fmt(&*self.err, f) | |
383 | } | |
384 | } | |
385 | ||
7cac9316 XL |
386 | impl de::Error for Error { |
387 | fn custom<T: Display>(msg: T) -> Error { | |
388 | Error { | |
041b39d2 XL |
389 | err: Box::new( |
390 | ErrorImpl { | |
391 | code: ErrorCode::Message(msg.to_string()), | |
392 | line: 0, | |
393 | column: 0, | |
394 | }, | |
395 | ), | |
7cac9316 XL |
396 | } |
397 | } | |
ea8adc8c XL |
398 | |
399 | fn invalid_type(unexp: de::Unexpected, exp: &de::Expected) -> Self { | |
400 | if let de::Unexpected::Unit = unexp { | |
401 | Error::custom(format_args!("invalid type: null, expected {}", exp)) | |
402 | } else { | |
403 | Error::custom(format_args!("invalid type: {}, expected {}", unexp, exp)) | |
404 | } | |
405 | } | |
7cac9316 XL |
406 | } |
407 | ||
408 | impl ser::Error for Error { | |
409 | fn custom<T: Display>(msg: T) -> Error { | |
410 | Error { | |
041b39d2 XL |
411 | err: Box::new( |
412 | ErrorImpl { | |
413 | code: ErrorCode::Message(msg.to_string()), | |
414 | line: 0, | |
415 | column: 0, | |
416 | }, | |
417 | ), | |
7cac9316 XL |
418 | } |
419 | } | |
420 | } |