Skip to content

Commit ba72798

Browse files
authored
Merge pull request #9723 from Turbo87/default-versions-ord
models/default_version: Implement `Ord` for `Version`
2 parents 82c5403 + 9f9992e commit ba72798

File tree

1 file changed

+74
-21
lines changed

1 file changed

+74
-21
lines changed

src/models/default_versions.rs

Lines changed: 74 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ use diesel::prelude::*;
88
/// This struct is used to load all versions of a crate from the database,
99
/// without loading all the additional data unnecessary for default version
1010
/// resolution.
11-
#[derive(Clone, Debug, Queryable, Selectable)]
11+
///
12+
/// It implements [Ord] in a way that sorts versions by the criteria specified
13+
/// in the [update_default_version] function documentation. The default version
14+
/// will be the "maximum" element in a sorted list of versions.
15+
#[derive(Clone, Debug, PartialEq, Eq, Queryable, Selectable)]
1216
#[diesel(table_name = versions)]
1317
#[diesel(check_for_backend(diesel::pg::Pg))]
1418
struct Version {
@@ -23,6 +27,22 @@ impl Version {
2327
fn is_prerelease(&self) -> bool {
2428
!self.num.pre.is_empty()
2529
}
30+
31+
fn ord_tuple(&self) -> (bool, bool, &semver::Version, i32) {
32+
(!self.yanked, !self.is_prerelease(), &self.num, self.id)
33+
}
34+
}
35+
36+
impl PartialOrd for Version {
37+
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
38+
Some(self.cmp(other))
39+
}
40+
}
41+
42+
impl Ord for Version {
43+
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
44+
self.ord_tuple().cmp(&other.ord_tuple())
45+
}
2646
}
2747

2848
/// Updates the `default_versions` table entry for the specified crate.
@@ -88,6 +108,8 @@ pub fn verify_default_version(crate_id: i32, conn: &mut impl Conn) -> QueryResul
88108
}
89109

90110
fn calculate_default_version(crate_id: i32, conn: &mut impl Conn) -> QueryResult<Version> {
111+
use diesel::result::Error::NotFound;
112+
91113
debug!("Loading all versions for the crate…");
92114
let versions = versions::table
93115
.filter(versions::crate_id.eq(crate_id))
@@ -96,26 +118,16 @@ fn calculate_default_version(crate_id: i32, conn: &mut impl Conn) -> QueryResult
96118

97119
debug!("Found {} versions", versions.len());
98120

99-
find_default_version(&versions)
100-
.cloned()
101-
.ok_or(diesel::result::Error::NotFound)
102-
}
103-
104-
fn find_default_version(versions: &[Version]) -> Option<&Version> {
105-
highest(versions, |v| !v.is_prerelease() && !v.yanked)
106-
.or_else(|| highest(versions, |v| !v.yanked))
107-
.or_else(|| highest(versions, |_| true))
108-
}
109-
110-
fn highest(versions: &[Version], filter: impl FnMut(&&Version) -> bool) -> Option<&Version> {
111-
versions.iter().filter(filter).max_by_key(|v| &v.num)
121+
versions.into_iter().max().ok_or(NotFound)
112122
}
113123

114124
#[cfg(test)]
115125
mod tests {
116126
use super::*;
117127
use crate::schema::crates;
118128
use crate::test_util::test_db_connection;
129+
use insta::assert_snapshot;
130+
use std::fmt::Write;
119131

120132
fn v(num: &str, yanked: bool) -> Version {
121133
let num = semver::Version::parse(num).unwrap();
@@ -124,14 +136,10 @@ mod tests {
124136

125137
#[test]
126138
fn test_find_default_version() {
127-
let check = |versions, expected| {
128-
let default_version = assert_some!(find_default_version(versions));
139+
fn check(versions: &[Version], expected: &str) {
140+
let default_version = assert_some!(versions.iter().max());
129141
assert_eq!(default_version.num.to_string(), expected);
130-
};
131-
132-
// No versions
133-
let versions = vec![];
134-
assert_none!(find_default_version(&versions));
142+
}
135143

136144
// Only a single version
137145
let versions = vec![v("1.0.0", false)];
@@ -179,6 +187,51 @@ mod tests {
179187
check(&versions, "1.0.0-beta.3");
180188
}
181189

190+
#[test]
191+
fn test_ord() {
192+
let mut versions = vec![
193+
v("1.0.0", false),
194+
v("1.0.0-beta.1", false),
195+
v("1.0.0-beta.2", false),
196+
v("1.0.0-beta.3", false),
197+
v("1.0.1", true),
198+
v("1.0.2", false),
199+
v("1.1.0", false),
200+
v("1.1.1-beta.1", true),
201+
v("1.1.1", true),
202+
v("1.0.3", false),
203+
v("2.0.0-beta.1", false),
204+
];
205+
206+
versions.sort();
207+
208+
assert_snapshot!(format_versions(&versions), @r#"
209+
1.1.1-beta.1 (yanked)
210+
1.0.1 (yanked)
211+
1.1.1 (yanked)
212+
1.0.0-beta.1
213+
1.0.0-beta.2
214+
1.0.0-beta.3
215+
2.0.0-beta.1
216+
1.0.0
217+
1.0.2
218+
1.0.3
219+
1.1.0
220+
"#);
221+
}
222+
223+
fn format_versions(versions: &[Version]) -> String {
224+
let mut buf = String::with_capacity(versions.len() * 20);
225+
for v in versions {
226+
write!(buf, "{}", v.num).unwrap();
227+
if v.yanked {
228+
buf.push_str(" (yanked)");
229+
}
230+
buf.push('\n');
231+
}
232+
buf
233+
}
234+
182235
fn create_crate(name: &str, conn: &mut impl Conn) -> i32 {
183236
diesel::insert_into(crates::table)
184237
.values(crates::name.eq(name))

0 commit comments

Comments
 (0)