Skip to content
This repository has been archived by the owner on Oct 13, 2024. It is now read-only.

feat: Basic "Version" trait #64

Merged
merged 1 commit into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
mod maven;
mod range;
mod traits;
28 changes: 16 additions & 12 deletions src/maven/version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use winnow::combinator::{alt, repeat};
use winnow::token::take_while;
use winnow::{PResult, Parser};

use crate::traits::Version;

#[derive(Debug)]
enum RawToken<'a> {
Num(u64),
Expand Down Expand Up @@ -166,10 +168,10 @@ fn parse_raw_tokens(raw_tokens: Vec<RawToken>) -> Vec<Token> {
tokens
}

fn version_tokens(input: &mut &'_ str) -> PResult<Version> {
fn version_tokens(input: &mut &'_ str) -> PResult<MavenVersion> {
raw_tokens
.map(parse_raw_tokens)
.map(|tokens| Version { tokens })
.map(|tokens| MavenVersion { tokens })
.parse_next(input)
}

Expand Down Expand Up @@ -227,11 +229,11 @@ fn cmp_tokens(left: &Token, right: &Token) -> Ordering {
}

#[derive(Debug)]
struct Version {
struct MavenVersion {
tokens: Vec<Token>,
}

impl std::str::FromStr for Version {
impl std::str::FromStr for MavenVersion {
type Err = String;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Expand All @@ -254,7 +256,7 @@ fn get_null_token(counterpart: &Token) -> Token {
}

#[inline]
fn cmp(x: &Version, y: &Version) -> Ordering {
fn cmp(x: &MavenVersion, y: &MavenVersion) -> Ordering {
let left_len = x.tokens.len();
let right_len = y.tokens.len();
let max_len = left_len.max(right_len);
Expand All @@ -278,26 +280,28 @@ fn cmp(x: &Version, y: &Version) -> Ordering {
Ordering::Equal
}

impl PartialEq for Version {
fn eq(&self, other: &Version) -> bool {
impl PartialEq for MavenVersion {
fn eq(&self, other: &MavenVersion) -> bool {
cmp(self, other) == Ordering::Equal
}
}

impl Eq for Version {}
impl Eq for MavenVersion {}

impl Ord for Version {
fn cmp(&self, other: &Version) -> Ordering {
impl Ord for MavenVersion {
fn cmp(&self, other: &MavenVersion) -> Ordering {
cmp(self, other)
}
}

impl PartialOrd for Version {
fn partial_cmp(&self, other: &Version) -> Option<Ordering> {
impl PartialOrd for MavenVersion {
fn partial_cmp(&self, other: &MavenVersion) -> Option<Ordering> {
Some(self.cmp(other))
}
}

impl Version for MavenVersion {}

#[cfg(test)]
#[path = "version_tests.rs"]
mod version_tests;
59 changes: 33 additions & 26 deletions src/maven/version_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,8 @@ fn equivalent_tokenization(#[case] input: &str, #[case] expected: &str) {
#[test]
fn version_constructor() {
assert_eq!(
Version::from_str("1.2.3").unwrap(),
Version {
MavenVersion::from_str("1.2.3").unwrap(),
MavenVersion {
tokens: vec![
Token {
prefix: Separator::Hyphen,
Expand All @@ -164,7 +164,7 @@ fn version_constructor() {
}
);

assert!(Version::from_str("1.2.3=#!").is_err());
assert!(MavenVersion::from_str("1.2.3=#!").is_err());
}

#[rstest]
Expand Down Expand Up @@ -218,8 +218,8 @@ fn version_constructor() {
#[case("1.0.0.x", "1-x")]
#[case("1.x", "1.0.0-x")]
fn equality(#[case] left: &str, #[case] right: &str) {
let left = Version::from_str(left).unwrap();
let right = Version::from_str(right).unwrap();
let left = MavenVersion::from_str(left).unwrap();
let right = MavenVersion::from_str(right).unwrap();
assert_eq!(left.partial_cmp(&right), Some(Ordering::Equal));
assert_eq!(right.partial_cmp(&left), Some(Ordering::Equal));
}
Expand Down Expand Up @@ -248,18 +248,18 @@ fn equality(#[case] left: &str, #[case] right: &str) {
#[case("2.0.1", "2.0.1-123")]
#[case("2.0.1-xyz", "2.0.1-123")]
fn comparison(#[case] left: &str, #[case] right: &str) {
let left = Version::from_str(left).unwrap();
let right = Version::from_str(right).unwrap();
let left = MavenVersion::from_str(left).unwrap();
let right = MavenVersion::from_str(right).unwrap();
assert_eq!(left.partial_cmp(&right), Some(Ordering::Less));
assert_eq!(right.partial_cmp(&left), Some(Ordering::Greater));
}

/// @see https://issues.apache.org/jira/browse/MNG-5568
#[test]
fn mng_5568() {
let a = Version::from_str("6.1.0").unwrap();
let b = Version::from_str("6.1.0rc3").unwrap();
let c = Version::from_str("6.1H.5-beta").unwrap(); // this is the unusual version string, with 'H' in the middle
let a = MavenVersion::from_str("6.1.0").unwrap();
let b = MavenVersion::from_str("6.1.0rc3").unwrap();
let c = MavenVersion::from_str("6.1H.5-beta").unwrap(); // this is the unusual version string, with 'H' in the middle

assert_eq!(b.partial_cmp(&a), Some(Ordering::Less)); // classical
assert_eq!(b.partial_cmp(&c), Some(Ordering::Less)); // now b < c, but before MNG-5568, we had b > c
Expand All @@ -269,10 +269,10 @@ fn mng_5568() {
/// @see https://jira.apache.org/jira/browse/MNG-6572
#[test]
fn mng_6572() {
let a = Version::from_str("20190126.230843").unwrap(); // resembles a SNAPSHOT
let b = Version::from_str("1234567890.12345").unwrap(); // 10 digit number
let c = Version::from_str("123456789012345.1H.5-beta").unwrap(); // 15 digit number
let d = Version::from_str("12345678901234567890.1H.5-beta").unwrap(); // 20 digit number
let a = MavenVersion::from_str("20190126.230843").unwrap(); // resembles a SNAPSHOT
let b = MavenVersion::from_str("1234567890.12345").unwrap(); // 10 digit number
let c = MavenVersion::from_str("123456789012345.1H.5-beta").unwrap(); // 15 digit number
let d = MavenVersion::from_str("12345678901234567890.1H.5-beta").unwrap(); // 20 digit number

assert_eq!(a.partial_cmp(&b), Some(Ordering::Less));
assert_eq!(b.partial_cmp(&c), Some(Ordering::Less));
Expand Down Expand Up @@ -308,8 +308,8 @@ fn version_equal_with_leading_zeroes() {

for combination in versions.into_iter().combinations(2) {
let (left, right) = (combination[0], combination[1]);
let left = Version::from_str(left).unwrap();
let right = Version::from_str(right).unwrap();
let left = MavenVersion::from_str(left).unwrap();
let right = MavenVersion::from_str(right).unwrap();
assert_eq!(left.partial_cmp(&right), Some(Ordering::Equal));
assert_eq!(right.partial_cmp(&left), Some(Ordering::Equal));
}
Expand Down Expand Up @@ -341,8 +341,8 @@ fn test_version_zero_equal_with_leading_zeroes() {

for combination in versions.into_iter().combinations(2) {
let (left, right) = (combination[0], combination[1]);
let left = Version::from_str(left).unwrap();
let right = Version::from_str(right).unwrap();
let left = MavenVersion::from_str(left).unwrap();
let right = MavenVersion::from_str(right).unwrap();
assert_eq!(left.partial_cmp(&right), Some(Ordering::Equal));
assert_eq!(right.partial_cmp(&left), Some(Ordering::Equal));
}
Expand All @@ -351,9 +351,9 @@ fn test_version_zero_equal_with_leading_zeroes() {
/// @see https://issues.apache.org/jira/browse/MNG-6964
#[test]
fn test_mng_6964() {
let a = Version::from_str("1-0.alpha").unwrap();
let b = Version::from_str("1-0.beta").unwrap();
let c = Version::from_str("1").unwrap();
let a = MavenVersion::from_str("1-0.alpha").unwrap();
let b = MavenVersion::from_str("1-0.beta").unwrap();
let c = MavenVersion::from_str("1").unwrap();

assert_eq!(a.partial_cmp(&c), Some(Ordering::Less)); // Now a < c, but before MNG-6964 they were equal
assert_eq!(b.partial_cmp(&c), Some(Ordering::Less)); // Now b < c, but before MNG-6964 they were equal
Expand All @@ -377,16 +377,23 @@ fn test_mng_7644() {

for qual in quals {
// 1.0.0.X1 < 1.0.0-X2 for any string x
let a = Version::from_str(&format!("1.0.0.{}1", qual)).unwrap();
let b = Version::from_str(&format!("1.0.0-{}2", qual)).unwrap();
let a = MavenVersion::from_str(&format!("1.0.0.{}1", qual)).unwrap();
let b = MavenVersion::from_str(&format!("1.0.0-{}2", qual)).unwrap();
assert_eq!(a.partial_cmp(&b), Some(Ordering::Less));

// 2.0.X == 2-X == 2.0.0.X for any string x
let c = Version::from_str(&format!("2-{}", qual)).unwrap();
let d = Version::from_str(&format!("2.0.{}", qual)).unwrap();
let e = Version::from_str(&format!("2.0.0.{}", qual)).unwrap();
let c = MavenVersion::from_str(&format!("2-{}", qual)).unwrap();
let d = MavenVersion::from_str(&format!("2.0.{}", qual)).unwrap();
let e = MavenVersion::from_str(&format!("2.0.0.{}", qual)).unwrap();
assert_eq!(c.partial_cmp(&d), Some(Ordering::Equal)); // previously ordered, now equals
assert_eq!(c.partial_cmp(&e), Some(Ordering::Equal)); // previously ordered, now equals
assert_eq!(d.partial_cmp(&e), Some(Ordering::Equal)); // previously ordered, now equals
}
}

#[test]
fn test_compatible() {
let a = MavenVersion::from_str("1.0.0").unwrap();
let b = MavenVersion::from_str("2.0.0").unwrap();
assert!(a.is_compatible(&b));
}
13 changes: 13 additions & 0 deletions src/traits.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
pub trait Version: PartialEq + PartialOrd {
// For the most version schemes it's always `true`.
//
// However, if there are different "equivalence classes"
// of versions, then `is_compatible` must be implemented.
//
// Example: in Docker ecosystem, we want to distinguish
// `node:20.12.2` from `node:20.12.2-alpine` images.
// These are same versions, but are different "lines".
fn is_compatible(&self, _other: &Self) -> bool {
true
}
}