use core::fmt;
use core::str::FromStr;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Version {
pub major: u16,
pub minor: u16,
}
impl Version {
#[must_use]
pub const fn new(major: u16, minor: u16) -> Self {
Self { major, minor }
}
#[must_use]
pub const fn accepts(self, target: Self) -> bool {
self.major == target.major && self.minor >= target.minor
}
}
impl fmt::Display for Version {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}", self.major, self.minor)
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error("invalid version string {0:?}, expected \"major.minor\"")]
pub struct ParseVersionError(String);
impl FromStr for Version {
type Err = ParseVersionError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (major, minor) = s
.split_once('.')
.ok_or_else(|| ParseVersionError(s.to_owned()))?;
let major = major.parse().map_err(|_| ParseVersionError(s.to_owned()))?;
let minor = minor.parse().map_err(|_| ParseVersionError(s.to_owned()))?;
Ok(Self { major, minor })
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn accepts_same_major_lower_minor() {
assert!(Version::new(1, 3).accepts(Version::new(1, 2)));
}
#[test]
fn rejects_same_major_higher_minor() {
assert!(!Version::new(1, 1).accepts(Version::new(1, 2)));
}
#[test]
fn rejects_different_major() {
assert!(!Version::new(2, 0).accepts(Version::new(1, 9)));
}
#[test]
fn round_trips_through_display_and_parse() {
let v = Version::new(3, 7);
assert_eq!(v.to_string().parse(), Ok(v));
}
#[test]
fn rejects_malformed_strings() {
assert!("1".parse::<Version>().is_err());
assert!("1.2.3".parse::<Version>().is_err());
assert!("a.b".parse::<Version>().is_err());
}
}