use core::cmp::Ordering;
use core::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Version {
pub major: u16,
pub minor: u16,
pub patch: u16,
}
impl Version {
#[inline]
pub const fn new(major: u16, minor: u16, patch: u16) -> Self {
Self {
major,
minor,
patch,
}
}
#[inline]
pub const fn zero() -> Self {
Self::new(0, 0, 0)
}
pub fn parse(s: &str) -> Option<Self> {
let mut parts = s.split('.');
let major: u16 = parts.next()?.parse().ok()?;
let minor: u16 = parts.next()?.parse().ok()?;
let patch: u16 = parts.next()?.parse().ok()?;
if parts.next().is_some() {
return None;
}
Some(Self::new(major, minor, patch))
}
#[inline]
pub fn is_compatible_with(&self, other: &Self) -> bool {
if self.major == 0 && other.major == 0 {
self.minor == other.minor
} else {
self.major == other.major
}
}
#[inline]
pub fn is_breaking_change_from(&self, other: &Self) -> bool {
if self.major == 0 && other.major == 0 {
self.minor != other.minor
} else {
self.major != other.major
}
}
#[inline]
pub fn is_minor_update_from(&self, other: &Self) -> bool {
self.major == other.major && self.minor > other.minor
}
#[inline]
pub fn is_patch_update_from(&self, other: &Self) -> bool {
self.major == other.major && self.minor == other.minor && self.patch > other.patch
}
#[inline]
pub fn satisfies(&self, minimum: &Self) -> bool {
*self >= *minimum
}
#[inline]
pub fn to_bytes(self) -> [u8; 6] {
let mut bytes = [0u8; 6];
bytes[0..2].copy_from_slice(&self.major.to_le_bytes());
bytes[2..4].copy_from_slice(&self.minor.to_le_bytes());
bytes[4..6].copy_from_slice(&self.patch.to_le_bytes());
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
if bytes.len() < 6 {
return None;
}
let major = u16::from_le_bytes([bytes[0], bytes[1]]);
let minor = u16::from_le_bytes([bytes[2], bytes[3]]);
let patch = u16::from_le_bytes([bytes[4], bytes[5]]);
Some(Self::new(major, minor, patch))
}
#[inline]
pub const fn tuple(self) -> (u16, u16, u16) {
(self.major, self.minor, self.patch)
}
}
impl Ord for Version {
fn cmp(&self, other: &Self) -> Ordering {
match self.major.cmp(&other.major) {
Ordering::Equal => match self.minor.cmp(&other.minor) {
Ordering::Equal => self.patch.cmp(&other.patch),
ord => ord,
},
ord => ord,
}
}
}
impl PartialOrd for Version {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl fmt::Display for Version {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "alloc")]
use alloc::format;
#[test]
fn test_version_new() {
let v = Version::new(1, 2, 3);
assert_eq!(v.major, 1);
assert_eq!(v.minor, 2);
assert_eq!(v.patch, 3);
}
#[test]
fn test_version_parse() {
assert_eq!(Version::parse("1.2.3"), Some(Version::new(1, 2, 3)));
assert_eq!(Version::parse("0.0.0"), Some(Version::new(0, 0, 0)));
assert_eq!(
Version::parse("100.200.300"),
Some(Version::new(100, 200, 300))
);
assert_eq!(Version::parse("1.2"), None);
assert_eq!(Version::parse("1.2.3.4"), None);
assert_eq!(Version::parse("a.b.c"), None);
assert_eq!(Version::parse(""), None);
}
#[cfg(feature = "alloc")]
#[test]
fn test_version_display() {
let v = Version::new(1, 2, 3);
assert_eq!(format!("{}", v), "1.2.3");
}
#[test]
fn test_version_ordering() {
let v1 = Version::new(1, 0, 0);
let v2 = Version::new(1, 1, 0);
let v3 = Version::new(1, 1, 1);
let v4 = Version::new(2, 0, 0);
assert!(v1 < v2);
assert!(v2 < v3);
assert!(v3 < v4);
assert!(v1 < v4);
}
#[test]
fn test_version_compatibility() {
let v1 = Version::new(1, 0, 0);
let v2 = Version::new(1, 5, 0);
let v3 = Version::new(2, 0, 0);
assert!(v1.is_compatible_with(&v2));
assert!(v2.is_compatible_with(&v1));
assert!(!v1.is_compatible_with(&v3));
assert!(!v3.is_compatible_with(&v1));
}
#[test]
fn test_version_0x_compatibility() {
let v1 = Version::new(0, 1, 0);
let v2 = Version::new(0, 1, 5);
let v3 = Version::new(0, 2, 0);
assert!(v1.is_compatible_with(&v2));
assert!(!v1.is_compatible_with(&v3));
}
#[test]
fn test_version_bytes() {
let v = Version::new(1, 2, 3);
let bytes = v.to_bytes();
let v2 = Version::from_bytes(&bytes).expect("parse failed");
assert_eq!(v, v2);
}
#[test]
fn test_breaking_change() {
let v1 = Version::new(1, 0, 0);
let v2 = Version::new(2, 0, 0);
assert!(v2.is_breaking_change_from(&v1));
assert!(!v1.is_breaking_change_from(&v1));
}
#[test]
fn test_minor_update() {
let v1 = Version::new(1, 0, 0);
let v2 = Version::new(1, 1, 0);
let v3 = Version::new(2, 0, 0);
assert!(v2.is_minor_update_from(&v1));
assert!(!v1.is_minor_update_from(&v1));
assert!(!v3.is_minor_update_from(&v1));
}
#[test]
fn test_patch_update() {
let v1 = Version::new(1, 0, 0);
let v2 = Version::new(1, 0, 1);
assert!(v2.is_patch_update_from(&v1));
assert!(!v1.is_patch_update_from(&v1));
}
#[test]
fn test_satisfies() {
let v = Version::new(1, 5, 0);
let min = Version::new(1, 0, 0);
assert!(v.satisfies(&min));
assert!(!min.satisfies(&v));
}
}