1use anyhow::Result;
11use serde::{Deserialize, Deserializer, Serialize};
12use tracing::error;
13use winnow::Parser;
14use winnow::ascii::digit1;
15use winnow::combinator::{alt, opt, preceded, seq};
16use winnow::error::{ContextError, ErrMode};
17use winnow::prelude::*;
18
19use std::fmt::{self, Display};
20use std::str::FromStr;
21
22#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Default)]
28pub struct Version {
29 pub major: u32,
31 pub minor: u32,
33 pub patch: u32,
35 pub build: u32,
37}
38
39impl FromStr for Version {
56 type Err = anyhow::Error;
57
58 fn from_str(s: &str) -> Result<Self> {
59 if s.is_empty() {
60 return Err(anyhow::anyhow!("Version string cannot be empty"));
61 }
62 let version =
63 Version::parse_version(s).map_err(|e| anyhow::anyhow!("Failed to parse version: {}", e))?;
64 Ok(version)
65 }
66}
67
68impl Version {
69 pub fn new(major: u32, minor: u32, patch: u32, build: Option<u32>) -> Self {
71 match build {
72 Some(build) => Self {
73 major,
74 minor,
75 patch,
76 build,
77 },
78 None => Self::new_without_build(major, minor, patch),
79 }
80 }
81 pub fn new_without_build(major: u32, minor: u32, patch: u32) -> Self {
82 Self {
83 major,
84 minor,
85 patch,
86 ..Default::default()
87 }
88 }
89
90 fn parse_version(input: &str) -> ModalResult<Version> {
92 let mut input_slice = input;
93
94 let (_, major, minor, patch, build) = seq!(
95 opt(alt(("v", "V"))),
96 digit1.parse_to::<u32>(),
97 preceded('.', digit1.parse_to::<u32>()),
98 preceded('.', digit1.parse_to::<u32>()),
99 opt(preceded('.', digit1.parse_to::<u32>())),
100 )
101 .parse_next(&mut input_slice)?;
102
103 if !input_slice.is_empty() {
105 let error_msg = format!(
107 "Invalid version format: extra characters '{}' found after '{}'",
108 input_slice,
109 &input[..input.len() - input_slice.len()]
110 );
111 error!("{}", error_msg);
112 return Err(ErrMode::Cut(ContextError::default()));
113 }
114
115 Ok(Version::new(major, minor, patch, build))
116 }
117
118 pub fn base_version(&self) -> Version {
129 Version::new_without_build(self.major, self.minor, self.patch)
130 }
131
132 pub fn can_apply_patch(&self, patch_base_version: &Version) -> bool {
148 self.base_version() == patch_base_version.base_version()
149 }
150
151 pub fn is_compatible_with_patch(&self, patch_version: &Version) -> bool {
156 self.base_version() == patch_version.base_version() && self.build <= patch_version.build
157 }
158
159 pub fn to_short_string(&self) -> String {
165 if self.build == 0 {
166 format!("{}.{}.{}", self.major, self.minor, self.patch)
167 } else {
168 self.to_string()
169 }
170 }
171
172 pub fn base_version_string(&self) -> String {
174 format!("{}.{}.{}", self.major, self.minor, self.patch)
175 }
176
177 pub fn validate(&self) -> Result<()> {
179 if self.major > 999 || self.minor > 999 || self.patch > 999 || self.build > 9999 {
181 return Err(anyhow::anyhow!(
182 "Version number values are too large and may be invalid"
183 ));
184 }
185
186 Ok(())
187 }
188}
189
190impl Display for Version {
191 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
192 write!(
193 f,
194 "{}.{}.{}.{}",
195 self.major, self.minor, self.patch, self.build
196 )
197 }
198}
199
200pub fn version_from_str<'de, D>(deserializer: D) -> std::result::Result<Version, D::Error>
202where
203 D: Deserializer<'de>,
204{
205 let s = String::deserialize(deserializer)?;
206 Version::from_str(&s).map_err(serde::de::Error::custom)
207}
208
209#[derive(Debug, Clone, PartialEq, Eq)]
211pub enum VersionComparison {
212 Equal,
214 Newer,
216 PatchUpgradeable,
218 FullUpgradeRequired,
220}
221
222impl Version {
223 pub fn compare_detailed(&self, server_version: &Version) -> VersionComparison {
239 if self == server_version {
240 return VersionComparison::Equal;
241 }
242
243 if self.can_apply_patch(server_version) {
245 if self.build < server_version.build {
247 VersionComparison::PatchUpgradeable
248 } else {
249 VersionComparison::Newer
250 }
251 } else {
252 let self_base = self.base_version();
254 let server_base = server_version.base_version();
255 if self_base < server_base {
256 VersionComparison::FullUpgradeRequired
257 } else {
258 VersionComparison::Newer
259 }
260 }
261 }
262}
263
264#[cfg(test)]
265mod tests {
266 use super::*;
267
268 #[test]
269 fn test_version_parsing() {
270 let v1 = Version::from_str("0.0.13.5").unwrap();
272 assert_eq!(v1.major, 0);
273 assert_eq!(v1.minor, 0);
274 assert_eq!(v1.patch, 13);
275 assert_eq!(v1.build, 5);
276
277 let v2 = Version::from_str("1.2.3").unwrap();
279 assert_eq!(v2.major, 1);
280 assert_eq!(v2.minor, 2);
281 assert_eq!(v2.patch, 3);
282 assert_eq!(v2.build, 0);
283
284 assert!(Version::from_str("1.2").is_err());
286 assert!(Version::from_str("1.2.3.4.5").is_err());
287 assert!(Version::from_str("").is_err());
288 assert!(Version::from_str("a.b.c").is_err());
289 }
290
291 #[test]
292 fn test_version_comparison() {
293 let v1 = Version::from_str("0.0.13.5").unwrap();
294 let v2 = Version::from_str("0.0.13.2").unwrap();
295 let v3 = Version::from_str("0.0.14.0").unwrap();
296
297 assert!(v1 > v2);
299 assert!(v2 < v1);
300 assert!(v3 > v1);
301 assert!(v3 > v2);
302
303 let v4 = Version::from_str("0.0.13.5").unwrap();
305 assert_eq!(v1, v4);
306 }
307
308 #[test]
309 fn test_base_version() {
310 let v1 = Version::from_str("0.0.13.5").unwrap();
311 let base = v1.base_version();
312
313 assert_eq!(base.major, 0);
314 assert_eq!(base.minor, 0);
315 assert_eq!(base.patch, 13);
316 assert_eq!(base.build, 0);
317 assert_eq!(base.to_string(), "0.0.13.0");
318 }
319
320 #[test]
321 fn test_can_apply_patch() {
322 let current = Version::from_str("0.0.13.2").unwrap();
323 let patch_target = Version::from_str("0.0.13.0").unwrap();
324 let different_base = Version::from_str("0.0.14.0").unwrap();
325
326 assert!(current.can_apply_patch(&patch_target));
327 assert!(!current.can_apply_patch(&different_base));
328 }
329
330 #[test]
331 fn test_version_display() {
332 let v = Version::from_str("0.0.13.5").unwrap();
333 assert_eq!(v.to_string(), "0.0.13.5");
334
335 let v_short = Version::from_str("0.0.13.0").unwrap();
336 assert_eq!(v_short.to_short_string(), "0.0.13");
337 assert_eq!(v.to_short_string(), "0.0.13.5");
338 }
339
340 #[test]
341 fn test_detailed_comparison() {
342 let current = Version::from_str("0.0.13.2").unwrap();
343
344 let same = Version::from_str("0.0.13.2").unwrap();
346 assert_eq!(current.compare_detailed(&same), VersionComparison::Equal);
347
348 let patch_upgrade = Version::from_str("0.0.13.5").unwrap();
350 assert_eq!(
351 current.compare_detailed(&patch_upgrade),
352 VersionComparison::PatchUpgradeable
353 );
354
355 let full_upgrade = Version::from_str("0.0.14.0").unwrap();
357 assert_eq!(
358 current.compare_detailed(&full_upgrade),
359 VersionComparison::FullUpgradeRequired
360 );
361
362 let older = Version::from_str("0.0.12.0").unwrap();
364 assert_eq!(current.compare_detailed(&older), VersionComparison::Newer);
365 }
366
367 #[test]
368 fn test_compatibility() {
369 let current = Version::from_str("0.0.13.2").unwrap();
370 let patch_v1 = Version::from_str("0.0.13.5").unwrap();
371 let patch_v2 = Version::from_str("0.0.13.1").unwrap();
372 let different_base = Version::from_str("0.0.14.0").unwrap();
373
374 assert!(current.is_compatible_with_patch(&patch_v1));
375 assert!(!current.is_compatible_with_patch(&patch_v2)); assert!(!current.is_compatible_with_patch(&different_base));
377 }
378
379 #[test]
380 fn test_validation() {
381 let valid_v = Version::from_str("0.0.13.5").unwrap();
382 assert!(valid_v.validate().is_ok());
383
384 let invalid_v = Version::new(1000, 1000, 1000, Some(10000));
385 assert!(invalid_v.validate().is_err());
386 }
387
388 #[test]
390 fn test_task_1_2_acceptance_criteria() {
391 let v1 = Version::from_str("0.0.13.5").expect("应该能解析四段式版本号");
393 let v2 = Version::from_str("0.0.13.2").expect("应该能解析四段式版本号");
394
395 assert!(v1 > v2, "版本比较应该正常工作");
397
398 assert_eq!(v1.base_version(), v2.base_version(), "基础版本应该相同");
400
401 assert!(v1.can_apply_patch(&v2), "补丁适用性检查应该正常工作");
403
404 println!("✅ Task 1.2: 版本管理系统重构 - 验收标准测试通过");
405 println!(" - ✅ 四段式版本号解析功能正常");
406 println!(" - ✅ 版本比较逻辑正常工作");
407 println!(" - ✅ 基础版本提取功能正常");
408 println!(" - ✅ 补丁适用性检查功能正常");
409 println!(" - ✅ 版本格式验证功能正常");
410 }
411}