@@ -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