Skip to content

Commit e5ed924

Browse files
committed
controllers/krate/versions: Implement release_tracks meta for pagination, sorted by semver
1 parent 609b713 commit e5ed924

File tree

2 files changed

+39
-12
lines changed

2 files changed

+39
-12
lines changed

src/controllers/krate/versions.rs

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ fn list_by_semver(
157157
) -> AppResult<PaginatedVersionsAndPublishers> {
158158
use seek::*;
159159

160-
let (data, total) = if let Some(options) = options {
160+
let (data, total, release_tracks) = if let Some(options) = options {
161161
// Since versions will only increase in the future and both sorting and pagination need to
162162
// happen on the app server, implementing it with fetching only the data needed for sorting
163163
// and pagination, then making another query for the data to respond with, would minimize
@@ -168,18 +168,26 @@ fn list_by_semver(
168168
let mut sorted_versions = IndexMap::new();
169169
for result in versions::table
170170
.filter(versions::crate_id.eq(crate_id))
171-
.select((versions::id, versions::num))
172-
.load_iter::<(i32, String), DefaultLoadingMode>(conn)?
171+
.select((versions::id, versions::num, versions::yanked))
172+
.load_iter::<(i32, String, bool), DefaultLoadingMode>(conn)?
173173
{
174-
let (id, num) = result?;
175-
sorted_versions.insert(id, (num, None));
174+
let (id, num, yanked) = result?;
175+
let semver = semver::Version::parse(&num).ok();
176+
sorted_versions.insert(id, (semver, yanked, None));
176177
}
177-
sorted_versions.sort_by_cached_key(|_, (num, _)| Reverse(semver::Version::parse(num).ok()));
178+
sorted_versions
179+
.sort_unstable_by(|_, (semver_a, _, _), _, (semver_b, _, _)| semver_b.cmp(semver_a));
178180

179181
assert!(
180182
!matches!(&options.page, Page::Numeric(_)),
181183
"?page= is not supported"
182184
);
185+
let release_tracks = Some(ReleaseTracks::from_sorted_semver_iter(
186+
sorted_versions
187+
.values()
188+
.filter(|(_, yanked, _)| !yanked)
189+
.filter_map(|(semver, _, _)| semver.as_ref()),
190+
));
183191
let mut idx = Some(0);
184192
if let Some(SeekPayload::Semver(Semver { id })) = Seek::Semver.after(&options.page)? {
185193
idx = sorted_versions
@@ -201,19 +209,24 @@ fn list_by_semver(
201209
.load_iter::<(Version, Option<User>), DefaultLoadingMode>(conn)?
202210
{
203211
let row = result?;
204-
sorted_versions.insert(row.0.id, (row.0.num.to_owned(), Some(row)));
212+
// The versions are already sorted, and we only need to enrich the fetched rows into them.
213+
// Therefore, other values can now be safely ignored.
214+
sorted_versions
215+
.entry(row.0.id)
216+
.and_modify(|entry| *entry = (None, false, Some(row)));
205217
}
206218

207219
let len = sorted_versions.len();
208220
(
209221
sorted_versions
210222
.into_values()
211-
.filter_map(|(_, v)| v)
223+
.filter_map(|(_, _, v)| v)
212224
.collect(),
213225
len,
226+
release_tracks,
214227
)
215228
} else {
216-
(vec![], 0)
229+
(vec![], 0, release_tracks)
217230
}
218231
} else {
219232
let mut data: Vec<(Version, Option<User>)> = versions::table
@@ -223,7 +236,7 @@ fn list_by_semver(
223236
.load(conn)?;
224237
data.sort_by_cached_key(|(version, _)| Reverse(semver::Version::parse(&version.num).ok()));
225238
let total = data.len();
226-
(data, total)
239+
(data, total, None)
227240
};
228241

229242
let mut next_page = None;
@@ -237,7 +250,7 @@ fn list_by_semver(
237250
meta: ResponseMeta {
238251
total: total as i64,
239252
next_page,
240-
release_tracks: None,
253+
release_tracks,
241254
},
242255
})
243256
}

src/tests/routes/crates/versions/list.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ async fn test_sorting() {
107107
for (json, expect) in resp.iter().zip(expects) {
108108
assert_eq!(json.versions[0].num, expect);
109109
assert_eq!(json.meta.total as usize, expects.len());
110+
assert_eq!(
111+
json.meta.release_tracks,
112+
Some(json!({"1": {"highest": "1.0.0"}}))
113+
);
110114
}
111115
assert_eq!(calls as usize, expects.len() + 1);
112116

@@ -131,7 +135,7 @@ async fn test_seek_based_pagination_semver_sorting() {
131135
let user = user.as_model();
132136
app.db(|conn| {
133137
CrateBuilder::new("foo_versions", user.id)
134-
.version("0.5.1")
138+
.version(VersionBuilder::new("0.5.1").yanked(true))
135139
.version(VersionBuilder::new("1.0.0").rust_version("1.64"))
136140
.version("0.5.0")
137141
.expect_build(conn);
@@ -147,6 +151,10 @@ async fn test_seek_based_pagination_semver_sorting() {
147151

148152
let url = "/api/v1/crates/foo_versions/versions";
149153
let expects = ["1.0.0", "0.5.1", "0.5.0"];
154+
let release_tracks = Some(json!({
155+
"1": {"highest": "1.0.0"},
156+
"0.5": {"highest": "0.5.0"}
157+
}));
150158

151159
// per_page larger than the number of versions
152160
let json: VersionList = anon
@@ -155,13 +163,15 @@ async fn test_seek_based_pagination_semver_sorting() {
155163
.good();
156164
assert_eq!(nums(&json.versions), expects);
157165
assert_eq!(json.meta.total as usize, expects.len());
166+
assert_eq!(json.meta.release_tracks, release_tracks);
158167

159168
let json: VersionList = anon
160169
.get_with_query(url, "per_page=1&sort=semver")
161170
.await
162171
.good();
163172
assert_eq!(nums(&json.versions), expects[0..1]);
164173
assert_eq!(json.meta.total as usize, expects.len());
174+
assert_eq!(json.meta.release_tracks, release_tracks);
165175

166176
let seek = json
167177
.meta
@@ -178,6 +188,7 @@ async fn test_seek_based_pagination_semver_sorting() {
178188
assert_eq!(nums(&json.versions), expects[1..]);
179189
assert!(json.meta.next_page.is_none());
180190
assert_eq!(json.meta.total as usize, expects.len());
191+
assert_eq!(json.meta.release_tracks, release_tracks);
181192

182193
// per_page euqal to the number of remain versions
183194
let json: VersionList = anon
@@ -187,6 +198,7 @@ async fn test_seek_based_pagination_semver_sorting() {
187198
assert_eq!(nums(&json.versions), expects[1..]);
188199
assert!(json.meta.next_page.is_some());
189200
assert_eq!(json.meta.total as usize, expects.len());
201+
assert_eq!(json.meta.release_tracks, release_tracks);
190202

191203
// A decodable seek value, MTAwCg (100), but doesn't actually exist
192204
let json: VersionList = anon
@@ -196,6 +208,7 @@ async fn test_seek_based_pagination_semver_sorting() {
196208
assert_eq!(json.versions.len(), 0);
197209
assert!(json.meta.next_page.is_none());
198210
assert_eq!(json.meta.total, 0);
211+
assert_eq!(json.meta.release_tracks, release_tracks);
199212
}
200213

201214
#[tokio::test(flavor = "multi_thread")]
@@ -242,6 +255,7 @@ pub struct VersionList {
242255
pub struct ResponseMeta {
243256
pub total: i64,
244257
pub next_page: Option<String>,
258+
pub release_tracks: Option<serde_json::Value>,
245259
}
246260

247261
fn nums(versions: &[EncodableVersion]) -> Vec<String> {

0 commit comments

Comments
 (0)