use anyhow::Result;
use serde::{Deserialize, Deserializer, Serialize};
use tracing::error;
use winnow::Parser;
use winnow::ascii::digit1;
use winnow::combinator::{alt, opt, preceded, seq};
use winnow::error::{ContextError, ErrMode};
use winnow::prelude::*;
use std::fmt::{self, Display};
use std::str::FromStr;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Default)]
pub struct Version {
pub major: u32,
pub minor: u32,
pub patch: u32,
pub build: u32,
}
impl FromStr for Version {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self> {
if s.is_empty() {
return Err(anyhow::anyhow!("Version string cannot be empty"));
}
let version =
Version::parse_version(s).map_err(|e| anyhow::anyhow!("Failed to parse version: {}", e))?;
Ok(version)
}
}
impl Version {
pub fn new(major: u32, minor: u32, patch: u32, build: Option<u32>) -> Self {
match build {
Some(build) => Self {
major,
minor,
patch,
build,
},
None => Self::new_without_build(major, minor, patch),
}
}
pub fn new_without_build(major: u32, minor: u32, patch: u32) -> Self {
Self {
major,
minor,
patch,
..Default::default()
}
}
fn parse_version(input: &str) -> ModalResult<Version> {
let mut input_slice = input;
let (_, major, minor, patch, build) = seq!(
opt(alt(("v", "V"))),
digit1.parse_to::<u32>(),
preceded('.', digit1.parse_to::<u32>()),
preceded('.', digit1.parse_to::<u32>()),
opt(preceded('.', digit1.parse_to::<u32>())),
)
.parse_next(&mut input_slice)?;
if !input_slice.is_empty() {
let error_msg = format!(
"Invalid version format: extra characters '{}' found after '{}'",
input_slice,
&input[..input.len() - input_slice.len()]
);
error!("{}", error_msg);
return Err(ErrMode::Cut(ContextError::default()));
}
Ok(Version::new(major, minor, patch, build))
}
pub fn base_version(&self) -> Version {
Version::new_without_build(self.major, self.minor, self.patch)
}
pub fn can_apply_patch(&self, patch_base_version: &Version) -> bool {
self.base_version() == patch_base_version.base_version()
}
pub fn is_compatible_with_patch(&self, patch_version: &Version) -> bool {
self.base_version() == patch_version.base_version() && self.build <= patch_version.build
}
pub fn to_short_string(&self) -> String {
if self.build == 0 {
format!("{}.{}.{}", self.major, self.minor, self.patch)
} else {
self.to_string()
}
}
pub fn base_version_string(&self) -> String {
format!("{}.{}.{}", self.major, self.minor, self.patch)
}
pub fn validate(&self) -> Result<()> {
if self.major > 999 || self.minor > 999 || self.patch > 999 || self.build > 9999 {
return Err(anyhow::anyhow!(
"Version number values are too large and may be invalid"
));
}
Ok(())
}
}
impl Display for Version {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}.{}.{}.{}",
self.major, self.minor, self.patch, self.build
)
}
}
pub fn version_from_str<'de, D>(deserializer: D) -> std::result::Result<Version, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Version::from_str(&s).map_err(serde::de::Error::custom)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum VersionComparison {
Equal,
Newer,
PatchUpgradeable,
FullUpgradeRequired,
}
impl Version {
pub fn compare_detailed(&self, server_version: &Version) -> VersionComparison {
if self == server_version {
return VersionComparison::Equal;
}
if self.can_apply_patch(server_version) {
if self.build < server_version.build {
VersionComparison::PatchUpgradeable
} else {
VersionComparison::Newer
}
} else {
let self_base = self.base_version();
let server_base = server_version.base_version();
if self_base < server_base {
VersionComparison::FullUpgradeRequired
} else {
VersionComparison::Newer
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_version_parsing() {
let v1 = Version::from_str("0.0.13.5").unwrap();
assert_eq!(v1.major, 0);
assert_eq!(v1.minor, 0);
assert_eq!(v1.patch, 13);
assert_eq!(v1.build, 5);
let v2 = Version::from_str("1.2.3").unwrap();
assert_eq!(v2.major, 1);
assert_eq!(v2.minor, 2);
assert_eq!(v2.patch, 3);
assert_eq!(v2.build, 0);
assert!(Version::from_str("1.2").is_err());
assert!(Version::from_str("1.2.3.4.5").is_err());
assert!(Version::from_str("").is_err());
assert!(Version::from_str("a.b.c").is_err());
}
#[test]
fn test_version_comparison() {
let v1 = Version::from_str("0.0.13.5").unwrap();
let v2 = Version::from_str("0.0.13.2").unwrap();
let v3 = Version::from_str("0.0.14.0").unwrap();
assert!(v1 > v2);
assert!(v2 < v1);
assert!(v3 > v1);
assert!(v3 > v2);
let v4 = Version::from_str("0.0.13.5").unwrap();
assert_eq!(v1, v4);
}
#[test]
fn test_base_version() {
let v1 = Version::from_str("0.0.13.5").unwrap();
let base = v1.base_version();
assert_eq!(base.major, 0);
assert_eq!(base.minor, 0);
assert_eq!(base.patch, 13);
assert_eq!(base.build, 0);
assert_eq!(base.to_string(), "0.0.13.0");
}
#[test]
fn test_can_apply_patch() {
let current = Version::from_str("0.0.13.2").unwrap();
let patch_target = Version::from_str("0.0.13.0").unwrap();
let different_base = Version::from_str("0.0.14.0").unwrap();
assert!(current.can_apply_patch(&patch_target));
assert!(!current.can_apply_patch(&different_base));
}
#[test]
fn test_version_display() {
let v = Version::from_str("0.0.13.5").unwrap();
assert_eq!(v.to_string(), "0.0.13.5");
let v_short = Version::from_str("0.0.13.0").unwrap();
assert_eq!(v_short.to_short_string(), "0.0.13");
assert_eq!(v.to_short_string(), "0.0.13.5");
}
#[test]
fn test_detailed_comparison() {
let current = Version::from_str("0.0.13.2").unwrap();
let same = Version::from_str("0.0.13.2").unwrap();
assert_eq!(current.compare_detailed(&same), VersionComparison::Equal);
let patch_upgrade = Version::from_str("0.0.13.5").unwrap();
assert_eq!(
current.compare_detailed(&patch_upgrade),
VersionComparison::PatchUpgradeable
);
let full_upgrade = Version::from_str("0.0.14.0").unwrap();
assert_eq!(
current.compare_detailed(&full_upgrade),
VersionComparison::FullUpgradeRequired
);
let older = Version::from_str("0.0.12.0").unwrap();
assert_eq!(current.compare_detailed(&older), VersionComparison::Newer);
}
#[test]
fn test_compatibility() {
let current = Version::from_str("0.0.13.2").unwrap();
let patch_v1 = Version::from_str("0.0.13.5").unwrap();
let patch_v2 = Version::from_str("0.0.13.1").unwrap();
let different_base = Version::from_str("0.0.14.0").unwrap();
assert!(current.is_compatible_with_patch(&patch_v1));
assert!(!current.is_compatible_with_patch(&patch_v2)); assert!(!current.is_compatible_with_patch(&different_base));
}
#[test]
fn test_validation() {
let valid_v = Version::from_str("0.0.13.5").unwrap();
assert!(valid_v.validate().is_ok());
let invalid_v = Version::new(1000, 1000, 1000, Some(10000));
assert!(invalid_v.validate().is_err());
}
#[test]
fn test_task_1_2_acceptance_criteria() {
let v1 = Version::from_str("0.0.13.5").expect("应该能解析四段式版本号");
let v2 = Version::from_str("0.0.13.2").expect("应该能解析四段式版本号");
assert!(v1 > v2, "版本比较应该正常工作");
assert_eq!(v1.base_version(), v2.base_version(), "基础版本应该相同");
assert!(v1.can_apply_patch(&v2), "补丁适用性检查应该正常工作");
println!("✅ Task 1.2: 版本管理系统重构 - 验收标准测试通过");
println!(" - ✅ 四段式版本号解析功能正常");
println!(" - ✅ 版本比较逻辑正常工作");
println!(" - ✅ 基础版本提取功能正常");
println!(" - ✅ 补丁适用性检查功能正常");
println!(" - ✅ 版本格式验证功能正常");
}
}