use crate::error::{PachaError, Result};
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::fmt;
use std::str::FromStr;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct DatasetVersion {
pub major: u32,
pub minor: u32,
pub patch: u32,
}
impl DatasetVersion {
#[must_use]
pub fn new(major: u32, minor: u32, patch: u32) -> Self {
Self {
major,
minor,
patch,
}
}
#[must_use]
pub fn initial() -> Self {
Self::new(1, 0, 0)
}
#[must_use]
pub fn bump_major(&self) -> Self {
Self::new(self.major + 1, 0, 0)
}
#[must_use]
pub fn bump_minor(&self) -> Self {
Self::new(self.major, self.minor + 1, 0)
}
#[must_use]
pub fn bump_patch(&self) -> Self {
Self::new(self.major, self.minor, self.patch + 1)
}
pub fn parse(s: &str) -> Result<Self> {
s.parse()
}
}
impl Default for DatasetVersion {
fn default() -> Self {
Self::initial()
}
}
impl fmt::Display for DatasetVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
}
}
impl FromStr for DatasetVersion {
type Err = PachaError;
fn from_str(s: &str) -> Result<Self> {
let parts: Vec<&str> = s.split('.').collect();
if parts.len() != 3 {
return Err(PachaError::InvalidVersion(format!(
"expected MAJOR.MINOR.PATCH, got '{s}'"
)));
}
let major = parts[0]
.parse::<u32>()
.map_err(|_| PachaError::InvalidVersion(format!("invalid major version in '{s}'")))?;
let minor = parts[1]
.parse::<u32>()
.map_err(|_| PachaError::InvalidVersion(format!("invalid minor version in '{s}'")))?;
let patch = parts[2]
.parse::<u32>()
.map_err(|_| PachaError::InvalidVersion(format!("invalid patch version in '{s}'")))?;
Ok(Self {
major,
minor,
patch,
})
}
}
impl PartialOrd for DatasetVersion {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for DatasetVersion {
fn cmp(&self, other: &Self) -> Ordering {
match self.major.cmp(&other.major) {
Ordering::Equal => {}
ord => return ord,
}
match self.minor.cmp(&other.minor) {
Ordering::Equal => {}
ord => return ord,
}
self.patch.cmp(&other.patch)
}
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
#[test]
fn test_version_new() {
let v = DatasetVersion::new(1, 2, 3);
assert_eq!(v.major, 1);
assert_eq!(v.minor, 2);
assert_eq!(v.patch, 3);
}
#[test]
fn test_version_display() {
assert_eq!(DatasetVersion::new(1, 2, 3).to_string(), "1.2.3");
}
#[test]
fn test_version_parse() {
assert_eq!(
"1.2.3".parse::<DatasetVersion>().unwrap(),
DatasetVersion::new(1, 2, 3)
);
}
#[test]
fn test_version_parse_errors() {
assert!("1.2".parse::<DatasetVersion>().is_err());
assert!("a.b.c".parse::<DatasetVersion>().is_err());
}
#[test]
fn test_version_bump() {
let v = DatasetVersion::new(1, 2, 3);
assert_eq!(v.bump_major(), DatasetVersion::new(2, 0, 0));
assert_eq!(v.bump_minor(), DatasetVersion::new(1, 3, 0));
assert_eq!(v.bump_patch(), DatasetVersion::new(1, 2, 4));
}
#[test]
fn test_version_ordering() {
let v100 = DatasetVersion::new(1, 0, 0);
let v110 = DatasetVersion::new(1, 1, 0);
let v200 = DatasetVersion::new(2, 0, 0);
assert!(v100 < v110);
assert!(v110 < v200);
}
proptest! {
#[test]
fn prop_version_roundtrip(major: u32, minor: u32, patch: u32) {
let v = DatasetVersion::new(major, minor, patch);
let s = v.to_string();
let parsed: DatasetVersion = s.parse().unwrap();
prop_assert_eq!(v, parsed);
}
}
}