11use super :: { markdown, match_version, MatchSemver , MetaData } ;
22use crate :: utils:: { get_correct_docsrs_style_file, report_error, spawn_blocking} ;
3+ use crate :: web:: rustdoc:: RustdocHtmlParams ;
4+ use crate :: web:: { axum_cached_redirect, match_version_axum} ;
35use crate :: {
46 db:: Pool ,
57 impl_axum_webpage,
@@ -15,6 +17,7 @@ use crate::{
1517use anyhow:: { Context , Result } ;
1618use axum:: {
1719 extract:: { Extension , Path } ,
20+ http:: Uri ,
1821 response:: { IntoResponse , Response as AxumResponse } ,
1922} ;
2023use chrono:: { DateTime , Utc } ;
@@ -24,6 +27,7 @@ use serde::Deserialize;
2427use serde:: { ser:: Serializer , Serialize } ;
2528use serde_json:: Value ;
2629use std:: sync:: Arc ;
30+ use tracing:: { instrument, trace} ;
2731
2832// TODO: Add target name and versions
2933
@@ -475,6 +479,167 @@ pub(crate) async fn get_all_releases(
475479 Ok ( res. into_response ( ) )
476480}
477481
482+ #[ derive( Debug , Clone , PartialEq , Serialize ) ]
483+ struct ShortMetadata {
484+ name : String ,
485+ version_or_latest : String ,
486+ doc_targets : Vec < String > ,
487+ }
488+
489+ #[ derive( Debug , Clone , PartialEq , Serialize ) ]
490+ struct PlatformList {
491+ metadata : ShortMetadata ,
492+ inner_path : String ,
493+ use_direct_platform_links : bool ,
494+ current_target : String ,
495+ }
496+
497+ impl_axum_webpage ! {
498+ PlatformList = "rustdoc/platforms.html" ,
499+ cpu_intensive_rendering = true ,
500+ }
501+
502+ #[ tracing:: instrument]
503+ pub ( crate ) async fn get_all_platforms (
504+ Path ( params) : Path < RustdocHtmlParams > ,
505+ Extension ( pool) : Extension < Pool > ,
506+ uri : Uri ,
507+ ) -> AxumResult < AxumResponse > {
508+ // since we directly use the Uri-path and not the extracted params from the router,
509+ // we have to percent-decode the string here.
510+ let original_path = percent_encoding:: percent_decode ( uri. path ( ) . as_bytes ( ) )
511+ . decode_utf8 ( )
512+ . map_err ( |_| AxumNope :: BadRequest ) ?;
513+ let mut req_path: Vec < & str > = original_path. split ( '/' ) . collect ( ) ;
514+
515+ let release_found = match_version_axum ( & pool, & params. name , Some ( & params. version ) ) . await ?;
516+ trace ! ( ?release_found, "found release" ) ;
517+
518+ // Remove the empty start, "releases", the name and the version from the path
519+ req_path. drain ( ..4 ) . for_each ( drop) ;
520+
521+ // Convenience function to allow for easy redirection
522+ #[ instrument]
523+ fn redirect (
524+ name : & str ,
525+ vers : & str ,
526+ path : & [ & str ] ,
527+ cache_policy : CachePolicy ,
528+ ) -> AxumResult < AxumResponse > {
529+ trace ! ( "redirect" ) ;
530+ // Format and parse the redirect url
531+ Ok ( axum_cached_redirect (
532+ encode_url_path ( & format ! ( "/platforms/{}/{}/{}" , name, vers, path. join( "/" ) ) ) ,
533+ cache_policy,
534+ ) ?
535+ . into_response ( ) )
536+ }
537+
538+ let ( version, version_or_latest) = match release_found. version {
539+ MatchSemver :: Exact ( ( version, _) ) => {
540+ // Redirect when the requested crate name isn't correct
541+ if let Some ( name) = release_found. corrected_name {
542+ return redirect ( & name, & version, & req_path, CachePolicy :: NoCaching ) ;
543+ }
544+
545+ ( version. clone ( ) , version)
546+ }
547+
548+ MatchSemver :: Latest ( ( version, _) ) => {
549+ // Redirect when the requested crate name isn't correct
550+ if let Some ( name) = release_found. corrected_name {
551+ return redirect ( & name, "latest" , & req_path, CachePolicy :: NoCaching ) ;
552+ }
553+
554+ ( version, "latest" . to_string ( ) )
555+ }
556+
557+ // Redirect when the requested version isn't correct
558+ MatchSemver :: Semver ( ( v, _) ) => {
559+ // to prevent cloudfront caching the wrong artifacts on URLs with loose semver
560+ // versions, redirect the browser to the returned version instead of loading it
561+ // immediately
562+ return redirect ( & params. name , & v, & req_path, CachePolicy :: ForeverInCdn ) ;
563+ }
564+ } ;
565+
566+ let ( name, doc_targets, releases, default_target) : ( String , Vec < String > , Vec < Release > , String ) =
567+ spawn_blocking ( {
568+ let pool = pool. clone ( ) ;
569+ move || {
570+ let mut conn = pool. get ( ) ?;
571+ let query = "
572+ SELECT
573+ crates.id,
574+ crates.name,
575+ releases.default_target,
576+ releases.doc_targets
577+ FROM releases
578+ INNER JOIN crates ON releases.crate_id = crates.id
579+ WHERE crates.name = $1 AND releases.version = $2;" ;
580+
581+ let rows = conn. query ( query, & [ & params. name , & version] ) ?;
582+
583+ let krate = if rows. is_empty ( ) {
584+ return Err ( AxumNope :: CrateNotFound . into ( ) ) ;
585+ } else {
586+ & rows[ 0 ]
587+ } ;
588+
589+ // get releases, sorted by semver
590+ let releases = releases_for_crate ( & mut * conn, krate. get ( "id" ) ) ?;
591+
592+ Ok ( (
593+ krate. get ( "name" ) ,
594+ MetaData :: parse_doc_targets ( krate. get ( "doc_targets" ) ) ,
595+ releases,
596+ krate. get ( "default_target" ) ,
597+ ) )
598+ }
599+ } )
600+ . await ?;
601+
602+ let latest_release = releases
603+ . iter ( )
604+ . find ( |release| release. version . pre . is_empty ( ) && !release. yanked )
605+ . unwrap_or ( & releases[ 0 ] ) ;
606+
607+ // The path within this crate version's rustdoc output
608+ let ( target, inner_path) = {
609+ let mut inner_path = req_path. clone ( ) ;
610+
611+ let target = if inner_path. len ( ) > 1 && doc_targets. iter ( ) . any ( |s| s == inner_path[ 0 ] ) {
612+ inner_path. remove ( 0 )
613+ } else {
614+ ""
615+ } ;
616+
617+ ( target, inner_path. join ( "/" ) )
618+ } ;
619+
620+ let current_target = if latest_release. build_status {
621+ if target. is_empty ( ) {
622+ default_target
623+ } else {
624+ target. to_owned ( )
625+ }
626+ } else {
627+ String :: new ( )
628+ } ;
629+
630+ let res = PlatformList {
631+ metadata : ShortMetadata {
632+ name,
633+ version_or_latest : version_or_latest. to_string ( ) ,
634+ doc_targets,
635+ } ,
636+ inner_path,
637+ use_direct_platform_links : true ,
638+ current_target,
639+ } ;
640+ Ok ( res. into_response ( ) )
641+ }
642+
478643#[ cfg( test) ]
479644mod tests {
480645 use super :: * ;
0 commit comments