e173 0.1.0-alpha.7

A library implementing various aspects of the ESTA BSR E1.73 standard
Documentation
use crate::e173_types_no_serde;

e173_types_no_serde! {

#[derive(PartialEq)]
pub struct Version {
    major: u64,
    minor: u64,
    patch: u64,
}

}

mod serde {
    use super::Version;
    use serde::{
        de::{Unexpected, Visitor},
        Deserialize, Deserializer, Serialize,
    };

    struct VersionVisitor;

    impl<'a> VersionVisitor {
        fn validate_version_part<E>(
            &self,
            value: &str,
            parts: &mut impl Iterator<Item = &'a str>,
        ) -> Result<u64, E>
        where
            E: serde::de::Error,
        {
            let part = parts
                .next()
                .ok_or(E::invalid_value(Unexpected::Str(value), self))?;

            // Per E1.73-1 5.2, leading zeros in version number components are not allowed
            if part.starts_with('0') && part.len() > 1 {
                return Err(E::invalid_value(Unexpected::Str(value), self));
            }

            part.parse()
                .map_err(|_| E::invalid_value(Unexpected::Str(value), self))
        }
    }

    impl<'de> Visitor<'de> for VersionVisitor {
        type Value = Version;

        fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
            formatter.write_str("a string formatted as a 3-digit version number, e.g. 1.2.3")
        }

        fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
        where
            E: serde::de::Error,
        {
            let mut parts = v.split('.');

            let major = self.validate_version_part(v, &mut parts)?;
            let minor = self.validate_version_part(v, &mut parts)?;
            let patch = self.validate_version_part(v, &mut parts)?;

            if parts.next().is_none() {
                Ok(Version {
                    major,
                    minor,
                    patch,
                })
            } else {
                Err(E::invalid_value(Unexpected::Str(v), &self))
            }
        }
    }

    impl<'de> Deserialize<'de> for Version {
        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
        where
            D: Deserializer<'de>,
        {
            deserializer.deserialize_str(VersionVisitor)
        }
    }

    impl Serialize for Version {
        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
        where
            S: serde::Serializer,
        {
            let vers = format!("{}.{}.{}", self.major, self.minor, self.patch);
            serializer.serialize_str(&vers)
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;

    macro_rules! test_correct_vers {
        ($str:literal, $maj:literal, $min:literal, $pat:literal) => {
            assert_eq!(
                serde_json::from_str::<Version>($str).unwrap(),
                Version {
                    major: $maj,
                    minor: $min,
                    patch: $pat
                }
            )
        };
    }

    #[test]
    fn test_deserialize_correct_version() {
        test_correct_vers!("\"1.0.0\"", 1, 0, 0);
        test_correct_vers!("\"0.1.0\"", 0, 1, 0);
        test_correct_vers!("\"1.2.3\"", 1, 2, 3);
        test_correct_vers!("\"10000.20000.30000\"", 10000, 20000, 30000);
    }

    #[test]
    fn test_deserialize_incorrect_version() {
        assert!(serde_json::from_str::<Version>("\"01.0.0\"").is_err());
        assert!(serde_json::from_str::<Version>("\"1.01.0\"").is_err());
        assert!(serde_json::from_str::<Version>("\"1.0.01\"").is_err());
        assert!(serde_json::from_str::<Version>("\"1.0\"").is_err());
        assert!(serde_json::from_str::<Version>("\"1.0.0.3\"").is_err());
        assert!(serde_json::from_str::<Version>("\"1.a.b\"").is_err());
        assert!(serde_json::from_str::<Version>("\"1.0.0-beta.3\"").is_err());
        assert!(serde_json::from_str::<Version>("\"1.0.0e\"").is_err());
        assert!(serde_json::from_str::<Version>("\"foo\"").is_err());
        assert!(serde_json::from_str::<Version>("\"\"").is_err());
        assert!(serde_json::from_str::<Version>("40").is_err());
    }
}