human-units 0.5.3

Units serialization and formatting library designed for configuration files and command line arguments
Documentation
use core::fmt::Formatter;
use core::fmt::Write;

use crate::Buffer;
use crate::Duration;

impl serde::Serialize for Duration {
    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        let mut buf = Buffer::<{ Duration::MAX_STRING_LEN }>::new();
        let _ = write!(&mut buf, "{self}");
        s.serialize_str(unsafe { core::str::from_utf8_unchecked(buf.as_slice()) })
    }
}

impl<'a> serde::Deserialize<'a> for Duration {
    fn deserialize<D>(d: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'a>,
    {
        d.deserialize_str(DurationVisitor)
    }
}

struct DurationVisitor;

impl<'a> serde::de::Visitor<'a> for DurationVisitor {
    type Value = Duration;

    fn expecting(&self, formatter: &mut Formatter) -> core::fmt::Result {
        write!(formatter, "a string obtained by `Duration::to_string`")
    }

    fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
    where
        E: serde::de::Error,
    {
        value.parse().map_err(|_| E::custom("invalid duration"))
    }
}

#[cfg(all(test, feature = "std"))]
mod tests {

    use core::time::Duration as StdDuration;

    use arbtest::arbtest;

    use super::*;

    #[test]
    fn test_serde_io() {
        assert_eq!(
            "\"1m\"",
            serde_json::to_string(&Duration(StdDuration::from_secs(60))).unwrap()
        );
        assert_eq!(
            Duration(StdDuration::from_secs(60)),
            serde_json::from_str("\"1m\"").unwrap()
        );
        assert_eq!(
            Duration(StdDuration::from_secs(60)),
            serde_yaml::from_str("1m").unwrap()
        );
        assert_eq!(
            Duration(StdDuration::from_secs(10)),
            serde_yaml::from_str("10").unwrap()
        );
    }

    #[test]
    fn test_max_string_len() {
        let string = format!("{}", Duration(StdDuration::new(u64::MAX, 999_999_999_u32)));
        assert_eq!(
            Duration::MAX_STRING_LEN,
            string.len(),
            "string = {string:?}"
        );
    }

    #[test]
    fn test_serde_json() {
        arbtest(|u| {
            let expected: Duration = u.arbitrary()?;
            let string = serde_json::to_string(&expected).unwrap();
            let actual = serde_json::from_str(&string).unwrap();
            assert_eq!(expected, actual);
            Ok(())
        });
    }

    #[test]
    fn test_serde_yaml() {
        arbtest(|u| {
            let expected: Duration = u.arbitrary()?;
            let string = serde_yaml::to_string(&expected).unwrap();
            let actual = serde_yaml::from_str(&string).unwrap();
            assert_eq!(expected, actual);
            Ok(())
        });
    }

    #[test]
    fn test_serde_toml() {
        arbtest(|u| {
            let expected: DurationWrapper = u.arbitrary()?;
            let string = toml::to_string(&expected).unwrap();
            let actual = toml::from_str(&string).unwrap();
            assert_eq!(expected, actual);
            Ok(())
        });
    }

    #[derive(
        serde::Serialize, serde::Deserialize, arbitrary::Arbitrary, Debug, PartialEq, Eq, Clone,
    )]
    struct DurationWrapper {
        duration: Duration,
    }
}