Testing serialize/deserialize functions for serde "with" attribute

Viewed 801

Serde derive macros come with the ability to control how a field is serialized/deserialized through the #[serde(with = "module")] field attribute. The "module" should have serialize and deserialize functions with the right arguments and return types.

An example that unfortunately got a bit too contrived:

use serde::{Deserialize, Serialize};

#[derive(Debug, Default, PartialEq, Eq)]
pub struct StringPair(String, String);

mod stringpair_serde {
    pub fn serialize<S>(sp: &super::StringPair, ser: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        ser.serialize_str(format!("{}:{}", sp.0, sp.1).as_str())
    }

    pub fn deserialize<'de, D>(d: D) -> Result<super::StringPair, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        d.deserialize_str(Visitor)
    }

    struct Visitor;

    impl<'de> serde::de::Visitor<'de> for Visitor {
        type Value = super::StringPair;

        fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
            write!(f, "a pair of strings separated by colon (:)")
        }

        fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
        where
            E: serde::de::Error,
        {
            Ok(s.split_once(":")
                .map(|tup| super::StringPair(tup.0.to_string(), tup.1.to_string()))
                .unwrap_or(Default::default()))
        }
    }
}

#[derive(Serialize, Deserialize)]
struct UsesStringPair {
    // Other fields ...

    #[serde(with = "stringpair_serde")]
    pub stringpair: StringPair,
}

fn main() {
    let usp = UsesStringPair {
        stringpair: StringPair("foo".to_string(), "bar".to_string()),
    };

    assert_eq!(
        serde_json::json!(&usp).to_string(),
        r#"{"stringpair":"foo:bar"}"#
    );

    let usp: UsesStringPair = serde_json::from_str(r#"{"stringpair":"baz:qux"}"#).unwrap();

    assert_eq!(
        usp.stringpair,
        StringPair("baz".to_string(), "qux".to_string())
    )
}

Testing derived serialization for UsesStringPair is trivial with simple assertions. But I have looked at serde_test example as that makes sense to me too.

However, I want to be able to independently test the stringpair_serde::{serialize, deserialize} functions (e.g. if my crate provides just mycrate::StringPair and mycrate::stringpair_serde, and UsesStringPair is for the crate users to implement).

One way I've looked into is creating a serde_json::Serializer (using new, requires a io::Write implementation, which I couldn't figure out how to create and use trivially, but that's a separate question) and calling serialize with the created Serializer, then making assertions on the result as before. However, that does not test any/all implementations of serde::Serializer, just the one provided in serde_json.

I'm wondering if there's a method like in the serde_test example that works for ser/deser functions provided by a module.

0 Answers
Related