aboutsummaryrefslogtreecommitdiff
path: root/src/error.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/error.rs')
-rw-r--r--src/error.rs126
1 files changed, 111 insertions, 15 deletions
diff --git a/src/error.rs b/src/error.rs
index 0898baf..fbf9eb1 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -9,6 +9,8 @@ use core::str::FromStr;
use serde::{de, ser};
#[cfg(feature = "std")]
use std::error;
+#[cfg(feature = "std")]
+use std::io::ErrorKind;
/// This type represents all possible errors that can occur when serializing or
/// deserializing JSON data.
@@ -36,15 +38,16 @@ impl Error {
/// The first character in the input and any characters immediately
/// following a newline character are in column 1.
///
- /// Note that errors may occur in column 0, for example if a read from an IO
- /// stream fails immediately following a previously read newline character.
+ /// Note that errors may occur in column 0, for example if a read from an
+ /// I/O stream fails immediately following a previously read newline
+ /// character.
pub fn column(&self) -> usize {
self.err.column
}
/// Categorizes the cause of this error.
///
- /// - `Category::Io` - failure to read or write bytes on an IO stream
+ /// - `Category::Io` - failure to read or write bytes on an I/O stream
/// - `Category::Syntax` - input that is not syntactically valid JSON
/// - `Category::Data` - input data that is semantically incorrect
/// - `Category::Eof` - unexpected end of the input data
@@ -61,12 +64,15 @@ impl Error {
| ErrorCode::ExpectedObjectCommaOrEnd
| ErrorCode::ExpectedSomeIdent
| ErrorCode::ExpectedSomeValue
+ | ErrorCode::ExpectedDoubleQuote
| ErrorCode::InvalidEscape
| ErrorCode::InvalidNumber
| ErrorCode::NumberOutOfRange
| ErrorCode::InvalidUnicodeCodePoint
| ErrorCode::ControlCharacterWhileParsingString
| ErrorCode::KeyMustBeAString
+ | ErrorCode::ExpectedNumericKey
+ | ErrorCode::FloatKeyMustBeFinite
| ErrorCode::LoneLeadingSurrogateInHexEscape
| ErrorCode::TrailingComma
| ErrorCode::TrailingCharacters
@@ -76,7 +82,7 @@ impl Error {
}
/// Returns true if this error was caused by a failure to read or write
- /// bytes on an IO stream.
+ /// bytes on an I/O stream.
pub fn is_io(&self) -> bool {
self.classify() == Category::Io
}
@@ -104,12 +110,61 @@ impl Error {
pub fn is_eof(&self) -> bool {
self.classify() == Category::Eof
}
+
+ /// The kind reported by the underlying standard library I/O error, if this
+ /// error was caused by a failure to read or write bytes on an I/O stream.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use serde_json::Value;
+ /// use std::io::{self, ErrorKind, Read};
+ /// use std::process;
+ ///
+ /// struct ReaderThatWillTimeOut<'a>(&'a [u8]);
+ ///
+ /// impl<'a> Read for ReaderThatWillTimeOut<'a> {
+ /// fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ /// if self.0.is_empty() {
+ /// Err(io::Error::new(ErrorKind::TimedOut, "timed out"))
+ /// } else {
+ /// self.0.read(buf)
+ /// }
+ /// }
+ /// }
+ ///
+ /// fn main() {
+ /// let reader = ReaderThatWillTimeOut(br#" {"k": "#);
+ ///
+ /// let _: Value = match serde_json::from_reader(reader) {
+ /// Ok(value) => value,
+ /// Err(error) => {
+ /// if error.io_error_kind() == Some(ErrorKind::TimedOut) {
+ /// // Maybe this application needs to retry certain kinds of errors.
+ ///
+ /// # return;
+ /// } else {
+ /// eprintln!("error: {}", error);
+ /// process::exit(1);
+ /// }
+ /// }
+ /// };
+ /// }
+ /// ```
+ #[cfg(feature = "std")]
+ pub fn io_error_kind(&self) -> Option<ErrorKind> {
+ if let ErrorCode::Io(io_error) = &self.err.code {
+ Some(io_error.kind())
+ } else {
+ None
+ }
+ }
}
/// Categorizes the cause of a `serde_json::Error`.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum Category {
- /// The error was caused by a failure to read or write bytes on an IO
+ /// The error was caused by a failure to read or write bytes on an I/O
/// stream.
Io,
@@ -134,8 +189,8 @@ pub enum Category {
impl From<Error> for io::Error {
/// Convert a `serde_json::Error` into an `io::Error`.
///
- /// JSON syntax and data errors are turned into `InvalidData` IO errors.
- /// EOF errors are turned into `UnexpectedEof` IO errors.
+ /// JSON syntax and data errors are turned into `InvalidData` I/O errors.
+ /// EOF errors are turned into `UnexpectedEof` I/O errors.
///
/// ```
/// use std::io;
@@ -165,8 +220,8 @@ impl From<Error> for io::Error {
} else {
match j.classify() {
Category::Io => unreachable!(),
- Category::Syntax | Category::Data => io::Error::new(io::ErrorKind::InvalidData, j),
- Category::Eof => io::Error::new(io::ErrorKind::UnexpectedEof, j),
+ Category::Syntax | Category::Data => io::Error::new(ErrorKind::InvalidData, j),
+ Category::Eof => io::Error::new(ErrorKind::UnexpectedEof, j),
}
}
}
@@ -182,7 +237,7 @@ pub(crate) enum ErrorCode {
/// Catchall for syntax error messages
Message(Box<str>),
- /// Some IO error occurred while serializing or deserializing.
+ /// Some I/O error occurred while serializing or deserializing.
Io(io::Error),
/// EOF while parsing a list.
@@ -212,6 +267,9 @@ pub(crate) enum ErrorCode {
/// Expected this character to start a JSON value.
ExpectedSomeValue,
+ /// Expected this character to be a `"`.
+ ExpectedDoubleQuote,
+
/// Invalid hex escape code.
InvalidEscape,
@@ -230,6 +288,12 @@ pub(crate) enum ErrorCode {
/// Object key is not a string.
KeyMustBeAString,
+ /// Contents of key were supposed to be a number.
+ ExpectedNumericKey,
+
+ /// Object key is a non-finite float value.
+ FloatKeyMustBeFinite,
+
/// Lone leading surrogate in hex escape.
LoneLeadingSurrogateInHexEscape,
@@ -296,6 +360,7 @@ impl Display for ErrorCode {
ErrorCode::ExpectedObjectCommaOrEnd => f.write_str("expected `,` or `}`"),
ErrorCode::ExpectedSomeIdent => f.write_str("expected ident"),
ErrorCode::ExpectedSomeValue => f.write_str("expected value"),
+ ErrorCode::ExpectedDoubleQuote => f.write_str("expected `\"`"),
ErrorCode::InvalidEscape => f.write_str("invalid escape"),
ErrorCode::InvalidNumber => f.write_str("invalid number"),
ErrorCode::NumberOutOfRange => f.write_str("number out of range"),
@@ -304,6 +369,12 @@ impl Display for ErrorCode {
f.write_str("control character (\\u0000-\\u001F) found while parsing a string")
}
ErrorCode::KeyMustBeAString => f.write_str("key must be a string"),
+ ErrorCode::ExpectedNumericKey => {
+ f.write_str("invalid value: expected key to be a number in quotes")
+ }
+ ErrorCode::FloatKeyMustBeFinite => {
+ f.write_str("float key must be finite (got NaN or +/-inf)")
+ }
ErrorCode::LoneLeadingSurrogateInHexEscape => {
f.write_str("lone leading surrogate in hex escape")
}
@@ -367,11 +438,20 @@ impl de::Error for Error {
#[cold]
fn invalid_type(unexp: de::Unexpected, exp: &dyn de::Expected) -> Self {
- if let de::Unexpected::Unit = unexp {
- Error::custom(format_args!("invalid type: null, expected {}", exp))
- } else {
- Error::custom(format_args!("invalid type: {}, expected {}", unexp, exp))
- }
+ Error::custom(format_args!(
+ "invalid type: {}, expected {}",
+ JsonUnexpected(unexp),
+ exp,
+ ))
+ }
+
+ #[cold]
+ fn invalid_value(unexp: de::Unexpected, exp: &dyn de::Expected) -> Self {
+ Error::custom(format_args!(
+ "invalid value: {}, expected {}",
+ JsonUnexpected(unexp),
+ exp,
+ ))
}
}
@@ -382,6 +462,22 @@ impl ser::Error for Error {
}
}
+struct JsonUnexpected<'a>(de::Unexpected<'a>);
+
+impl<'a> Display for JsonUnexpected<'a> {
+ fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ match self.0 {
+ de::Unexpected::Unit => formatter.write_str("null"),
+ de::Unexpected::Float(value) => write!(
+ formatter,
+ "floating point `{}`",
+ ryu::Buffer::new().format(value),
+ ),
+ unexp => Display::fmt(&unexp, formatter),
+ }
+ }
+}
+
// Parse our own error message that looks like "{} at line {} column {}" to work
// around erased-serde round-tripping the error through de::Error::custom.
fn make_error(mut msg: String) -> Error {