1#![deny(missing_docs)]
2#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
3#![allow(deprecated)]
5
6use lazy_regex::{regex_captures, regex_is_match, regex_replace};
7use num_bigint::BigInt;
8use std::cmp::Ordering;
9use std::str::FromStr;
10
11pub mod upstream;
12pub mod vcs;
13pub mod vendor;
14
15#[derive(Debug, Clone)]
19pub struct Version {
20 pub epoch: Option<u32>,
22
23 pub upstream_version: String,
25
26 pub debian_revision: Option<String>,
28}
29
30fn non_digit_cmp(va: &str, vb: &str) -> Ordering {
31 fn order(x: char) -> i32 {
32 match x {
33 '~' => -1,
34 '0'..='9' => unreachable!(),
35 'A'..='Z' | 'a'..='z' => x as i32,
36 _ => x as i32 + 256,
37 }
38 }
39
40 va.chars()
41 .map(order)
42 .chain(std::iter::repeat(0))
43 .zip(vb.chars().map(order).chain(std::iter::repeat(0)))
44 .take(va.len().max(vb.len()))
45 .find_map(|(a, b)| match a.cmp(&b) {
46 Ordering::Equal => None,
47 other => Some(other),
48 })
49 .unwrap_or(Ordering::Equal)
50}
51
52#[test]
53fn test_non_digit_cmp() {
54 assert_eq!(non_digit_cmp("a", "b"), Ordering::Less);
55 assert_eq!(non_digit_cmp("b", "a"), Ordering::Greater);
56 assert_eq!(non_digit_cmp("a", "a"), Ordering::Equal);
57 assert_eq!(non_digit_cmp("a", "-"), Ordering::Less);
58 assert_eq!(non_digit_cmp("a", "+"), Ordering::Less);
59 assert_eq!(non_digit_cmp("a", ""), Ordering::Greater);
60 assert_eq!(non_digit_cmp("", "a"), Ordering::Less);
61 assert_eq!(non_digit_cmp("", ""), Ordering::Equal);
62 assert_eq!(non_digit_cmp("~", ""), Ordering::Less);
63 assert_eq!(non_digit_cmp("~~", "~"), Ordering::Less);
64 assert_eq!(non_digit_cmp("~~", "~~a"), Ordering::Less);
65 assert_eq!(non_digit_cmp("~~a", "~"), Ordering::Less);
66 assert_eq!(non_digit_cmp("~", "a"), Ordering::Less);
67 assert_eq!(non_digit_cmp("!", "@"), Ordering::Less); assert_eq!(non_digit_cmp("@", "!"), Ordering::Greater);
70 assert_eq!(non_digit_cmp("#", "$"), Ordering::Less); assert_eq!(non_digit_cmp("|", "}"), Ordering::Less); }
73
74fn drop_leading_zeroes(s: &str) -> &str {
75 let bytes = s.as_bytes();
77 let mut start = 0;
78 while start + 1 < bytes.len() && bytes[start] == b'0' && bytes[start + 1].is_ascii_digit() {
79 start += 1;
80 }
81 &s[start..]
82}
83
84fn version_cmp_part(mut a: &str, mut b: &str) -> Ordering {
85 while !a.is_empty() || !b.is_empty() {
86 let a_non_digit = &a[..a
88 .chars()
89 .position(|c| c.is_ascii_digit())
90 .unwrap_or(a.len())];
91 let b_non_digit = &b[..b
92 .chars()
93 .position(|c| c.is_ascii_digit())
94 .unwrap_or(b.len())];
95
96 match non_digit_cmp(a_non_digit, b_non_digit) {
98 Ordering::Equal => (),
99 ordering => return ordering,
100 }
101
102 a = &a[a_non_digit.len()..];
104 b = &b[b_non_digit.len()..];
105
106 let a_digit = &a[..a
108 .chars()
109 .position(|c| !c.is_ascii_digit())
110 .unwrap_or(a.len())];
111 let b_digit = &b[..b
112 .chars()
113 .position(|c| !c.is_ascii_digit())
114 .unwrap_or(b.len())];
115
116 let ordering = match (a_digit.len(), b_digit.len()) {
118 (0, 0) => Ordering::Equal,
119 (0, _) => Ordering::Less,
120 (_, 0) => Ordering::Greater,
121 (a_len, b_len) if a_len <= 19 && b_len <= 19 => {
123 match (a_digit.parse::<u64>(), b_digit.parse::<u64>()) {
124 (Ok(a_num), Ok(b_num)) => a_num.cmp(&b_num),
125 _ => a_digit
127 .parse::<BigInt>()
128 .unwrap()
129 .cmp(&b_digit.parse::<BigInt>().unwrap()),
130 }
131 }
132 _ => a_digit
134 .parse::<BigInt>()
135 .unwrap()
136 .cmp(&b_digit.parse::<BigInt>().unwrap()),
137 };
138
139 match ordering {
140 Ordering::Equal => (),
141 ordering => return ordering,
142 }
143
144 a = &a[a_digit.len()..];
146 b = &b[b_digit.len()..];
147 }
148 Ordering::Equal
149}
150
151impl Ord for Version {
152 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
153 let self_norm = self.explicit();
154 let other_norm = other.explicit();
155 if self_norm.0 != other_norm.0 {
156 return std::cmp::Ord::cmp(&self_norm.0, &other_norm.0);
157 }
158
159 match version_cmp_part(self_norm.1, other_norm.1) {
160 Ordering::Equal => (),
161 ordering => return ordering,
162 }
163
164 version_cmp_part(self_norm.2, other_norm.2)
165 }
166}
167
168impl PartialOrd for Version {
169 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
170 Some(self.cmp(other))
171 }
172}
173
174impl PartialEq for Version {
175 fn eq(&self, other: &Self) -> bool {
176 self.partial_cmp(other) == Some(std::cmp::Ordering::Equal)
177 }
178}
179
180impl Eq for Version {}
181
182#[derive(Debug, PartialEq, Eq)]
184pub struct ParseError(String);
185
186impl std::fmt::Display for ParseError {
187 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
188 f.write_str(&self.0)
189 }
190}
191
192impl std::error::Error for ParseError {}
193
194impl FromStr for Version {
195 type Err = ParseError;
196
197 fn from_str(text: &str) -> Result<Self, Self::Err> {
198 let (_, epoch, upstream_version, debian_revision) = if let Some(c) = regex_captures!(
199 r"^(?:(\d+):)?([A-Za-z0-9.+:~-]+?)(?:-([A-Za-z0-9+.~]+))?$",
200 text
201 ) {
202 c
203 } else {
204 return Err(ParseError(format!("Invalid version string: {}", text)));
205 };
206
207 let epoch = Some(epoch)
208 .filter(|e| !e.is_empty())
209 .map(|e| {
210 e.parse()
211 .map_err(|e| ParseError(format!("Error parsing epoch: {}", e)))
212 })
213 .transpose()?;
214
215 let debian_revision = if debian_revision.is_empty() {
216 None
217 } else {
218 Some(debian_revision.to_string())
219 };
220
221 Ok(Version {
222 epoch,
223 upstream_version: upstream_version.to_string(),
224 debian_revision,
225 })
226 }
227}
228
229impl std::fmt::Display for Version {
230 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
231 if let Some(epoch) = self.epoch.as_ref() {
232 write!(f, "{}:", epoch)?;
233 }
234 f.write_str(&self.upstream_version)?;
235 if let Some(debian_revision) = self.debian_revision.as_ref() {
236 write!(f, "-{}", debian_revision)?;
237 }
238 Ok(())
239 }
240}
241
242impl std::hash::Hash for Version {
243 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
244 (
245 self.epoch,
246 self.upstream_version.as_str(),
247 self.debian_revision.as_deref(),
248 )
249 .hash(state);
250 }
251}
252
253fn bump_native_nmu(upstream: &str) -> String {
255 if let Some((_, prefix, n)) = regex_captures!(r"^(.*)\+nmu(\d+)$", upstream) {
256 if let Ok(n) = n.parse::<u32>() {
257 return format!("{}+nmu{}", prefix, n + 1);
258 }
259 }
260 format!("{}+nmu1", upstream)
261}
262
263fn bump_revision_nmu(rev: &str) -> Option<String> {
270 if regex_is_match!(r"^\d+$", rev) {
271 return Some(format!("{}.1", rev));
272 }
273 if let Some((_, prefix, n)) = regex_captures!(r"^(\d+)\.(\d+)$", rev) {
274 let n: u32 = n.parse().ok()?;
275 return Some(format!("{}.{}", prefix, n + 1));
276 }
277 None
278}
279
280impl Version {
281 fn explicit(&self) -> (u32, &str, &str) {
286 (
287 self.epoch.unwrap_or(0),
288 self.upstream_version.as_str(),
289 self.debian_revision.as_deref().unwrap_or("0"),
290 )
291 }
292
293 pub fn parse_lenient(text: &str) -> Result<Self, ParseError> {
311 let (_, epoch, upstream_version, debian_revision) = if let Some(c) = regex_captures!(
312 r"^(?:(\d+):)?([A-Za-z0-9.+:~_-]+?)(?:-([A-Za-z0-9+.~_]+))?$",
313 text
314 ) {
315 c
316 } else {
317 return Err(ParseError(format!("Invalid version string: {}", text)));
318 };
319
320 let epoch = Some(epoch)
321 .filter(|e| !e.is_empty())
322 .map(|e| {
323 e.parse()
324 .map_err(|e| ParseError(format!("Error parsing epoch: {}", e)))
325 })
326 .transpose()?;
327
328 let debian_revision = if debian_revision.is_empty() {
329 None
330 } else {
331 Some(debian_revision.to_string())
332 };
333
334 Ok(Version {
335 epoch,
336 upstream_version: upstream_version.to_string(),
337 debian_revision,
338 })
339 }
340
341 pub fn is_bin_nmu(&self) -> bool {
359 self.bin_nmu_count().is_some()
360 }
361
362 pub fn bin_nmu_count(&self) -> Option<i32> {
366 fn bin_nmu_suffix(s: &str) -> Option<i32> {
367 s.split_once("+b").and_then(|(_, rest)| rest.parse().ok())
368 }
369 if let Some(debian_revision) = self.debian_revision.as_ref() {
370 bin_nmu_suffix(debian_revision)
371 } else {
372 bin_nmu_suffix(self.upstream_version.as_str())
373 }
374 }
375
376 pub fn increment_bin_nmu(self) -> Version {
381 fn increment_bin_nmu_suffix(s: &str) -> String {
382 match s.split_once("+b") {
383 Some((prefix, rest)) => match rest.parse::<i32>() {
384 Ok(num) => format!("{}+b{}", prefix, num + 1),
385 Err(_) => format!("{}+b1", s),
386 },
387 None => format!("{}+b1", s),
388 }
389 }
390
391 if let Some(debian_revision) = self.debian_revision.as_ref() {
392 Version {
393 epoch: self.epoch,
394 upstream_version: self.upstream_version,
395 debian_revision: Some(increment_bin_nmu_suffix(debian_revision)),
396 }
397 } else {
398 Version {
399 epoch: self.epoch,
400 upstream_version: increment_bin_nmu_suffix(&self.upstream_version),
401 debian_revision: self.debian_revision,
402 }
403 }
404 }
405
406 pub fn is_nmu(&self) -> bool {
419 self.nmu_count().is_some()
420 }
421
422 pub fn nmu_count(&self) -> Option<i32> {
427 fn nmu_suffix(s: &str) -> Option<i32> {
428 s.split_once("+nmu").and_then(|(_, rest)| rest.parse().ok())
429 }
430 if let Some(debian_revision) = self.debian_revision.as_ref() {
431 nmu_suffix(debian_revision).or_else(|| {
434 regex_captures!(r"^\d+\.(\d+)$", debian_revision).and_then(|(_, n)| n.parse().ok())
435 })
436 } else {
437 nmu_suffix(self.upstream_version.as_str())
439 }
440 }
441
442 pub fn is_backport(&self) -> bool {
458 regex_is_match!(r"~bpo\d+\+\d+$", &self.to_string())
459 }
460
461 pub fn is_stable_update(&self) -> bool {
477 regex_is_match!(r"[~+]deb\d+u\d+$", &self.to_string())
478 }
479
480 pub fn canonicalize(&self) -> Version {
490 let epoch = match self.epoch {
491 Some(0) => None,
492 epoch => epoch,
493 };
494
495 let upstream_version_stripped = drop_leading_zeroes(&self.upstream_version);
496 let upstream_version = if upstream_version_stripped == self.upstream_version {
497 self.upstream_version.clone()
498 } else {
499 upstream_version_stripped.to_string()
500 };
501
502 let debian_revision = match self.debian_revision.as_ref() {
503 Some(r) if r.chars().all(|c| c == '0') => None,
504 None => None,
505 Some(revision) => {
506 let stripped = drop_leading_zeroes(revision);
507 if stripped == revision {
508 Some(revision.clone())
509 } else {
510 Some(stripped.to_string())
511 }
512 }
513 };
514
515 Version {
516 epoch,
517 upstream_version,
518 debian_revision,
519 }
520 }
521
522 pub fn increment_debian(&mut self) {
527 if let Some(ref mut debian_revision) = self.debian_revision {
528 *debian_revision = regex_replace!(r"\d+$", debian_revision, |x: &str| {
529 (x.parse::<i32>().unwrap() + 1).to_string()
530 })
531 .into_owned();
532 } else {
533 self.upstream_version = regex_replace!(r"\d+$", &self.upstream_version, |x: &str| {
534 (x.parse::<i32>().unwrap() + 1).to_string()
535 })
536 .into_owned();
537 }
538 }
539
540 pub fn is_native(&self) -> bool {
542 self.debian_revision.is_none()
543 }
544
545 pub fn bump_nmu(self) -> Version {
580 if let Some(debian_revision) = self.debian_revision {
581 let debian_revision = bump_revision_nmu(&debian_revision)
582 .unwrap_or_else(|| bump_native_nmu(&debian_revision));
583 Version {
584 epoch: self.epoch,
585 upstream_version: self.upstream_version,
586 debian_revision: Some(debian_revision),
587 }
588 } else {
589 Version {
590 epoch: self.epoch,
591 upstream_version: bump_native_nmu(&self.upstream_version),
592 debian_revision: None,
593 }
594 }
595 }
596}
597
598#[cfg(feature = "sqlx")]
599use sqlx::{postgres::PgTypeInfo, Postgres};
600
601#[cfg(feature = "sqlx")]
602impl sqlx::Type<Postgres> for Version {
603 fn type_info() -> PgTypeInfo {
604 PgTypeInfo::with_name("debversion")
605 }
606}
607
608#[cfg(feature = "sqlx")]
609impl sqlx::Encode<'_, Postgres> for Version {
610 fn encode_by_ref(
611 &self,
612 buf: &mut sqlx::postgres::PgArgumentBuffer,
613 ) -> Result<sqlx::encode::IsNull, Box<dyn std::error::Error + Send + Sync>> {
614 let version_str = self.to_string();
615 sqlx::Encode::<Postgres>::encode_by_ref(&version_str.as_str(), buf)
616 }
617}
618
619#[cfg(feature = "sqlx")]
620impl sqlx::Decode<'_, Postgres> for Version {
621 fn decode(
622 value: sqlx::postgres::PgValueRef<'_>,
623 ) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
624 let s: &str = sqlx::Decode::<Postgres>::decode(value)?;
625 Ok(s.parse::<Version>()?)
626 }
627}
628
629#[cfg(all(feature = "sqlx", test))]
630mod sqlx_tests {
631 #[test]
632 fn type_info() {
633 use super::Version;
634 use sqlx::postgres::PgTypeInfo;
635 use sqlx::Type;
636
637 assert_eq!(PgTypeInfo::with_name("debversion"), Version::type_info());
638 }
639}
640
641#[cfg(feature = "python-debian")]
642use pyo3::prelude::*;
643
644#[cfg(feature = "python-debian")]
645impl FromPyObject<'_, '_> for Version {
646 type Error = PyErr;
647
648 fn extract(ob: pyo3::Borrowed<'_, '_, PyAny>) -> PyResult<Self> {
649 let debian_support = Python::import(ob.py(), "debian.debian_support")?;
650 let version_cls = debian_support.getattr("Version")?;
651 if !ob.is_instance(&version_cls)? {
652 return Err(pyo3::exceptions::PyTypeError::new_err("Expected a Version"));
653 }
654 Ok(Version {
655 epoch: ob
656 .getattr("epoch")?
657 .extract::<Option<String>>()?
658 .map(|s| s.parse().unwrap()),
659 upstream_version: ob.getattr("upstream_version")?.extract::<String>()?,
660 debian_revision: ob.getattr("debian_revision")?.extract::<Option<String>>()?,
661 })
662 }
663}
664
665#[cfg(feature = "python-debian")]
666impl<'py> IntoPyObject<'py> for Version {
667 type Target = PyAny;
668
669 type Output = Bound<'py, Self::Target>;
670
671 type Error = PyErr;
672
673 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
674 let debian_support = py.import("debian.debian_support").unwrap();
675 let version_cls = debian_support.getattr("Version").unwrap();
676 version_cls.call1((self.to_string(),))
677 }
678}
679
680#[cfg(feature = "python-debian")]
681mod python_tests {
682 #[test]
683 fn test_from_pyobject() {
684 use super::Version;
685 use pyo3::prelude::*;
686 use std::ffi::CString;
687
688 Python::attach(|py| {
689 let globals = pyo3::types::PyDict::new(py);
690 globals
691 .set_item(
692 "debian_support",
693 py.import("debian.debian_support").unwrap(),
694 )
695 .unwrap();
696 let v = py
697 .eval(
698 &CString::new("debian_support.Version('1.0-1')").unwrap(),
699 Some(&globals),
700 None,
701 )
702 .unwrap()
703 .extract::<Version>()
704 .unwrap();
705 assert_eq!(
706 v,
707 Version {
708 epoch: None,
709 upstream_version: "1.0".to_string(),
710 debian_revision: Some("1".to_string())
711 }
712 );
713 });
714 }
715
716 #[test]
717 fn test_to_pyobject() {
718 use super::Version;
719 use pyo3::prelude::*;
720
721 Python::attach(|py| {
722 let v = Version {
723 epoch: Some(1),
724 upstream_version: "1.0".to_string(),
725 debian_revision: Some("1".to_string()),
726 };
727 let v = v.into_pyobject(py).unwrap();
728 let expected: Version = "1:1.0-1".parse().unwrap();
729 assert_eq!(v.get_type().name().unwrap(), "Version");
730 assert_eq!(v.unbind().extract::<Version>(py).unwrap(), expected);
731 });
732 }
733
734 #[test]
735 fn test_from_pyobject_error() {
736 use super::Version;
737 use pyo3::prelude::*;
738 use std::ffi::CString;
739
740 Python::attach(|py| {
741 let string_obj = py
743 .eval(&CString::new("'not a version'").unwrap(), None, None)
744 .unwrap();
745 let result = string_obj.extract::<Version>();
746 assert!(result.is_err());
747 });
748 }
749}
750
751#[cfg(feature = "serde")]
752impl serde::Serialize for Version {
753 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
754 where
755 S: serde::Serializer,
756 {
757 serializer.serialize_str(&self.to_string())
758 }
759}
760
761#[cfg(feature = "serde")]
762impl<'de> serde::Deserialize<'de> for Version {
763 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
764 where
765 D: serde::Deserializer<'de>,
766 {
767 let concatenated: String = String::deserialize(deserializer)?;
768 concatenated.parse().map_err(serde::de::Error::custom)
769 }
770}
771
772pub trait AsVersion {
774 fn into_version(self) -> Result<Version, ParseError>;
776}
777
778impl AsVersion for &str {
779 fn into_version(self) -> Result<Version, ParseError> {
780 self.parse()
781 }
782}
783
784impl AsVersion for String {
785 fn into_version(self) -> Result<Version, ParseError> {
786 self.parse()
787 }
788}
789
790impl AsVersion for Version {
791 fn into_version(self) -> Result<Version, ParseError> {
792 Ok(self)
793 }
794}
795
796#[cfg(test)]
797mod tests {
798 use super::{version_cmp_part, ParseError, Version};
799 use std::cmp::Ordering;
800
801 #[test]
802 fn test_canonicalize() {
803 assert_eq!(
804 "1.0-1".parse::<Version>().unwrap().canonicalize(),
805 "1.0-1".parse::<Version>().unwrap()
806 );
807 assert_eq!(
808 "1.0-0".parse::<Version>().unwrap().canonicalize(),
809 "1.0".parse::<Version>().unwrap()
810 );
811 assert_eq!(
812 "0:1.0-2".parse::<Version>().unwrap().canonicalize(),
813 "1.0-2".parse::<Version>().unwrap()
814 );
815 assert_eq!(
816 "0001.0-0".parse::<Version>().unwrap().canonicalize(),
817 "1.0".parse::<Version>().unwrap()
818 );
819 assert_eq!(
820 "000.1".parse::<Version>().unwrap().canonicalize(),
821 "0.1".parse::<Version>().unwrap()
822 );
823 }
824
825 #[test]
826 fn test_explicit() {
827 assert_eq!(
828 (0, "1.0", "1"),
829 "1.0-1".parse::<Version>().unwrap().explicit()
830 );
831 assert_eq!(
832 (1, "1.0", "1"),
833 "1:1.0-1".parse::<Version>().unwrap().explicit()
834 );
835 assert_eq!(
836 (0, "1.0", "0"),
837 "1.0".parse::<Version>().unwrap().explicit()
838 );
839 assert_eq!(
840 (0, "1.0", "0"),
841 "1.0-0".parse::<Version>().unwrap().explicit()
842 );
843 assert_eq!(
844 (1, "1.0", "0"),
845 "1:1.0-0".parse::<Version>().unwrap().explicit()
846 );
847 assert_eq!(
848 (0, "000.1", "0"),
849 "000.1".parse::<Version>().unwrap().explicit()
850 );
851 }
852
853 macro_rules! assert_cmp(
854 ($a:expr, $b:expr, $cmp:tt) => {
855 assert_eq!($a.parse::<Version>().unwrap().cmp(&$b.parse::<Version>().unwrap()), std::cmp::Ordering::$cmp);
856 }
857 );
858
859 #[test]
860 fn test_version_cmp_part() {
861 assert_eq!(version_cmp_part("1.0", "1.0"), Ordering::Equal);
862 assert_eq!(version_cmp_part("0.1", "0.1"), Ordering::Equal);
863 assert_eq!(version_cmp_part("000.1", "0.1"), Ordering::Equal);
864 assert_eq!(version_cmp_part("1.0", "2.0"), Ordering::Less);
865 assert_eq!(version_cmp_part("1.0", "0.0"), Ordering::Greater);
866 assert_eq!(version_cmp_part("10.0", "2.0"), Ordering::Greater);
867 assert_eq!(version_cmp_part("1.0~rc1", "1.0"), Ordering::Less);
868 }
869
870 #[test]
871 fn test_cmp() {
872 assert_cmp!("1.0-1", "1.0-1", Equal);
873 assert_cmp!("1.0-1", "1.0-2", Less);
874 assert_cmp!("1.0-2", "1.0-1", Greater);
875 assert_cmp!("1.0-1", "1.0", Greater);
876 assert_cmp!("1.0", "1.0-1", Less);
877 assert_cmp!("2.50.0", "10.0.1", Less);
878
879 assert_cmp!("1:1.0-1", "1.0-1", Greater);
881 assert_cmp!("1.0-1", "1:1.0-1", Less);
882 assert_cmp!("1:1.0-1", "1:1.0-1", Equal);
883 assert_cmp!("1:1.0-1", "2:1.0-1", Less);
884 assert_cmp!("2:1.0-1", "1:1.0-1", Greater);
885
886 assert_cmp!("1.0~rc1-1", "1.0-1", Less);
888 assert_cmp!("1.0-1", "1.0~rc1-1", Greater);
889 assert_cmp!("1.0~rc1-1", "1.0~rc1-1", Equal);
890 assert_cmp!("1.0~rc1-1", "1.0~rc2-1", Less);
891 assert_cmp!("1.0~rc2-1", "1.0~rc1-1", Greater);
892
893 assert_cmp!("1.0a-1", "1.0-1", Greater);
895 assert_cmp!("1.0-1", "1.0a-1", Less);
896 assert_cmp!("1.0a-1", "1.0a-1", Equal);
897
898 assert_cmp!("23.13.9-7", "0.6.45-2", Greater);
900 }
901
902 #[test]
903 fn test_parse() {
904 assert_eq!(
905 Version {
906 epoch: None,
907 upstream_version: "1.0".to_string(),
908 debian_revision: Some("1".to_string())
909 },
910 "1.0-1".parse().unwrap()
911 );
912
913 assert_eq!(
914 Version {
915 epoch: None,
916 upstream_version: "1.0".to_string(),
917 debian_revision: None
918 },
919 "1.0".parse().unwrap()
920 );
921
922 assert_eq!(
923 Version {
924 epoch: Some(1),
925 upstream_version: "1.0".to_string(),
926 debian_revision: Some("1".to_string())
927 },
928 "1:1.0-1".parse().unwrap()
929 );
930 assert_eq!(
931 "1:;a".parse::<Version>().unwrap_err(),
932 ParseError("Invalid version string: 1:;a".to_string())
933 );
934 }
935
936 #[test]
937 fn test_parse_error_display() {
938 let error = ParseError("test error message".to_string());
939 assert_eq!(format!("{}", error), "test error message");
940 assert_eq!(error.to_string(), "test error message");
941 }
942
943 #[test]
944 fn test_to_string() {
945 assert_eq!(
946 "1.0-1",
947 Version {
948 epoch: None,
949 upstream_version: "1.0".to_string(),
950 debian_revision: Some("1".to_string())
951 }
952 .to_string()
953 );
954 assert_eq!(
955 "1.0",
956 Version {
957 epoch: None,
958 upstream_version: "1.0".to_string(),
959 debian_revision: None,
960 }
961 .to_string()
962 );
963 }
964
965 #[test]
966 fn test_eq() {
967 assert_eq!(
968 "1.0-1".parse::<Version>().unwrap(),
969 "1.0-1".parse::<Version>().unwrap()
970 );
971 }
972
973 #[test]
974 fn test_hash() {
975 use std::collections::hash_map::DefaultHasher;
976 use std::hash::{Hash, Hasher};
977
978 let mut hasher1 = DefaultHasher::new();
979 let mut hasher2 = DefaultHasher::new();
980 let mut hasher3 = DefaultHasher::new();
981
982 "1.0-1".parse::<Version>().unwrap().hash(&mut hasher1);
983 "1.0-1".parse::<Version>().unwrap().hash(&mut hasher2);
984 "0:1.0-1".parse::<Version>().unwrap().hash(&mut hasher3);
985
986 let hash1 = hasher1.finish();
987 let hash2 = hasher2.finish();
988 let hash3 = hasher3.finish();
989
990 assert_eq!(hash1, hash2);
991 assert_ne!(hash1, hash3);
992 }
993
994 #[test]
995 fn to_string() {
996 assert_eq!(
997 "1.0-1",
998 Version {
999 epoch: None,
1000 upstream_version: "1.0".to_string(),
1001 debian_revision: Some("1".to_string())
1002 }
1003 .to_string()
1004 );
1005 assert_eq!(
1006 "1.0",
1007 Version {
1008 epoch: None,
1009 upstream_version: "1.0".to_string(),
1010 debian_revision: None,
1011 }
1012 .to_string()
1013 );
1014 assert_eq!(
1015 "1:1.0",
1016 Version {
1017 epoch: Some(1),
1018 upstream_version: "1.0".to_string(),
1019 debian_revision: None,
1020 }
1021 .to_string()
1022 );
1023 }
1024
1025 #[test]
1026 fn partial_eq() {
1027 assert!("1.0-1"
1028 .parse::<Version>()
1029 .unwrap()
1030 .eq(&"1.0-1".parse::<Version>().unwrap()));
1031 }
1032
1033 #[test]
1034 fn increment() {
1035 let mut v = "1.0-1".parse::<Version>().unwrap();
1036 v.increment_debian();
1037
1038 assert_eq!("1.0-2".parse::<Version>().unwrap(), v);
1039
1040 let mut v = "1.0".parse::<Version>().unwrap();
1041 v.increment_debian();
1042 assert_eq!("1.1".parse::<Version>().unwrap(), v);
1043
1044 let mut v = "1.0ubuntu1".parse::<Version>().unwrap();
1045 v.increment_debian();
1046 assert_eq!("1.0ubuntu2".parse::<Version>().unwrap(), v);
1047
1048 let mut v = "1.0-0ubuntu1".parse::<Version>().unwrap();
1049 v.increment_debian();
1050 assert_eq!("1.0-0ubuntu2".parse::<Version>().unwrap(), v);
1051 }
1052
1053 #[test]
1054 fn is_native() {
1055 assert!(!"1.0-1".parse::<Version>().unwrap().is_native());
1056 assert!("1.0".parse::<Version>().unwrap().is_native());
1057 assert!(!"1.0-0".parse::<Version>().unwrap().is_native());
1058 }
1059
1060 #[test]
1061 fn test_is_binnmu() {
1062 assert!("1.0+b1".parse::<Version>().unwrap().is_bin_nmu());
1063 assert!("1.0-1+b1".parse::<Version>().unwrap().is_bin_nmu());
1064 assert!(!"1.0-1".parse::<Version>().unwrap().is_bin_nmu());
1065 assert!(!"1.0".parse::<Version>().unwrap().is_bin_nmu());
1066 }
1067
1068 #[test]
1069 fn test_bin_nmu_count() {
1070 assert_eq!(
1071 Some(1),
1072 "1.0+b1".parse::<Version>().unwrap().bin_nmu_count()
1073 );
1074 assert_eq!(
1075 Some(1),
1076 "1.0-1+b1".parse::<Version>().unwrap().bin_nmu_count()
1077 );
1078 assert_eq!(None, "1.0-1".parse::<Version>().unwrap().bin_nmu_count());
1079 assert_eq!(None, "1.0".parse::<Version>().unwrap().bin_nmu_count());
1080 }
1081
1082 #[test]
1083 fn test_increment_bin_nmu() {
1084 assert_eq!(
1085 "1.0+b2".parse::<Version>().unwrap(),
1086 "1.0+b1".parse::<Version>().unwrap().increment_bin_nmu()
1087 );
1088 assert_eq!(
1089 "1.0-1+b2".parse::<Version>().unwrap(),
1090 "1.0-1+b1".parse::<Version>().unwrap().increment_bin_nmu()
1091 );
1092 assert_eq!(
1093 "1.0+b1".parse::<Version>().unwrap(),
1094 "1.0".parse::<Version>().unwrap().increment_bin_nmu()
1095 );
1096 assert_eq!(
1097 "1.0-1+b1".parse::<Version>().unwrap(),
1098 "1.0-1".parse::<Version>().unwrap().increment_bin_nmu()
1099 );
1100 }
1101
1102 #[test]
1103 fn test_nmu_count() {
1104 assert_eq!(Some(1), "1.0+nmu1".parse::<Version>().unwrap().nmu_count());
1106 assert_eq!(
1107 Some(1),
1108 "1.0-1+nmu1".parse::<Version>().unwrap().nmu_count()
1109 );
1110 assert_eq!(
1111 Some(2),
1112 "1.0-1ubuntu1+nmu2".parse::<Version>().unwrap().nmu_count()
1113 );
1114
1115 assert_eq!(Some(1), "1.0-1.1".parse::<Version>().unwrap().nmu_count());
1117 assert_eq!(Some(1), "1.0-2.1".parse::<Version>().unwrap().nmu_count());
1118 assert_eq!(Some(3), "1.0-2.3".parse::<Version>().unwrap().nmu_count());
1119
1120 assert_eq!(None, "1.0-1".parse::<Version>().unwrap().nmu_count());
1122 assert_eq!(None, "1.0".parse::<Version>().unwrap().nmu_count());
1123 assert_eq!(None, "1.0-1ubuntu1".parse::<Version>().unwrap().nmu_count());
1124 assert_eq!(None, "1.1".parse::<Version>().unwrap().nmu_count());
1127 }
1128
1129 #[test]
1130 fn test_is_nmu() {
1131 assert!("1.0+nmu1".parse::<Version>().unwrap().is_nmu());
1132 assert!("1.0-1+nmu1".parse::<Version>().unwrap().is_nmu());
1133 assert!("1.0-1.1".parse::<Version>().unwrap().is_nmu());
1134 assert!("1.0-2.1".parse::<Version>().unwrap().is_nmu());
1135 assert!(!"1.0-1".parse::<Version>().unwrap().is_nmu());
1136 assert!(!"1.0".parse::<Version>().unwrap().is_nmu());
1137 assert!(!"1.1".parse::<Version>().unwrap().is_nmu());
1138 }
1139
1140 #[test]
1141 fn test_is_backport() {
1142 assert!("1.0-1~bpo12+1".parse::<Version>().unwrap().is_backport());
1143 assert!("1.0-1~bpo11+2".parse::<Version>().unwrap().is_backport());
1144 assert!("2:1.0-1~bpo12+1".parse::<Version>().unwrap().is_backport());
1145 assert!("1.0~bpo12+1".parse::<Version>().unwrap().is_backport());
1147 assert!(!"1.0-1".parse::<Version>().unwrap().is_backport());
1148 assert!(!"1.0".parse::<Version>().unwrap().is_backport());
1149 assert!(!"1.0-1~bpo12".parse::<Version>().unwrap().is_backport());
1151 assert!(!"1.0-1+deb12u1".parse::<Version>().unwrap().is_backport());
1153 }
1154
1155 #[test]
1156 fn test_is_stable_update() {
1157 assert!("1.0-1+deb12u1"
1158 .parse::<Version>()
1159 .unwrap()
1160 .is_stable_update());
1161 assert!("1.0-1~deb11u2"
1162 .parse::<Version>()
1163 .unwrap()
1164 .is_stable_update());
1165 assert!("2:1.0-1+deb12u1"
1166 .parse::<Version>()
1167 .unwrap()
1168 .is_stable_update());
1169 assert!("1.0+deb12u1".parse::<Version>().unwrap().is_stable_update());
1171 assert!(!"1.0-1".parse::<Version>().unwrap().is_stable_update());
1172 assert!(!"1.0".parse::<Version>().unwrap().is_stable_update());
1173 assert!(!"1.0-1+deb12".parse::<Version>().unwrap().is_stable_update());
1175 assert!(!"1.0-1deb12u1"
1177 .parse::<Version>()
1178 .unwrap()
1179 .is_stable_update());
1180 assert!(!"1.0-1~bpo12+1"
1182 .parse::<Version>()
1183 .unwrap()
1184 .is_stable_update());
1185 }
1186
1187 #[test]
1188 fn test_comparing_very_long_versions() {
1189 let a = "1:11.1.0~++20210314110124+1fdec59bffc1-1~exp1~20210314220751.162";
1191 let b = "1:11.1.0~++20211011013104+1fdec59bffc1-1~exp1~20211011133507.6";
1192 assert_cmp!(a, b, Less);
1193 }
1194
1195 #[test]
1196 fn test_drop_leading_zeroes() {
1197 use super::drop_leading_zeroes;
1198
1199 assert_eq!(drop_leading_zeroes("1.0"), "1.0");
1201 assert_eq!(drop_leading_zeroes("001.0"), "1.0");
1202 assert_eq!(drop_leading_zeroes("000.1"), "0.1");
1203
1204 assert_eq!(drop_leading_zeroes("0"), "0");
1206 assert_eq!(drop_leading_zeroes("00"), "0");
1207 assert_eq!(drop_leading_zeroes("0a"), "0a");
1208 assert_eq!(drop_leading_zeroes("01a"), "1a");
1209
1210 assert_eq!(drop_leading_zeroes("a"), "a");
1212
1213 assert_eq!(drop_leading_zeroes(""), "");
1215 }
1216
1217 #[test]
1218 fn test_version_cmp_part_edge_cases() {
1219 assert_eq!(version_cmp_part("", ""), Ordering::Equal);
1223 assert_eq!(version_cmp_part("", "1"), Ordering::Less);
1224 assert_eq!(version_cmp_part("1", ""), Ordering::Greater);
1225
1226 assert_eq!(version_cmp_part("1", "1"), Ordering::Equal);
1228 assert_eq!(version_cmp_part("01", "1"), Ordering::Equal);
1229
1230 let long_a = "123456789012345678901234567890";
1232 let long_b = "123456789012345678901234567891";
1233 assert_eq!(version_cmp_part(long_a, long_b), Ordering::Less);
1234
1235 assert_eq!(version_cmp_part("1a2", "1a3"), Ordering::Less);
1237 assert_eq!(version_cmp_part("1a2", "1b1"), Ordering::Less);
1238
1239 assert_eq!(version_cmp_part("1~", "1"), Ordering::Less);
1241 assert_eq!(version_cmp_part("~1", "1"), Ordering::Less);
1242 }
1243
1244 #[test]
1245 fn test_canonicalize_edge_cases() {
1246 let v1 = Version {
1250 epoch: Some(0),
1251 upstream_version: "1.0".to_string(),
1252 debian_revision: None,
1253 };
1254 let canonical = v1.canonicalize();
1255 assert_eq!(canonical.epoch, None);
1256
1257 let v2 = Version {
1259 epoch: None,
1260 upstream_version: "1.0".to_string(),
1261 debian_revision: Some("000".to_string()),
1262 };
1263 let canonical2 = v2.canonicalize();
1264 assert_eq!(canonical2.debian_revision, None);
1265
1266 let v3 = Version {
1268 epoch: None,
1269 upstream_version: "1.0".to_string(),
1270 debian_revision: Some("001".to_string()),
1271 };
1272 let canonical3 = v3.canonicalize();
1273 assert_eq!(canonical3.debian_revision, Some("1".to_string()));
1274
1275 let v4 = Version {
1277 epoch: None,
1278 upstream_version: "001.0".to_string(),
1279 debian_revision: None,
1280 };
1281 let canonical4 = v4.canonicalize();
1282 assert_eq!(canonical4.upstream_version, "1.0");
1283
1284 let v5 = Version {
1286 epoch: Some(1),
1287 upstream_version: "1.0".to_string(),
1288 debian_revision: Some("1".to_string()),
1289 };
1290 let canonical5 = v5.canonicalize();
1291 assert_eq!(canonical5.upstream_version, "1.0");
1292 assert_eq!(canonical5.debian_revision, Some("1".to_string()));
1293 }
1294
1295 #[test]
1296 fn test_partial_eq_false() {
1297 assert!("1.0-1"
1299 .parse::<Version>()
1300 .unwrap()
1301 .ne(&"1.0-2".parse::<Version>().unwrap()));
1302
1303 assert!("1.0-1"
1304 .parse::<Version>()
1305 .unwrap()
1306 .ne(&"2.0-1".parse::<Version>().unwrap()));
1307
1308 assert!("1:1.0-1"
1309 .parse::<Version>()
1310 .unwrap()
1311 .ne(&"2:1.0-1".parse::<Version>().unwrap()));
1312 }
1313
1314 #[test]
1315 fn test_non_digit_cmp_edge_cases() {
1316 use super::non_digit_cmp;
1317
1318 assert_eq!(non_digit_cmp("~", "a"), Ordering::Less);
1320 assert_eq!(non_digit_cmp("~", "A"), Ordering::Less);
1321 assert_eq!(non_digit_cmp("~", "!"), Ordering::Less);
1322
1323 assert_eq!(non_digit_cmp("!", "@"), Ordering::Less);
1325 assert_eq!(non_digit_cmp("@", "A"), Ordering::Greater);
1326 assert_eq!(non_digit_cmp("Z", "["), Ordering::Less);
1327
1328 assert_eq!(non_digit_cmp("", ""), Ordering::Equal);
1330 assert_eq!(non_digit_cmp("", "a"), Ordering::Less);
1331 assert_eq!(non_digit_cmp("a", ""), Ordering::Greater);
1332 }
1333
1334 #[test]
1335 fn test_bump_nmu() {
1336 assert_eq!(
1338 "1.0+nmu1".parse::<Version>().unwrap(),
1339 "1.0".parse::<Version>().unwrap().bump_nmu()
1340 );
1341 assert_eq!(
1342 "1.0+nmu2".parse::<Version>().unwrap(),
1343 "1.0+nmu1".parse::<Version>().unwrap().bump_nmu()
1344 );
1345
1346 assert_eq!(
1348 "1.0-1.1".parse::<Version>().unwrap(),
1349 "1.0-1".parse::<Version>().unwrap().bump_nmu()
1350 );
1351 assert_eq!(
1352 "1.0-2.1".parse::<Version>().unwrap(),
1353 "1.0-2".parse::<Version>().unwrap().bump_nmu()
1354 );
1355
1356 assert_eq!(
1358 "1.0-1.2".parse::<Version>().unwrap(),
1359 "1.0-1.1".parse::<Version>().unwrap().bump_nmu()
1360 );
1361 assert_eq!(
1362 "1.0-2.2".parse::<Version>().unwrap(),
1363 "1.0-2.1".parse::<Version>().unwrap().bump_nmu()
1364 );
1365
1366 assert_eq!(
1368 "2:1.0-1.1".parse::<Version>().unwrap(),
1369 "2:1.0-1".parse::<Version>().unwrap().bump_nmu()
1370 );
1371
1372 assert_eq!(
1375 "1.0-1ubuntu1+nmu1".parse::<Version>().unwrap(),
1376 "1.0-1ubuntu1".parse::<Version>().unwrap().bump_nmu()
1377 );
1378 assert_eq!(
1379 "1.0-1ubuntu1+nmu2".parse::<Version>().unwrap(),
1380 "1.0-1ubuntu1+nmu1".parse::<Version>().unwrap().bump_nmu()
1381 );
1382
1383 assert!("1.0".parse::<Version>().unwrap().bump_nmu().is_nmu());
1386 assert!("1.0-1".parse::<Version>().unwrap().bump_nmu().is_nmu());
1387 assert!("1.0-1.1".parse::<Version>().unwrap().bump_nmu().is_nmu());
1388 assert!("1.0-1ubuntu1"
1389 .parse::<Version>()
1390 .unwrap()
1391 .bump_nmu()
1392 .is_nmu());
1393 }
1394
1395 #[test]
1396 fn test_parse_lenient() {
1397 let v = Version::parse_lenient("2.0.37+cvs.JCW_PRE2_2037-1").unwrap();
1399 assert_eq!(v.epoch, None);
1400 assert_eq!(v.upstream_version, "2.0.37+cvs.JCW_PRE2_2037");
1401 assert_eq!(v.debian_revision, Some("1".to_string()));
1402
1403 let v2 = Version::parse_lenient("1:3.5_beta-2").unwrap();
1405 assert_eq!(v2.epoch, Some(1));
1406 assert_eq!(v2.upstream_version, "3.5_beta");
1407 assert_eq!(v2.debian_revision, Some("2".to_string()));
1408
1409 let v3 = Version::parse_lenient("1.0-ubuntu_1").unwrap();
1411 assert_eq!(v3.epoch, None);
1412 assert_eq!(v3.upstream_version, "1.0");
1413 assert_eq!(v3.debian_revision, Some("ubuntu_1".to_string()));
1414
1415 let v4 = Version::parse_lenient("1.0-1").unwrap();
1417 assert_eq!(v4.epoch, None);
1418 assert_eq!(v4.upstream_version, "1.0");
1419 assert_eq!(v4.debian_revision, Some("1".to_string()));
1420 }
1421
1422 #[test]
1423 fn test_parse_strict_rejects_underscores() {
1424 assert!("2.0.37+cvs.JCW_PRE2_2037-1".parse::<Version>().is_err());
1426 assert!("3.5_beta-1".parse::<Version>().is_err());
1427 assert!("1.0-ubuntu_1".parse::<Version>().is_err());
1428
1429 assert!("1.0-1".parse::<Version>().is_ok());
1431 assert!("1:2.0+really1.0-1".parse::<Version>().is_ok());
1432 }
1433}