1use regex::Regex;
4use rez_next_common::RezCoreError;
5use serde::{Deserialize, Serialize};
6use std::cmp::Ordering;
7use std::hash::{Hash, Hasher};
8
9#[derive(Debug)]
11pub struct Version {
12 tokens: Vec<String>,
14 separators: Vec<String>,
16 pub string_repr: String,
18 cached_hash: Option<u64>,
20}
21
22impl Serialize for Version {
23 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
24 where
25 S: serde::Serializer,
26 {
27 self.string_repr.serialize(serializer)
29 }
30}
31
32impl<'de> Deserialize<'de> for Version {
33 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
34 where
35 D: serde::Deserializer<'de>,
36 {
37 let s = String::deserialize(deserializer)?;
38 Self::parse(&s).map_err(serde::de::Error::custom)
39 }
40}
41
42impl Version {
43 pub fn new(version_str: Option<&str>) -> Result<Self, RezCoreError> {
44 let version_str = version_str.unwrap_or("");
45 Self::parse(version_str)
46 }
47
48 pub fn as_str(&self) -> &str {
49 &self.string_repr
50 }
51}
52
53impl Version {
54 fn parse_internal_gil_free(s: &str) -> Result<(Vec<String>, Vec<String>), RezCoreError> {
57 if s.starts_with('v') || s.starts_with('V') {
59 return Err(RezCoreError::VersionParse(format!(
60 "Version prefixes not supported: '{}'",
61 s
62 )));
63 }
64
65 if s.contains("..") || s.starts_with('.') || s.ends_with('.') {
67 return Err(RezCoreError::VersionParse(format!(
68 "Invalid version syntax: '{}'",
69 s
70 )));
71 }
72
73 let token_regex = Regex::new(r"[a-zA-Z0-9_]+").unwrap();
75 let tokens: Vec<&str> = token_regex.find_iter(s).map(|m| m.as_str()).collect();
76
77 if tokens.is_empty() {
78 return Err(RezCoreError::VersionParse(format!(
79 "Invalid version syntax: '{}'",
80 s
81 )));
82 }
83
84 let numeric_tokens: Vec<_> = tokens
86 .iter()
87 .filter(|t| t.chars().all(|c| c.is_ascii_digit()))
88 .collect();
89 if numeric_tokens.len() > 5 {
90 return Err(RezCoreError::VersionParse(format!(
91 "Version too complex: '{}'",
92 s
93 )));
94 }
95
96 if tokens.len() > 10 {
98 return Err(RezCoreError::VersionParse(format!(
99 "Version too complex: '{}'",
100 s
101 )));
102 }
103
104 let separators: Vec<&str> = token_regex.split(s).collect();
106
107 if !separators[0].is_empty() || !separators[separators.len() - 1].is_empty() {
109 return Err(RezCoreError::VersionParse(format!(
110 "Invalid version syntax: '{}'",
111 s
112 )));
113 }
114
115 for sep in &separators[1..separators.len() - 1] {
116 if sep.len() > 1 {
117 return Err(RezCoreError::VersionParse(format!(
118 "Invalid version syntax: '{}'",
119 s
120 )));
121 }
122 if !matches!(*sep, "." | "-" | "_" | "+") {
124 return Err(RezCoreError::VersionParse(format!(
125 "Invalid separator '{}' in version: '{}'",
126 sep, s
127 )));
128 }
129 }
130
131 for token_str in &tokens {
133 if !token_str.chars().all(|c| c.is_alphanumeric() || c == '_') {
135 return Err(RezCoreError::VersionParse(format!(
136 "Invalid characters in token: '{}'",
137 token_str
138 )));
139 }
140
141 if token_str.starts_with('_') || token_str.ends_with('_') {
143 return Err(RezCoreError::VersionParse(format!(
144 "Invalid token format: '{}'",
145 token_str
146 )));
147 }
148
149 if token_str.chars().all(|c| c.is_alphabetic()) && token_str.len() > 10 {
151 return Err(RezCoreError::VersionParse(format!(
152 "Invalid version token: '{}'",
153 token_str
154 )));
155 }
156
157 if *token_str == "not" || *token_str == "version" {
159 return Err(RezCoreError::VersionParse(format!(
160 "Invalid version token: '{}'",
161 token_str
162 )));
163 }
164 }
165
166 let token_strings: Vec<String> = tokens.into_iter().map(|s| s.to_string()).collect();
168 let sep_strings: Vec<String> = separators[1..separators.len() - 1]
169 .iter()
170 .map(|s| s.to_string())
171 .collect();
172
173 Ok((token_strings, sep_strings))
174 }
175
176 pub fn inf() -> Self {
178 Self {
179 tokens: vec![],
180 separators: vec![],
181 string_repr: "inf".to_string(),
182 cached_hash: None,
183 }
184 }
185
186 pub fn is_inf(&self) -> bool {
188 self.string_repr == "inf"
189 }
190
191 pub fn empty() -> Self {
193 Self {
194 tokens: vec![],
195 separators: vec![],
196 string_repr: "".to_string(),
197 cached_hash: None,
198 }
199 }
200
201 pub fn epsilon() -> Self {
203 Self::empty()
204 }
205
206 pub fn is_empty(&self) -> bool {
208 self.tokens.is_empty() && self.string_repr.is_empty()
209 }
210
211 pub fn len(&self) -> usize {
213 self.tokens.len()
214 }
215
216 pub fn major(&self) -> Option<u64> {
218 self.tokens.first().and_then(|t| t.parse::<u64>().ok())
219 }
220
221 pub fn minor(&self) -> Option<u64> {
223 self.tokens.get(1).and_then(|t| t.parse::<u64>().ok())
224 }
225
226 pub fn patch(&self) -> Option<u64> {
228 self.tokens.get(2).and_then(|t| t.parse::<u64>().ok())
229 }
230
231 pub fn is_epsilon(&self) -> bool {
233 self.is_empty()
234 }
235
236 pub fn is_prerelease(&self) -> bool {
238 if self.is_empty() || self.is_inf() {
239 return false;
240 }
241
242 for token in &self.tokens {
244 let s_lower = token.to_lowercase();
245 if s_lower.contains("alpha")
247 || s_lower.contains("beta")
248 || s_lower.contains("rc")
249 || s_lower.contains("dev")
250 || s_lower.contains("pre")
251 || s_lower.contains("snapshot")
252 {
253 return true;
254 }
255 }
256 false
257 }
258
259 pub fn parse(s: &str) -> Result<Self, RezCoreError> {
261 let s = s.trim();
262
263 if s.is_empty() {
265 return Ok(Self::empty());
266 }
267
268 if s == "inf" {
270 return Ok(Self::inf());
271 }
272
273 if s == "epsilon" {
275 return Ok(Self::epsilon());
276 }
277
278 let (tokens, separators) = Self::parse_internal_gil_free(s)?;
280
281 Ok(Self {
282 tokens,
283 separators,
284 string_repr: s.to_string(),
285 cached_hash: None,
286 })
287 }
288
289 fn compare_rez(&self, other: &Self) -> Ordering {
291 match (self.is_inf(), other.is_inf()) {
293 (true, true) => return Ordering::Equal,
294 (true, false) => return Ordering::Greater,
295 (false, true) => return Ordering::Less,
296 (false, false) => {} }
298
299 match (self.is_empty(), other.is_empty()) {
301 (true, true) => return Ordering::Equal,
302 (true, false) => return Ordering::Less,
303 (false, true) => return Ordering::Greater,
304 (false, false) => {} }
306
307 Self::compare_token_strings(&self.tokens, &other.tokens)
309 }
310
311 fn compare_single_token(t1: &str, t2: &str) -> Ordering {
321 if let (Ok(n1), Ok(n2)) = (t1.parse::<i64>(), t2.parse::<i64>()) {
323 return n1.cmp(&n2);
324 }
325 if t1 == t2 {
327 return Ordering::Equal;
328 }
329
330 let t1_all_alpha = t1.chars().all(|c| c.is_alphabetic() || c == '_');
332 let t2_all_alpha = t2.chars().all(|c| c.is_alphabetic() || c == '_');
333 let t1_all_num = t1.chars().all(|c| c.is_ascii_digit());
334 let t2_all_num = t2.chars().all(|c| c.is_ascii_digit());
335 if t1_all_alpha && t2_all_num {
336 return Ordering::Less;
337 }
338 if t1_all_num && t2_all_alpha {
339 return Ordering::Greater;
340 }
341 if t1_all_alpha && t2_all_alpha {
343 return t1.cmp(t2);
344 }
345
346 let seg1 = Self::split_token_segments(t1);
349 let seg2 = Self::split_token_segments(t2);
350
351 for (s1, s2) in seg1.iter().zip(seg2.iter()) {
352 let s1_is_num = s1.parse::<u64>().is_ok();
353 let s2_is_num = s2.parse::<u64>().is_ok();
354 let cmp = match (s1_is_num, s2_is_num) {
355 (true, true) => {
356 let n1: u64 = s1.parse().unwrap();
357 let n2: u64 = s2.parse().unwrap();
358 n1.cmp(&n2)
359 }
360 (false, false) => s1.as_str().cmp(s2.as_str()),
361 (false, true) => Ordering::Less, (true, false) => Ordering::Greater, };
364 if cmp != Ordering::Equal {
365 return cmp;
366 }
367 }
368 seg1.len().cmp(&seg2.len())
369 }
370
371 fn split_token_segments(s: &str) -> Vec<String> {
374 let mut segments = Vec::new();
375 let mut current = String::new();
376 let mut in_digits = false;
377
378 for ch in s.chars() {
379 let is_digit = ch.is_ascii_digit();
380 if current.is_empty() {
381 in_digits = is_digit;
382 current.push(ch);
383 } else if is_digit == in_digits {
384 current.push(ch);
385 } else {
386 segments.push(current.clone());
387 current.clear();
388 in_digits = is_digit;
389 current.push(ch);
390 }
391 }
392 if !current.is_empty() {
393 segments.push(current);
394 }
395 segments
396 }
397
398 fn compare_token_strings(tokens1: &[String], tokens2: &[String]) -> Ordering {
400 for (t1, t2) in tokens1.iter().zip(tokens2.iter()) {
401 let cmp = Self::compare_single_token(t1, t2);
402 if cmp != Ordering::Equal {
403 return cmp;
404 }
405 }
406
407 tokens2.len().cmp(&tokens1.len())
410 }
411}
412
413impl PartialEq for Version {
414 fn eq(&self, other: &Self) -> bool {
415 self.compare_rez(other) == Ordering::Equal
416 }
417}
418
419impl Eq for Version {}
420
421impl Ord for Version {
422 fn cmp(&self, other: &Self) -> Ordering {
423 self.compare_rez(other)
424 }
425}
426
427impl PartialOrd for Version {
428 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
429 Some(self.cmp(other))
430 }
431}
432
433impl Hash for Version {
434 fn hash<H: Hasher>(&self, state: &mut H) {
435 self.string_repr.hash(state);
436 }
437}
438
439impl Clone for Version {
440 fn clone(&self) -> Self {
441 Self {
442 tokens: self.tokens.clone(),
443 separators: self.separators.clone(),
444 string_repr: self.string_repr.clone(),
445 cached_hash: self.cached_hash,
446 }
447 }
448}
449
450#[cfg(test)]
451mod tests {
452 use super::*;
453
454 #[test]
455 fn test_version_creation() {
456 let version = Version::parse("1.2.3").unwrap();
457 assert_eq!(version.as_str(), "1.2.3");
458 assert_eq!(version.tokens.len(), 3);
459 assert!(!version.is_empty());
460 }
461
462 #[test]
463 fn test_empty_version() {
464 let version = Version::parse("").unwrap();
465 assert_eq!(version.as_str(), "");
466 assert_eq!(version.tokens.len(), 0);
467 assert!(version.is_empty());
468 }
469
470 #[test]
471 fn test_version_inf() {
472 let version = Version::inf();
473 assert_eq!(version.as_str(), "inf");
474 assert!(version.is_inf());
475 }
476
477 #[test]
478 fn test_version_epsilon() {
479 let version = Version::epsilon();
480 assert_eq!(version.as_str(), "");
481 assert!(version.is_epsilon());
482 assert!(version.is_empty());
483 }
484
485 #[test]
486 fn test_version_empty() {
487 let version = Version::empty();
488 assert_eq!(version.as_str(), "");
489 assert!(version.is_empty());
490 assert!(version.is_epsilon());
491 }
492
493 #[test]
494 fn test_version_parsing_special() {
495 let empty = Version::parse("").unwrap();
497 assert!(empty.is_empty());
498
499 let inf = Version::parse("inf").unwrap();
501 assert!(inf.is_inf());
502
503 let epsilon = Version::parse("epsilon").unwrap();
505 assert!(epsilon.is_epsilon());
506 }
507
508 #[test]
509 fn test_version_comparison_boundaries() {
510 let empty = Version::empty();
511 let epsilon = Version::epsilon();
512 let normal = Version::parse("1.0.0").unwrap();
513 let inf = Version::inf();
514
515 assert_eq!(empty.cmp(&epsilon), Ordering::Equal);
517
518 assert_eq!(epsilon.cmp(&normal), Ordering::Less);
520 assert_eq!(normal.cmp(&inf), Ordering::Less);
521 assert_eq!(epsilon.cmp(&inf), Ordering::Less);
522
523 assert_eq!(inf.cmp(&normal), Ordering::Greater);
525 assert_eq!(normal.cmp(&epsilon), Ordering::Greater);
526 assert_eq!(inf.cmp(&epsilon), Ordering::Greater);
527 }
528
529 #[test]
530 fn test_version_prerelease_comparison() {
531 let release = Version::parse("2").unwrap();
533 let prerelease = Version::parse("2.alpha1").unwrap();
534
535 assert_eq!(release.cmp(&prerelease), Ordering::Greater);
537 assert_eq!(prerelease.cmp(&release), Ordering::Less);
538
539 assert!(release >= prerelease); assert!(prerelease < release); }
543
544 #[test]
545 fn test_version_copy() {
546 let version = Version::parse("1.2.3").unwrap();
547 let copied = version.clone();
548 assert_eq!(version.as_str(), copied.as_str());
549 assert_eq!(version.tokens.len(), copied.tokens.len());
550 }
551
552 #[test]
553 fn test_version_trim() {
554 let version = Version::parse("1.2.3.4").unwrap();
555 let mut trimmed_tokens = version.tokens.clone();
557 trimmed_tokens.truncate(2);
558 assert_eq!(trimmed_tokens.len(), 2);
559 }
560
561 #[test]
564 fn test_prerelease_alpha_beta_rc_ordering() {
565 let alpha = Version::parse("1.0.alpha").unwrap();
567 let beta = Version::parse("1.0.beta").unwrap();
568 let rc = Version::parse("1.0.rc").unwrap();
569 let release = Version::parse("1.0").unwrap();
570
571 assert!(alpha < beta, "alpha should be less than beta");
572 assert!(beta < rc, "beta should be less than rc");
573 assert!(rc < release, "rc should be less than release");
574 assert!(alpha < release, "alpha should be less than release");
575 }
576
577 #[test]
578 fn test_prerelease_alpha_numbered_variants() {
579 let a1 = Version::parse("1.0.alpha1").unwrap();
581 let a2 = Version::parse("1.0.alpha2").unwrap();
582 let a10 = Version::parse("1.0.alpha10").unwrap();
583
584 assert!(a1 < a2, "alpha1 < alpha2");
585 assert!(a2 < a10, "alpha2 < alpha10 (numeric comparison)");
586 }
587
588 #[test]
589 fn test_prerelease_dev_pre_snapshot_ordering() {
590 let dev = Version::parse("1.0.dev").unwrap();
594 let alpha = Version::parse("1.0.alpha").unwrap();
595 let pre = Version::parse("1.0.pre").unwrap();
596 let snapshot = Version::parse("1.0.snapshot").unwrap();
597 let release = Version::parse("1.0").unwrap();
598
599 assert!(dev < release, "1.0.dev < 1.0");
601 assert!(alpha < release, "1.0.alpha < 1.0");
602 assert!(pre < release, "1.0.pre < 1.0");
603 assert!(snapshot < release, "1.0.snapshot < 1.0");
604
605 assert!(alpha < dev, "alpha < dev (a < d)");
607 assert!(dev < pre, "dev < pre (d < p)");
608 assert!(pre < snapshot, "pre < snapshot (p < s)");
609
610 assert!(dev.is_prerelease(), "dev is detected as prerelease");
612 assert!(pre.is_prerelease(), "pre is detected as prerelease");
613 assert!(
614 snapshot.is_prerelease(),
615 "snapshot is detected as prerelease"
616 );
617 }
618
619 #[test]
620 fn test_prerelease_mixed_with_numeric_tokens() {
621 let v_alpha = Version::parse("2.0.0-alpha").unwrap();
623 let v_beta = Version::parse("2.0.0-beta").unwrap();
624 let v_stable = Version::parse("2.0.0").unwrap();
625
626 assert!(v_alpha < v_beta, "2.0.0-alpha < 2.0.0-beta");
627 assert!(v_beta < v_stable, "2.0.0-beta < 2.0.0");
628 assert!(v_alpha.is_prerelease());
629 assert!(v_beta.is_prerelease());
630 assert!(!v_stable.is_prerelease());
631 }
632
633 #[test]
634 fn test_prerelease_rc_vs_stable_same_prefix() {
635 let rc1 = Version::parse("3.0.rc1").unwrap();
637 let stable = Version::parse("3.0").unwrap();
638 let rc2 = Version::parse("3.0.rc2").unwrap();
639
640 assert!(rc1 < stable, "rc1 < stable 3.0");
641 assert!(rc2 < stable, "rc2 < stable 3.0");
642 assert!(rc1 < rc2, "rc1 < rc2");
643 }
644
645 #[test]
646 fn test_prerelease_is_prerelease_detection() {
647 assert!(Version::parse("1.alpha").unwrap().is_prerelease());
649 assert!(Version::parse("1.beta").unwrap().is_prerelease());
650 assert!(Version::parse("1.rc").unwrap().is_prerelease());
651 assert!(Version::parse("1.dev").unwrap().is_prerelease());
652 assert!(Version::parse("1.pre").unwrap().is_prerelease());
653 assert!(Version::parse("1.snapshot").unwrap().is_prerelease());
654
655 assert!(!Version::parse("1.0").unwrap().is_prerelease());
657 assert!(!Version::parse("1.0.0").unwrap().is_prerelease());
658 assert!(!Version::parse("2024.5").unwrap().is_prerelease());
659
660 assert!(!Version::empty().is_prerelease());
662 assert!(!Version::inf().is_prerelease());
663 }
664}