use std::fmt;
use std::str::FromStr;
use crate::{FormatError, Result};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct FormatVersion {
pub major: u32,
pub minor: u32,
pub patch: u32,
}
impl FormatVersion {
pub const UNVERSIONED: Self = Self::new(0, 1, 0);
pub const fn new(major: u32, minor: u32, patch: u32) -> Self {
Self { major, minor, patch }
}
pub fn readable_by(self, known: Self) -> bool {
self.breaking() <= known.breaking()
}
pub fn writable_by(self, known: Self) -> bool {
(self.major, self.minor) <= (known.major, known.minor)
}
fn breaking(self) -> (u32, u32) {
if self.major == 0 { (0, self.minor) } else { (self.major, 0) }
}
}
impl fmt::Display for FormatVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
}
}
impl FromStr for FormatVersion {
type Err = FormatError;
fn from_str(s: &str) -> Result<Self> {
let bad = || FormatError::new(format!("a format version is MAJOR.MINOR.PATCH, got {s:?}"));
let parts = s.trim().split('.').map(str::parse).collect::<std::result::Result<Vec<u32>, _>>();
match parts.map_err(|_| bad())?[..] {
[major, minor, patch] => Ok(Self::new(major, minor, patch)),
[legacy] => Ok(Self::new(0, legacy, 0)),
_ => Err(bad()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn v(s: &str) -> FormatVersion {
s.parse().unwrap()
}
#[test]
fn parses_semver_and_the_legacy_integer() {
assert_eq!(v("0.2.0"), FormatVersion::new(0, 2, 0));
assert_eq!(v(" 1.12.3 ").to_string(), "1.12.3");
assert_eq!(v("2"), v("0.2.0"));
assert_eq!(v("1"), FormatVersion::UNVERSIONED);
for bad in ["", "1.2", "1.2.3.4", "v1.2.3", "1.-2.3"] {
assert!(bad.parse::<FormatVersion>().is_err(), "{bad:?}");
}
}
#[test]
fn compatibility_follows_the_bump() {
let known = v("0.2.0");
assert!(v("0.1.0").readable_by(known) && v("0.1.0").writable_by(known));
assert!(v("0.2.5").readable_by(known) && v("0.2.5").writable_by(known));
assert!(!v("0.3.0").readable_by(known) && !v("0.3.0").writable_by(known));
assert!(!v("1.0.0").readable_by(known));
let known = v("1.2.0");
assert!(v("0.9.0").readable_by(known) && v("0.9.0").writable_by(known));
assert!(v("1.2.7").writable_by(known));
assert!(v("1.3.0").readable_by(known) && !v("1.3.0").writable_by(known));
assert!(!v("2.0.0").readable_by(known) && !v("2.0.0").writable_by(known));
}
}