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