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

Issue:
StreamDeserializer errors on cut-off decimal numbers
Simple example:
```rust
fn main() {
    dbg!(serde_json::Deserializer::from_slice(b"[1.").into_iter::<Vec<f64>>().next());
}
```
I'd expect that to return None or an EOF error - the array is not finished yet and the number is valid if it's followed by more digits. But instead it returns an early parse error:
```rust
serde_json::Deserializer::from_slice(b"[1.").into_iter::<Vec<f64>>().next() = Some(
    Err(
        Error("invalid number", line: 1, column: 3)
    )
)
```

(What I'm really doing is using a StreamDeserializer with a little bit of custom parsing to parse a large array of values, so my problem would also be solved with #404 )


Discussion / hints:
Thanks, I would accept a PR to return an EOF error in this case.

Fix patch:
diff --git a/src/de.rs b/src/de.rs
index e3effc365..f8d4ace53 100644
--- a/src/de.rs
+++ b/src/de.rs
@@ -380,7 +380,14 @@ impl<'de, R: Read<'de>> Deserializer<R> {
     }
 
     fn parse_integer(&mut self, positive: bool) -> Result<ParserNumber> {
-        match try!(self.next_char_or_null()) {
+        let next = match try!(self.next_char()) {
+            Some(b) => b,
+            None => {
+                return Err(self.error(ErrorCode::EofWhileParsingValue));
+            }
+        };
+
+        match next {
             b'0' => {
                 // There can be only one leading '0'.
                 match try!(self.peek_or_null()) {
@@ -496,7 +503,10 @@ impl<'de, R: Read<'de>> Deserializer<R> {
         }
 
         if !at_least_one_digit {
-            return Err(self.peek_error(ErrorCode::InvalidNumber));
+            match try!(self.peek()) {
+                Some(_) => return Err(self.peek_error(ErrorCode::InvalidNumber)),
+                None => return Err(self.peek_error(ErrorCode::EofWhileParsingValue)),
+            }
         }
 
         match try!(self.peek_or_null()) {
@@ -525,8 +535,15 @@ impl<'de, R: Read<'de>> Deserializer<R> {
             _ => true,
         };
 
+        let next = match try!(self.next_char()) {
+            Some(b) => b,
+            None => {
+                return Err(self.error(ErrorCode::EofWhileParsingValue));
+            }
+        };
+
         // Make sure a digit follows the exponent place.
-        let mut exp = match try!(self.next_char_or_null()) {
+        let mut exp = match next {
             c @ b'0'...b'9' => (c - b'0') as i32,
             _ => {
                 return Err(self.error(ErrorCode::InvalidNumber));
@@ -623,19 +640,19 @@ impl<'de, R: Read<'de>> Deserializer<R> {
     }
 
     #[cfg(feature = "arbitrary_precision")]
-    fn scan_or_null(&mut self, buf: &mut String) -> Result<u8> {
+    fn scan_or_eof(&mut self, buf: &mut String) -> Result<u8> {
         match try!(self.next_char()) {
             Some(b) => {
                 buf.push(b as char);
                 Ok(b)
             }
-            None => Ok(b'\x00'),
+            None => Err(self.error(ErrorCode::EofWhileParsingValue))
         }
     }
 
     #[cfg(feature = "arbitrary_precision")]
     fn scan_integer(&mut self, buf: &mut String) -> Result<()> {
-        match try!(self.scan_or_null(buf)) {
+        match try!(self.scan_or_eof(buf)) {
             b'0' => {
                 // There can be only one leading '0'.
                 match try!(self.peek_or_null()) {
@@ -680,7 +697,10 @@ impl<'de, R: Read<'de>> Deserializer<R> {
         }
 
         if !at_least_one_digit {
-            return Err(self.peek_error(ErrorCode::InvalidNumber));
+            match try!(self.peek()) {
+                Some(_) => return Err(self.peek_error(ErrorCode::InvalidNumber)),
+                None => return Err(self.peek_error(ErrorCode::EofWhileParsingValue)),
+            }
         }
 
         match try!(self.peek_or_null()) {
@@ -706,7 +726,7 @@ impl<'de, R: Read<'de>> Deserializer<R> {
         }
 
         // Make sure a digit follows the exponent place.
-        match try!(self.scan_or_null(buf)) {
+        match try!(self.scan_or_eof(buf)) {
             b'0'...b'9' => {}
             _ => {
                 return Err(self.error(ErrorCode::InvalidNumber));


Test patch:
diff --git a/tests/stream.rs b/tests/stream.rs
index 7127b2534..88a395294 100644
--- a/tests/stream.rs
+++ b/tests/stream.rs
@@ -81,6 +81,36 @@ fn test_json_stream_truncated() {
     });
 }
 
+#[test]
+fn test_json_stream_truncated_decimal() {
+    let data = "{\"x\":4.";
+
+    test_stream!(data, Value, |stream| {
+        assert!(stream.next().unwrap().unwrap_err().is_eof());
+        assert_eq!(stream.byte_offset(), 0);
+    });
+}
+
+#[test]
+fn test_json_stream_truncated_negative() {
+    let data = "{\"x\":-";
+
+    test_stream!(data, Value, |stream| {
+        assert!(stream.next().unwrap().unwrap_err().is_eof());
+        assert_eq!(stream.byte_offset(), 0);
+    });
+}
+
+#[test]
+fn test_json_stream_truncated_exponent() {
+    let data = "{\"x\":4e";
+
+    test_stream!(data, Value, |stream| {
+        assert!(stream.next().unwrap().unwrap_err().is_eof());
+        assert_eq!(stream.byte_offset(), 0);
+    });
+}
+
 #[test]
 fn test_json_stream_empty() {
     let data = "";
diff --git a/tests/test.rs b/tests/test.rs
index 6948ee280..afa6a01fe 100644
--- a/tests/test.rs
+++ b/tests/test.rs
@@ -737,15 +737,15 @@ fn test_parse_number_errors() {
     test_parse_err::<f64>(&[
         ("+", "expected value at line 1 column 1"),
         (".", "expected value at line 1 column 1"),
-        ("-", "invalid number at line 1 column 1"),
+        ("-", "EOF while parsing a value at line 1 column 1"),
         ("00", "invalid number at line 1 column 2"),
         ("0x80", "trailing characters at line 1 column 2"),
         ("\\0", "expected value at line 1 column 1"),
-        ("1.", "invalid number at line 1 column 2"),
+        ("1.", "EOF while parsing a value at line 1 column 2"),
         ("1.a", "invalid number at line 1 column 3"),
         ("1.e1", "invalid number at line 1 column 3"),
-        ("1e", "invalid number at line 1 column 2"),
-        ("1e+", "invalid number at line 1 column 3"),
+        ("1e", "EOF while parsing a value at line 1 column 2"),
+        ("1e+", "EOF while parsing a value at line 1 column 3"),
         ("1a", "trailing characters at line 1 column 2"),
         (
             "100e777777777777777777777777777",