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