serde-rs/test

assert_de_tokens_error panics if deserialization fails in an early field

tspiteri opened this issue · 4 comments

For the code below, in both the Fine and Bad cases, an attempt is made to deserialize the nonzero field with a value of 0, which returns an error. In the Fine case, the nonzero field is the last field and everything works as expected. In the Bad case, the nonzero field is the first field, and assert_de_tokens_error panics with '2 remaining tokens'.

#[macro_use]
extern crate serde_derive;
extern crate serde;
#[cfg(test)]
extern crate serde_test;

use serde::de::{self, Deserialize, Deserializer, Visitor};
use std::fmt::{Formatter, Result as FmtResult};

#[derive(Serialize)]
pub struct Nonzero(i32);

struct NonzeroVisitor;

impl<'de> Visitor<'de> for NonzeroVisitor {
    type Value = Nonzero;

    fn expecting(&self, formatter: &mut Formatter) -> FmtResult {
        formatter.write_str("an integer not equal to 0")
    }

    fn visit_i32<E>(self, value: i32) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        if value != 0 {
            Ok(Nonzero(value))
        } else {
            Err(E::custom(String::from("value is zero")))
        }
    }
}
impl<'de> Deserialize<'de> for Nonzero {
    fn deserialize<D>(deserializer: D) -> Result<Nonzero, D::Error>
    where
        D: Deserializer<'de>,
    {
        deserializer.deserialize_i32(NonzeroVisitor)
    }
}

#[derive(Serialize, Deserialize)]
pub struct Fine {
    other: i32,
    nonzero: Nonzero,
}

#[derive(Serialize, Deserialize)]
pub struct Bad {
    nonzero: Nonzero,
    other: i32,
}

#[cfg(test)]
mod tests {
    use serde_test::{self, Token};

    #[test]
    fn fine() {
        use Fine;
        let tokens_fine = [
            Token::Struct {
                name: "Fine",
                len: 2,
            },
            Token::Str("other"),
            Token::I32(5),
            Token::Str("nonzero"),
            Token::I32(0),
            Token::StructEnd,
        ];
        serde_test::assert_de_tokens_error::<Fine>(&tokens_fine, "value is zero");
    }

    #[test]
    fn bad() {
        use Bad;
        let tokens_bad = [
            Token::Struct {
                name: "Bad",
                len: 2,
            },
            Token::Str("nonzero"),
            Token::I32(0),
            Token::Str("other"),
            Token::I32(5),
            Token::StructEnd,
        ];
        serde_test::assert_de_tokens_error::<Bad>(&tokens_bad, "value is zero");
    }
}

Thanks! I would welcome a pull request to fix this.

I would submit a PR that removes the check that is panicking, but I don't know if I'm missing something.

It seems that commit 093201abfb736b7b32828696d7d190603ebd6aa6 introduced an assertion that there are no tokens after an error, which is causing this issue. I do not know the reason behind that check, so I'm not comfortable simply deleting it in case it serves some other purpose. And I don't know if the similar check in assert_ser_tokens_error might cause a similar issue to this.

Ah, good call. Since the error is detected on the Token::I32(0) token, how about removing the rest of the tokens that are not needed for the test?

let tokens_bad = [
    Token::Struct {
        name: "Bad",
        len: 2,
    },
    Token::Str("nonzero"),
    Token::I32(0),
];
serde_test::assert_de_tokens_error::<Bad>(&tokens_bad, "value is zero");

Yes, that solves my problem. Closing. Thanks!