diff --git a/src/models/krate.rs b/src/models/krate.rs index 5e5c975529b..e457251fa7b 100644 --- a/src/models/krate.rs +++ b/src/models/krate.rs @@ -357,6 +357,8 @@ impl Crate { .map(|v| v.to_string()) .unwrap_or_else(|| "0.0.0".to_string()); + let max_stable_version = top_versions.highest_stable.as_ref().map(|v| v.to_string()); + EncodableCrate { id: name.clone(), name: name.clone(), @@ -370,6 +372,7 @@ impl Crate { badges, max_version, newest_version, + max_stable_version, documentation, homepage, exact_match, diff --git a/src/models/version.rs b/src/models/version.rs index c9c906a9829..39d3c2fe425 100644 --- a/src/models/version.rs +++ b/src/models/version.rs @@ -43,6 +43,8 @@ pub struct NewVersion { pub struct TopVersions { /// The "highest" version in terms of semver pub highest: Option, + /// The "highest" non-prerelease version + pub highest_stable: Option, /// The "newest" version in terms of publishing date pub newest: Option, } @@ -61,9 +63,19 @@ impl TopVersions { T: Clone + IntoIterator, { let newest = pairs.clone().into_iter().max().map(|(_, v)| v); - let highest = pairs.into_iter().map(|(_, v)| v).max(); + let highest = pairs.clone().into_iter().map(|(_, v)| v).max(); - Self { newest, highest } + let highest_stable = pairs + .into_iter() + .map(|(_, v)| v) + .filter(|v| !v.is_prerelease()) + .max(); + + Self { + newest, + highest, + highest_stable, + } } } @@ -262,6 +274,7 @@ mod tests { TopVersions::from_date_version_pairs(versions), TopVersions { highest: None, + highest_stable: None, newest: None, } ); @@ -274,23 +287,39 @@ mod tests { TopVersions::from_date_version_pairs(versions), TopVersions { highest: Some(version("1.0.0")), + highest_stable: Some(version("1.0.0")), newest: Some(version("1.0.0")), } ); } + #[test] + fn top_versions_prerelease() { + let versions = vec![(date("2020-12-03T12:34:56"), version("1.0.0-beta.5"))]; + assert_eq!( + TopVersions::from_date_version_pairs(versions), + TopVersions { + highest: Some(version("1.0.0-beta.5")), + highest_stable: None, + newest: Some(version("1.0.0-beta.5")), + } + ); + } + #[test] fn top_versions_multiple() { let versions = vec![ (date("2018-12-03T12:34:56"), version("1.0.0")), (date("2019-12-03T12:34:56"), version("2.0.0-alpha.1")), (date("2020-12-03T12:34:56"), version("1.1.0")), + (date("2020-12-31T12:34:56"), version("1.0.4")), ]; assert_eq!( TopVersions::from_date_version_pairs(versions), TopVersions { highest: Some(version("2.0.0-alpha.1")), - newest: Some(version("1.1.0")), + highest_stable: Some(version("1.1.0")), + newest: Some(version("1.0.4")), } ); } diff --git a/src/tests/krate/search.rs b/src/tests/krate/search.rs index ec7e36ce208..7564ebc56d7 100644 --- a/src/tests/krate/search.rs +++ b/src/tests/krate/search.rs @@ -508,6 +508,27 @@ fn yanked_versions_are_not_considered_for_max_version() { assert_eq!(json.crates[0].max_version, "1.0.0"); } +#[test] +fn max_stable_version() { + let (app, anon, user) = TestApp::init().with_user(); + let user = user.as_model(); + + app.db(|conn| { + CrateBuilder::new("foo", user.id) + .description("foo") + .version("0.3.0") + .version("1.0.0") + .version(VersionBuilder::new("1.1.0").yanked(true)) + .version("2.0.0-beta.1") + .version("0.3.1") + .expect_build(conn); + }); + + let json = anon.search("q=foo"); + assert_eq!(json.meta.total, 1); + assert_eq!(json.crates[0].max_stable_version, Some("1.0.0".to_string())); +} + /* Given two crates, one with downloads less than 90 days ago, the other with all downloads greater than 90 days ago, check that the order returned is by recent downloads, descending. Check diff --git a/src/views.rs b/src/views.rs index 636e0a96095..6a80dd27ee7 100644 --- a/src/views.rs +++ b/src/views.rs @@ -156,6 +156,7 @@ pub struct EncodableCrate { // NOTE: Used by shields.io, altering `max_version` requires a PR with shields.io pub max_version: String, pub newest_version: String, // Most recently updated version, which may not be max + pub max_stable_version: Option, pub description: Option, pub homepage: Option, pub documentation: Option, @@ -510,6 +511,7 @@ mod tests { recent_downloads: None, max_version: "".to_string(), newest_version: "".to_string(), + max_stable_version: None, description: None, homepage: None, documentation: None,