@@ -9,8 +9,9 @@ use crate::{
99 encode_url_path,
1010 error:: { AxumNope , AxumResult } ,
1111 } ,
12+ Storage ,
1213} ;
13- use anyhow:: anyhow ;
14+ use anyhow:: { bail , Context , Result } ;
1415use axum:: {
1516 extract:: { Extension , Path } ,
1617 response:: { IntoResponse , Response as AxumResponse } ,
@@ -91,6 +92,34 @@ pub struct Release {
9192 pub target_name : String ,
9293}
9394
95+ #[ fn_error_context:: context( "fetching readme for {name} {version}" ) ]
96+ fn fetch_readme_from_source (
97+ storage : & Storage ,
98+ name : & str ,
99+ version : & str ,
100+ archive_storage : bool ,
101+ ) -> anyhow:: Result < String > {
102+ let manifest = storage. fetch_source_file ( name, version, "Cargo.toml" , archive_storage) ?;
103+ let manifest = String :: from_utf8 ( manifest. content )
104+ . context ( "parsing Cargo.toml" ) ?
105+ . parse :: < toml:: Value > ( )
106+ . context ( "parsing Cargo.toml" ) ?;
107+ let paths = match manifest. get ( "package" ) . and_then ( |p| p. get ( "readme" ) ) {
108+ Some ( toml:: Value :: Boolean ( true ) ) => vec ! [ "README.md" ] ,
109+ Some ( toml:: Value :: Boolean ( false ) ) => vec ! [ ] ,
110+ Some ( toml:: Value :: String ( path) ) => vec ! [ path. as_ref( ) ] ,
111+ _ => vec ! [ "README.md" , "README.txt" , "README" ] ,
112+ } ;
113+ for path in & paths {
114+ if let Ok ( readme) = storage. fetch_source_file ( name, version, path, archive_storage) {
115+ let readme = String :: from_utf8 ( readme. content )
116+ . with_context ( || format ! ( "parsing {path} content" ) ) ?;
117+ return Ok ( readme) ;
118+ }
119+ }
120+ bail ! ( "couldn't find readme in stored source, checked {paths:?}" )
121+ }
122+
94123impl CrateDetails {
95124 pub fn new (
96125 conn : & mut impl GenericClient ,
@@ -237,6 +266,13 @@ impl CrateDetails {
237266 Ok ( Some ( crate_details) )
238267 }
239268
269+ fn enrich_readme ( & mut self , storage : & Storage ) -> Result < ( ) > {
270+ let readme =
271+ fetch_readme_from_source ( storage, & self . name , & self . version , self . archive_storage ) ?;
272+ self . readme = Some ( readme) ;
273+ Ok ( ( ) )
274+ }
275+
240276 /// Returns the latest non-yanked, non-prerelease release of this crate (or latest
241277 /// yanked/prereleased if that is all that exist).
242278 pub fn latest_release ( & self ) -> & Release {
@@ -270,7 +306,9 @@ pub(crate) fn releases_for_crate(
270306 . into_iter ( )
271307 . filter_map ( |row| {
272308 let version: String = row. get ( "version" ) ;
273- match semver:: Version :: parse ( & version) {
309+ match semver:: Version :: parse ( & version) . with_context ( || {
310+ format ! ( "invalid semver in database for crate {crate_id}: {version}" )
311+ } ) {
274312 Ok ( semversion) => Some ( Release {
275313 id : row. get ( "id" ) ,
276314 version : semversion,
@@ -281,9 +319,7 @@ pub(crate) fn releases_for_crate(
281319 target_name : row. get ( "target_name" ) ,
282320 } ) ,
283321 Err ( err) => {
284- report_error ( & anyhow ! ( err) . context ( format ! (
285- "invalid semver in database for crate {crate_id}: {version}"
286- ) ) ) ;
322+ report_error ( & err) ;
287323 None
288324 }
289325 }
@@ -310,9 +346,10 @@ pub(crate) struct CrateDetailHandlerParams {
310346 version : Option < String > ,
311347}
312348
313- #[ tracing:: instrument]
349+ #[ tracing:: instrument( skip ( pool , storage ) ) ]
314350pub ( crate ) async fn crate_details_handler (
315351 Path ( params) : Path < CrateDetailHandlerParams > ,
352+ Extension ( storage) : Extension < Arc < Storage > > ,
316353 Extension ( pool) : Extension < Pool > ,
317354 Extension ( repository_stats_updater) : Extension < Arc < RepositoryStatsUpdater > > ,
318355) -> AxumResult < AxumResponse > {
@@ -352,13 +389,19 @@ pub(crate) async fn crate_details_handler(
352389
353390 let details = spawn_blocking ( move || {
354391 let mut conn = pool. get ( ) ?;
355- CrateDetails :: new (
392+ let mut details = CrateDetails :: new (
356393 & mut * conn,
357394 & params. name ,
358395 & version,
359396 & version_or_latest,
360397 Some ( & repository_stats_updater) ,
361- )
398+ ) ?;
399+ if let Some ( ref mut details) = details {
400+ if let Err ( e) = details. enrich_readme ( & storage) {
401+ tracing:: debug!( "{e:?}" )
402+ }
403+ }
404+ Ok ( details)
362405 } )
363406 . await ?
364407 . ok_or ( AxumNope :: VersionNotFound ) ?;
@@ -1111,4 +1154,49 @@ mod tests {
11111154 Ok ( ( ) )
11121155 } ) ;
11131156 }
1157+
1158+ #[ test]
1159+ fn readme ( ) {
1160+ wrapper ( |env| {
1161+ env. fake_release ( )
1162+ . name ( "dummy" )
1163+ . version ( "0.1.0" )
1164+ . readme_only_database ( "database readme" )
1165+ . create ( ) ?;
1166+
1167+ env. fake_release ( )
1168+ . name ( "dummy" )
1169+ . version ( "0.2.0" )
1170+ . readme_only_database ( "database readme" )
1171+ . source_file ( "README.md" , b"storage readme" )
1172+ . create ( ) ?;
1173+
1174+ env. fake_release ( )
1175+ . name ( "dummy" )
1176+ . version ( "0.3.0" )
1177+ . source_file ( "README.md" , b"storage readme" )
1178+ . create ( ) ?;
1179+
1180+ env. fake_release ( )
1181+ . name ( "dummy" )
1182+ . version ( "0.4.0" )
1183+ . readme_only_database ( "database readme" )
1184+ . source_file ( "MEREAD" , b"storage meread" )
1185+ . source_file ( "Cargo.toml" , br#"package.readme = "MEREAD""# )
1186+ . create ( ) ?;
1187+
1188+ let check_readme = |path, content| {
1189+ let resp = env. frontend ( ) . get ( path) . send ( ) . unwrap ( ) ;
1190+ let body = String :: from_utf8 ( resp. bytes ( ) . unwrap ( ) . to_vec ( ) ) . unwrap ( ) ;
1191+ assert ! ( body. contains( content) ) ;
1192+ } ;
1193+
1194+ check_readme ( "/crate/dummy/0.1.0" , "database readme" ) ;
1195+ check_readme ( "/crate/dummy/0.2.0" , "storage readme" ) ;
1196+ check_readme ( "/crate/dummy/0.3.0" , "storage readme" ) ;
1197+ check_readme ( "/crate/dummy/0.4.0" , "storage meread" ) ;
1198+
1199+ Ok ( ( ) )
1200+ } ) ;
1201+ }
11141202}
0 commit comments