Skip to content

Commit 2a3f187

Browse files
committed
models/default_version: Implement Ord for Version
This allows us to simplify the `find_default_version()` version fn, which now only needs to iterate the slice once instead of up to three times, and it makes it possible to directly compare two `Version` instances too.
1 parent 82c5403 commit 2a3f187

File tree

1 file changed

+69
-8
lines changed

1 file changed

+69
-8
lines changed

src/models/default_versions.rs

Lines changed: 69 additions & 8 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.
@@ -102,20 +122,16 @@ fn calculate_default_version(crate_id: i32, conn: &mut impl Conn) -> QueryResult
102122
}
103123

104124
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)
125+
versions.iter().max()
112126
}
113127

114128
#[cfg(test)]
115129
mod tests {
116130
use super::*;
117131
use crate::schema::crates;
118132
use crate::test_util::test_db_connection;
133+
use insta::assert_snapshot;
134+
use std::fmt::Write;
119135

120136
fn v(num: &str, yanked: bool) -> Version {
121137
let num = semver::Version::parse(num).unwrap();
@@ -179,6 +195,51 @@ mod tests {
179195
check(&versions, "1.0.0-beta.3");
180196
}
181197

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

0 commit comments

Comments
 (0)