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