Skip to content

Commit 028832f

Browse files
committed
Implement ReleaseTracks
1 parent f1b6235 commit 028832f

File tree

1 file changed

+190
-0
lines changed

1 file changed

+190
-0
lines changed

src/controllers/krate/versions.rs

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

0 commit comments

Comments
 (0)