Skip to content

Commit 609b713

Browse files
committed
Implement ReleaseTracks
This will add `release_tracks` information to the meta field for paginated versions. This is necessary for pagination because it only loads a portion of versions per page, while `release_tracks` may require all sorted versions to determine their values. Therefore, we also need to calculate these on the server side.
1 parent f1b6235 commit 609b713

File tree

1 file changed

+182
-1
lines changed

1 file changed

+182
-1
lines changed

src/controllers/krate/versions.rs

Lines changed: 182 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,11 @@ fn list_by_date(
133133

134134
Ok(PaginatedVersionsAndPublishers {
135135
data,
136-
meta: ResponseMeta { total, next_page },
136+
meta: ResponseMeta {
137+
total,
138+
next_page,
139+
release_tracks: None,
140+
},
137141
})
138142
}
139143

@@ -233,6 +237,7 @@ fn list_by_semver(
233237
meta: ResponseMeta {
234238
total: total as i64,
235239
next_page,
240+
release_tracks: None,
236241
},
237242
})
238243
}
@@ -302,4 +307,180 @@ struct PaginatedVersionsAndPublishers {
302307
struct ResponseMeta {
303308
total: i64,
304309
next_page: Option<String>,
310+
#[serde(skip_serializing_if = "Option::is_none")]
311+
release_tracks: Option<ReleaseTracks>,
312+
}
313+
314+
#[derive(Debug, Eq, PartialEq, Serialize)]
315+
struct ReleaseTracks(IndexMap<ReleaseSlug, HighestSemver>);
316+
317+
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
318+
enum ReleaseSlug {
319+
Minor(u64),
320+
Major(u64),
321+
}
322+
323+
#[derive(Debug, Clone, Eq, PartialEq, Serialize)]
324+
struct HighestSemver {
325+
highest: semver::Version,
326+
}
327+
328+
impl ReleaseTracks {
329+
// Return the release tracks based on a sorted semver versions iterator (in descending order).
330+
// **Remember to** filter out yanked versions manually before calling this function.
331+
pub fn from_sorted_semver_iter<'a, I>(versions: I) -> Self
332+
where
333+
I: Iterator<Item = &'a semver::Version>,
334+
{
335+
let mut map = IndexMap::new();
336+
for num in versions.filter(|num| num.pre.is_empty()) {
337+
let key = ReleaseSlug::from_semver(num);
338+
let prev = map.last();
339+
if prev.filter(|&(k, _)| *k == key).is_none() {
340+
map.insert(
341+
key,
342+
HighestSemver {
343+
highest: num.clone(),
344+
},
345+
);
346+
}
347+
}
348+
349+
Self(map)
350+
}
351+
}
352+
353+
impl ReleaseSlug {
354+
pub fn from_semver(version: &semver::Version) -> Self {
355+
if version.major == 0 {
356+
Self::Minor(version.minor)
357+
} else {
358+
Self::Major(version.major)
359+
}
360+
}
361+
}
362+
363+
impl std::fmt::Display for ReleaseSlug {
364+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
365+
match self {
366+
Self::Minor(minor) => write!(f, "0.{minor}"),
367+
Self::Major(major) => write!(f, "{major}"),
368+
}
369+
}
370+
}
371+
372+
impl serde::Serialize for ReleaseSlug {
373+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
374+
where
375+
S: serde::Serializer,
376+
Self: std::fmt::Display,
377+
{
378+
serializer.collect_str(self)
379+
}
380+
}
381+
382+
#[cfg(test)]
383+
mod tests {
384+
use super::{HighestSemver, ReleaseSlug, ReleaseTracks};
385+
use indexmap::IndexMap;
386+
387+
#[track_caller]
388+
fn version(str: &str) -> semver::Version {
389+
semver::Version::parse(str).unwrap()
390+
}
391+
392+
#[test]
393+
fn release_tracks_empty() {
394+
let versions = [];
395+
assert_eq!(
396+
ReleaseTracks::from_sorted_semver_iter(versions.into_iter()),
397+
ReleaseTracks(IndexMap::new())
398+
);
399+
}
400+
401+
#[test]
402+
fn release_tracks_prerelease() {
403+
let versions = [version("1.0.0-beta.5")];
404+
assert_eq!(
405+
ReleaseTracks::from_sorted_semver_iter(versions.iter()),
406+
ReleaseTracks(IndexMap::new())
407+
);
408+
}
409+
410+
#[test]
411+
fn release_tracks_multiple() {
412+
let versions = [
413+
"100.1.1",
414+
"100.1.0",
415+
"1.3.5",
416+
"1.2.5",
417+
"1.1.5",
418+
"0.4.0-rc.1",
419+
"0.3.23",
420+
"0.3.22",
421+
"0.3.21-pre.0",
422+
"0.3.20",
423+
"0.3.3",
424+
"0.3.2",
425+
"0.3.1",
426+
"0.3.0",
427+
"0.2.1",
428+
"0.2.0",
429+
"0.1.2",
430+
"0.1.1",
431+
]
432+
.map(version);
433+
434+
let release_tracks = ReleaseTracks::from_sorted_semver_iter(versions.iter());
435+
assert_eq!(
436+
release_tracks,
437+
ReleaseTracks(IndexMap::from([
438+
(
439+
ReleaseSlug::Major(100),
440+
HighestSemver {
441+
highest: version("100.1.1")
442+
}
443+
),
444+
(
445+
ReleaseSlug::Major(1),
446+
HighestSemver {
447+
highest: version("1.3.5")
448+
}
449+
),
450+
(
451+
ReleaseSlug::Minor(3),
452+
HighestSemver {
453+
highest: version("0.3.23")
454+
}
455+
),
456+
(
457+
ReleaseSlug::Minor(2),
458+
HighestSemver {
459+
highest: version("0.2.1")
460+
}
461+
),
462+
(
463+
ReleaseSlug::Minor(1),
464+
HighestSemver {
465+
highest: version("0.1.2")
466+
}
467+
),
468+
]))
469+
);
470+
471+
let json = serde_json::from_str::<serde_json::Value>(
472+
&serde_json::to_string(&release_tracks).unwrap(),
473+
)
474+
.unwrap();
475+
assert_eq!(
476+
json,
477+
json!({
478+
"100": { "highest": "100.1.1" },
479+
"1": { "highest": "1.3.5" },
480+
"0.3": { "highest": "0.3.23" },
481+
"0.2": { "highest": "0.2.1" },
482+
"0.1": { "highest": "0.1.2" }
483+
})
484+
);
485+
}
305486
}

0 commit comments

Comments
 (0)