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