Repository: serde-rs/json (language: rust)

Issue:
timeout reporting changed in serde-json 1.0.94 
It seems the reporting of timeouts changed in serde-json 1.0.94 and that this broke the tests of the ureq crate.

    git clone https://github.com/algesten/ureq/
    cd ureq
    git checkout 2.6.2
    cargo test --lib --all-features

    ---- test::timeout::overall_timeout_reading_json stdout ----
    thread 'test::timeout::overall_timeout_reading_json' panicked at 'expected timeout but got something else: "Unexpected io::ErrorKind: Custom { kind: InvalidData, error: \"Failed to read JSON: timed out reading response at line 6 column 1\" }"', src/test/timeout.rs:175:6
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
    
    
    failures:
        test::timeout::overall_timeout_reading_json
    
    test result: FAILED. 134 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 7.41s

With serde-json 1.0.93 the tests pass, with serde-json 1.0.94 and newer they fail. As a quick fix ureq upstream has set an upper limit on the version of serde-json but that isn't a good long term soloution, nor is it an acceptable soloution for distros like Debian.




Discussion / hints:
Serde_json does not implement anything relating to timeout so I am not sure what this is referring to.
I see what changed &mdash; ureq was expecting to do `serde_json_error.source().unwrap().downcast_ref::<std::io::Error>().unwrap().kind()` to get the same ErrorKind that serde_json obtained from the std::io::Read argument to `from_reader`.

In order for that to work, error messages would necessarily end up getting duplicated (either in the caller, or in serde_json). Either

```console
Error: failed to read JSON

Caused by:
  timed out waiting on channel at line 6 column 1

Caused by:
  timed out waiting on channel
```

or:

```console
Error: failed to read JSON

Caused by:
  failed to read JSON bytes from input stream at line 6 column 1

Caused by:
  timed out waiting on channel
```

Possibly we could expose a `pub fn io_error_kind(&self) -> Option<ErrorKind>` on serde_json::Error instead to avoid errors being redundant like this.
> Possibly we could expose a pub fn io_error_kind(&self) -> Option<ErrorKind> on serde_json::Error instead to avoid errors being redundant like this.

That does sound like a reasonable soloution to me.

Fix patch:
diff --git a/src/error.rs b/src/error.rs
index 52169a058..7ba3a4edd 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.
@@ -105,6 +107,55 @@ 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`.
@@ -166,8 +217,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),
             }
         }
     }


Test patch: