1use crate::error::CoreError;
8use std::cmp::Ordering;
9use std::fmt;
10use std::str::FromStr;
11
12use serde::{Deserialize, Serialize};
13
14#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
16pub struct Version {
17 major: u64,
19 minor: u64,
21 patch: u64,
23 prerelease: Option<String>,
25 buildmetadata: Option<String>,
27}
28
29impl Version {
30 pub fn new(major: u64, minor: u64, patch: u64) -> Self {
32 Self {
33 major,
34 minor,
35 patch,
36 prerelease: None,
37 buildmetadata: None,
38 }
39 }
40
41 pub fn parse_simple(versionstr: &str) -> Result<Self, String> {
43 let parts: Vec<&str> = versionstr.split('.').collect();
44 if parts.len() != 3 {
45 return Err(format!("Invalid version format: {versionstr}"));
46 }
47
48 Ok(Self {
49 major: parts[0]
50 .parse()
51 .map_err(|e| format!("Invalid major version: {e}"))?,
52 minor: parts[1]
53 .parse()
54 .map_err(|e| format!("Invalid minor version: {e}"))?,
55 patch: parts[2]
56 .parse()
57 .map_err(|e| format!("Invalid patch version: {e}"))?,
58 prerelease: None,
59 buildmetadata: None,
60 })
61 }
62
63 pub fn release(major: u64, minor: u64, patch: u64, prerelease: String) -> Self {
65 Self {
66 major,
67 minor,
68 patch,
69 prerelease: Some(prerelease),
70 buildmetadata: None,
71 }
72 }
73
74 pub fn parse(versionstr: &str) -> Result<Self, CoreError> {
76 let version = versionstr.trim();
77
78 let version = if version.starts_with('v') || version.starts_with('V') {
80 &version[1..]
81 } else {
82 version
83 };
84
85 let (version_part, buildmetadata) = if let Some(plus_pos) = version.find('+') {
87 (
88 &version[..plus_pos],
89 Some(version[plus_pos + 1..].to_string()),
90 )
91 } else {
92 (version, None)
93 };
94
95 let (core_version, prerelease) = if let Some(dash_pos) = version_part.find('-') {
97 (
98 &version_part[..dash_pos],
99 Some(version_part[dash_pos + 1..].to_string()),
100 )
101 } else {
102 (version_part, None)
103 };
104
105 let parts: Vec<&str> = core_version.split('.').collect();
107 if parts.len() != 3 {
108 return Err(CoreError::ComputationError(
109 crate::error::ErrorContext::new(format!("Invalid version format: {version}")),
110 ));
111 }
112
113 let major = parts[0].parse::<u64>().map_err(|_| {
114 CoreError::ComputationError(crate::error::ErrorContext::new(format!(
115 "Invalid major version: {}",
116 parts[0]
117 )))
118 })?;
119 let minor = parts[1].parse::<u64>().map_err(|_| {
120 CoreError::ComputationError(crate::error::ErrorContext::new(format!(
121 "Invalid minor version: {}",
122 parts[1]
123 )))
124 })?;
125 let patch = parts[2].parse::<u64>().map_err(|_| {
126 CoreError::ComputationError(crate::error::ErrorContext::new(format!(
127 "Invalid patch version: {}",
128 parts[2]
129 )))
130 })?;
131
132 Ok(Self {
133 major,
134 minor,
135 patch,
136 prerelease,
137 buildmetadata,
138 })
139 }
140
141 pub fn major(&self) -> u64 {
143 self.major
144 }
145
146 pub fn minor(&self) -> u64 {
148 self.minor
149 }
150
151 pub fn patch(&self) -> u64 {
153 self.patch
154 }
155
156 pub fn prerelease(&self) -> Option<&str> {
158 self.prerelease.as_deref()
159 }
160
161 pub fn buildmetadata(&self) -> Option<&str> {
163 self.buildmetadata.as_deref()
164 }
165
166 pub fn is_prerelease(&self) -> bool {
168 self.prerelease.is_some()
169 }
170
171 pub fn is_stable(&self) -> bool {
173 !self.is_prerelease()
174 }
175
176 pub fn increment_major(&mut self) {
178 self.major += 1;
179 self.minor = 0;
180 self.patch = 0;
181 self.prerelease = None;
182 self.buildmetadata = None;
183 }
184
185 pub fn increment_minor(&mut self) {
187 self.minor += 1;
188 self.patch = 0;
189 self.prerelease = None;
190 self.buildmetadata = None;
191 }
192
193 pub fn increment_patch(&mut self) {
195 self.patch += 1;
196 self.prerelease = None;
197 self.buildmetadata = None;
198 }
199
200 pub fn release_2(&mut self, prerelease: Option<String>) {
202 self.prerelease = prerelease;
203 }
204
205 pub fn metadata(&mut self, buildmetadata: Option<String>) {
207 self.buildmetadata = buildmetadata;
208 }
209
210 pub fn is_compatible_with(&self, other: &Self) -> bool {
212 self.major == other.major && self.major > 0
214 }
215
216 pub fn has_breakingchanges_from(&self, other: &Self) -> bool {
218 self.major > other.major
219 }
220
221 pub fn core_version(&self) -> Self {
223 Self::new(self.major, self.minor, self.patch)
224 }
225}
226
227impl fmt::Display for Version {
228 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
229 write!(f, "{}.{}.{}", self.major, self.minor, self.patch)?;
230
231 if let Some(ref prerelease) = self.prerelease {
232 write!(f, "-{prerelease}")?;
233 }
234
235 if let Some(ref buildmetadata) = self.buildmetadata {
236 write!(f, "+{buildmetadata}")?;
237 }
238
239 Ok(())
240 }
241}
242
243impl FromStr for Version {
244 type Err = CoreError;
245
246 fn from_str(s: &str) -> Result<Self, Self::Err> {
247 Self::parse(s)
248 }
249}
250
251impl PartialOrd for Version {
252 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
253 Some(self.cmp(other))
254 }
255}
256
257impl Ord for Version {
258 fn cmp(&self, other: &Self) -> Ordering {
259 match self.major.cmp(&other.major) {
261 Ordering::Equal => {}
262 other => return other,
263 }
264
265 match self.minor.cmp(&other.minor) {
266 Ordering::Equal => {}
267 other => return other,
268 }
269
270 match self.patch.cmp(&other.patch) {
271 Ordering::Equal => {}
272 other => return other,
273 }
274
275 match (&self.prerelease, &other.prerelease) {
277 (None, None) => Ordering::Equal,
278 (Some(_), None) => Ordering::Less, (None, Some(_)) => Ordering::Greater, (Some(a), Some(b)) => compare_prerelease(a, b),
281 }
282 }
283}
284
285#[allow(dead_code)]
287fn compare_prerelease(a: &str, b: &str) -> Ordering {
288 let a_parts: Vec<&str> = a.split('.').collect();
289 let b_parts: Vec<&str> = b.split('.').collect();
290
291 for (a_part, b_part) in a_parts.iter().zip(b_parts.iter()) {
292 let a_num = a_part.parse::<u64>();
294 let b_num = b_part.parse::<u64>();
295
296 match (a_num, b_num) {
297 (Ok(a_n), Ok(b_n)) => match a_n.cmp(&b_n) {
298 Ordering::Equal => {}
299 other => return other,
300 },
301 (Ok(_), Err(_)) => return Ordering::Less, (Err(_), Ok(_)) => return Ordering::Greater, (Err(_), Err(_)) => match a_part.cmp(b_part) {
304 Ordering::Equal => {}
305 other => return other,
306 },
307 }
308 }
309
310 a_parts.len().cmp(&b_parts.len())
312}
313
314#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
316pub enum VersionConstraint {
317 Exact(Version),
319 GreaterThan(Version),
321 GreaterThanOrEqual(Version),
323 LessThan(Version),
325 LessThanOrEqual(Version),
327 Compatible(Version),
329 Tilde(Version),
331 Caret(Version),
333 Any,
335 And(Vec<VersionConstraint>),
337 Or(Vec<VersionConstraint>),
339}
340
341impl VersionConstraint {
342 pub fn constraint(constraintstr: &str) -> Result<Self, CoreError> {
344 let constraint = constraintstr.trim();
345
346 if constraint == "*" {
347 return Ok(Self::Any);
348 }
349
350 if let Some(stripped) = constraint.strip_prefix(">=") {
351 let version = Version::parse(stripped)?;
352 return Ok(Self::GreaterThanOrEqual(version));
353 }
354
355 if let Some(stripped) = constraint.strip_prefix("<=") {
356 let version = Version::parse(stripped)?;
357 return Ok(Self::LessThanOrEqual(version));
358 }
359
360 if let Some(stripped) = constraint.strip_prefix('>') {
361 let version = Version::parse(stripped)?;
362 return Ok(Self::GreaterThan(version));
363 }
364
365 if let Some(stripped) = constraint.strip_prefix('<') {
366 let version = Version::parse(stripped)?;
367 return Ok(Self::LessThan(version));
368 }
369
370 if let Some(stripped) = constraint.strip_prefix('~') {
371 let version = Version::parse(stripped)?;
372 return Ok(Self::Tilde(version));
373 }
374
375 if let Some(stripped) = constraint.strip_prefix('^') {
376 let version = Version::parse(stripped)?;
377 return Ok(Self::Caret(version));
378 }
379
380 if let Some(stripped) = constraint.strip_prefix('=') {
381 let version = Version::parse(stripped)?;
382 return Ok(Self::Exact(version));
383 }
384
385 let version = Version::parse(constraint)?;
387 Ok(Self::Exact(version))
388 }
389
390 pub fn matches(&self, version: &Version) -> bool {
392 match self {
393 Self::Exact(v) => version == v,
394 Self::GreaterThan(v) => version > v,
395 Self::GreaterThanOrEqual(v) => version >= v,
396 Self::LessThan(v) => version < v,
397 Self::LessThanOrEqual(v) => version <= v,
398 Self::Compatible(v) => version.major == v.major && version >= v,
399 Self::Tilde(v) => version.major == v.major && version.minor == v.minor && version >= v,
400 Self::Caret(v) => {
401 if v.major() > 0 {
402 version.major == v.major && version >= v
403 } else if v.minor() > 0 {
404 version.major == 0 && version.minor == v.minor && version >= v
405 } else {
406 version.major == 0
407 && version.minor == 0
408 && version.patch == v.patch
409 && version >= v
410 }
411 }
412 Self::Any => true,
413 Self::And(constraints) => constraints.iter().all(|c| c.matches(version)),
414 Self::Or(constraints) => constraints.iter().any(|c| c.matches(version)),
415 }
416 }
417}
418
419impl fmt::Display for VersionConstraint {
420 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
421 match self {
422 Self::Exact(v) => write!(f, "={v}"),
423 Self::GreaterThan(v) => write!(f, ">{v}"),
424 Self::GreaterThanOrEqual(v) => write!(f, ">={v}"),
425 Self::LessThan(v) => write!(f, "<{v}"),
426 Self::LessThanOrEqual(v) => write!(f, "<={v}"),
427 Self::Compatible(v) => write!(f, "~{v}"),
428 Self::Tilde(v) => write!(f, "~{v}"),
429 Self::Caret(v) => write!(f, "^{v}"),
430 Self::Any => write!(f, "*"),
431 Self::And(constraints) => {
432 let constraintstrs: Vec<String> =
433 constraints.iter().map(|c| c.to_string()).collect();
434 let joined = constraintstrs.join(" && ");
435 write!(f, "{joined}")
436 }
437 Self::Or(constraints) => {
438 let constraintstrs: Vec<String> =
439 constraints.iter().map(|c| c.to_string()).collect();
440 let joined = constraintstrs.join(" || ");
441 write!(f, "{joined}")
442 }
443 }
444 }
445}
446
447#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
449pub struct VersionRange {
450 pub min: Option<Version>,
452 pub max: Option<Version>,
454 pub include_prerelease: bool,
456}
457
458impl VersionRange {
459 pub fn new(min: Option<Version>, max: Option<Version>) -> Self {
461 Self {
462 min,
463 max,
464 include_prerelease: false,
465 }
466 }
467
468 pub fn with_prerelease(mut self) -> Self {
470 self.include_prerelease = true;
471 self
472 }
473
474 pub fn contains(&self, version: &Version) -> bool {
476 if version.is_prerelease() && !self.include_prerelease {
478 return false;
479 }
480
481 if let Some(ref min) = self.min {
483 if version < min {
484 return false;
485 }
486 }
487
488 if let Some(ref max) = self.max {
490 if version >= max {
491 return false;
492 }
493 }
494
495 true
496 }
497
498 pub fn filterversions<'a>(&self, versions: &'a [Version]) -> Vec<&'a Version> {
500 versions.iter().filter(|v| self.contains(v)).collect()
501 }
502}
503
504pub struct VersionBuilder {
506 major: u64,
507 minor: u64,
508 patch: u64,
509 prerelease: Option<String>,
510 buildmetadata: Option<String>,
511}
512
513impl VersionBuilder {
514 pub fn new(major: u64, minor: u64, patch: u64) -> Self {
516 Self {
517 major,
518 minor,
519 patch,
520 prerelease: None,
521 buildmetadata: None,
522 }
523 }
524
525 pub fn release(mut self, prerelease: &str) -> Self {
527 self.prerelease = Some(prerelease.to_string());
528 self
529 }
530
531 pub fn metadata(mut self, buildmetadata: &str) -> Self {
533 self.buildmetadata = Some(buildmetadata.to_string());
534 self
535 }
536
537 pub fn build(self) -> Version {
539 Version {
540 major: self.major,
541 minor: self.minor,
542 patch: self.patch,
543 prerelease: self.prerelease,
544 buildmetadata: self.buildmetadata,
545 }
546 }
547}
548
549#[cfg(test)]
550mod tests {
551 use super::*;
552
553 #[test]
554 fn test_version_creation() {
555 let version = Version::new(1, 2, 3);
556 assert_eq!(version.major(), 1);
557 assert_eq!(version.minor(), 2);
558 assert_eq!(version.patch(), 3);
559 assert!(!version.is_prerelease());
560 assert!(version.is_stable());
561 }
562
563 #[test]
564 fn test_version_parsing() {
565 let version = Version::parse("1.2.3").unwrap();
566 assert_eq!(version.to_string(), "1.2.3");
567
568 let version = Version::parse("1.2.3-alpha.1").unwrap();
569 assert_eq!(version.to_string(), "1.2.3-alpha.1");
570 assert_eq!(version.prerelease(), Some("alpha.1"));
571 assert!(version.is_prerelease());
572
573 let version = Version::parse("1.2.3+build.123").unwrap();
574 assert_eq!(version.buildmetadata(), Some("build.123"));
575
576 let version = Version::parse("v1.2.3").unwrap();
577 assert_eq!(version.to_string(), "1.2.3");
578 }
579
580 #[test]
581 fn test_version_comparison() {
582 let v1_0_0 = Version::parse("1.0.0").unwrap();
583 let v1_0_1 = Version::parse("1.0.1").unwrap();
584 let v1_1_0 = Version::parse("1.1.0").unwrap();
585 let v2_0_0 = Version::parse("2.0.0").unwrap();
586 let v1_0_0_alpha = Version::parse("1.0.0-alpha").unwrap();
587
588 assert!(v1_0_0 < v1_0_1);
589 assert!(v1_0_1 < v1_1_0);
590 assert!(v1_1_0 < v2_0_0);
591 assert!(v1_0_0_alpha < v1_0_0);
592 }
593
594 #[test]
595 fn test_version_increments() {
596 let mut version = Version::parse("1.2.3-alpha+build").unwrap();
597
598 version.increment_patch();
599 assert_eq!(version.to_string(), "1.2.4");
600
601 version.increment_minor();
602 assert_eq!(version.to_string(), "1.3.0");
603
604 version.increment_major();
605 assert_eq!(version.to_string(), "2.0.0");
606 }
607
608 #[test]
609 fn test_version_constraints() {
610 let constraint = VersionConstraint::constraint(">=1.2.0").unwrap();
611 let version = Version::parse("1.2.3").unwrap();
612 assert!(constraint.matches(&version));
613
614 let constraint = VersionConstraint::constraint("^1.2.0").unwrap();
615 let version = Version::parse("1.5.0").unwrap();
616 assert!(constraint.matches(&version));
617 let version = Version::parse("2.0.0").unwrap();
618 assert!(!constraint.matches(&version));
619
620 let constraint = VersionConstraint::constraint("~1.2.0").unwrap();
621 let version = Version::parse("1.2.5").unwrap();
622 assert!(constraint.matches(&version));
623 let version = Version::parse("1.3.0").unwrap();
624 assert!(!constraint.matches(&version));
625 }
626
627 #[test]
628 fn test_version_range() {
629 let min = Version::parse("1.0.0").unwrap();
630 let max = Version::parse("2.0.0").unwrap();
631 let range = VersionRange::new(Some(min), Some(max));
632
633 let version = Version::parse("1.5.0").unwrap();
634 assert!(range.contains(&version));
635
636 let version = Version::parse("0.9.0").unwrap();
637 assert!(!range.contains(&version));
638
639 let version = Version::parse("2.0.0").unwrap();
640 assert!(!range.contains(&version));
641
642 let version = Version::parse("1.5.0-alpha").unwrap();
643 assert!(!range.contains(&version));
644
645 let range = range.with_prerelease();
646 assert!(range.contains(&version));
647 }
648
649 #[test]
650 fn test_version_builder() {
651 let version = VersionBuilder::new(1, 2, 3)
652 .release("alpha.1")
653 .metadata("build.123")
654 .build();
655
656 assert_eq!(version.to_string(), "1.2.3-alpha.1+build.123");
657 }
658
659 #[test]
660 fn test_compatibility() {
661 let v1_0_0 = Version::parse("1.0.0").unwrap();
662 let v1_2_0 = Version::parse("1.2.0").unwrap();
663 let v2_0_0 = Version::parse("2.0.0").unwrap();
664
665 assert!(v1_0_0.is_compatible_with(&v1_2_0));
666 assert!(v1_2_0.is_compatible_with(&v1_0_0));
667 assert!(!v1_0_0.is_compatible_with(&v2_0_0));
668 assert!(v2_0_0.has_breakingchanges_from(&v1_0_0)); }
670
671 #[test]
672 fn test_prerelease_comparison() {
673 let versions = vec![
674 Version::parse("1.0.0-alpha").unwrap(),
675 Version::parse("1.0.0-alpha.1").unwrap(),
676 Version::parse("1.0.0-alpha.beta").unwrap(),
677 Version::parse("1.0.0-beta").unwrap(),
678 Version::parse("1.0.0-beta.2").unwrap(),
679 Version::parse("1.0.0-beta.11").unwrap(),
680 Version::parse("1.0.0-rc.1").unwrap(),
681 Version::parse("1.0.0").unwrap(),
682 ];
683
684 let mut sortedversions = versions.clone();
685 sortedversions.sort();
686
687 assert_eq!(sortedversions, versions);
688 }
689}