@@ -9,8 +9,9 @@ use crate::{
9
9
encode_url_path,
10
10
error:: { AxumNope , AxumResult } ,
11
11
} ,
12
+ Storage ,
12
13
} ;
13
- use anyhow:: anyhow ;
14
+ use anyhow:: { bail , Context , Result } ;
14
15
use axum:: {
15
16
extract:: { Extension , Path } ,
16
17
response:: { IntoResponse , Response as AxumResponse } ,
@@ -91,6 +92,34 @@ pub struct Release {
91
92
pub target_name : String ,
92
93
}
93
94
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
+
94
123
impl CrateDetails {
95
124
pub fn new (
96
125
conn : & mut impl GenericClient ,
@@ -237,6 +266,13 @@ impl CrateDetails {
237
266
Ok ( Some ( crate_details) )
238
267
}
239
268
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
+
240
276
/// Returns the latest non-yanked, non-prerelease release of this crate (or latest
241
277
/// yanked/prereleased if that is all that exist).
242
278
pub fn latest_release ( & self ) -> & Release {
@@ -270,7 +306,9 @@ pub(crate) fn releases_for_crate(
270
306
. into_iter ( )
271
307
. filter_map ( |row| {
272
308
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
+ } ) {
274
312
Ok ( semversion) => Some ( Release {
275
313
id : row. get ( "id" ) ,
276
314
version : semversion,
@@ -281,9 +319,7 @@ pub(crate) fn releases_for_crate(
281
319
target_name : row. get ( "target_name" ) ,
282
320
} ) ,
283
321
Err ( err) => {
284
- report_error ( & anyhow ! ( err) . context ( format ! (
285
- "invalid semver in database for crate {crate_id}: {version}"
286
- ) ) ) ;
322
+ report_error ( & err) ;
287
323
None
288
324
}
289
325
}
@@ -310,9 +346,10 @@ pub(crate) struct CrateDetailHandlerParams {
310
346
version : Option < String > ,
311
347
}
312
348
313
- #[ tracing:: instrument]
349
+ #[ tracing:: instrument( skip ( pool , storage ) ) ]
314
350
pub ( crate ) async fn crate_details_handler (
315
351
Path ( params) : Path < CrateDetailHandlerParams > ,
352
+ Extension ( storage) : Extension < Arc < Storage > > ,
316
353
Extension ( pool) : Extension < Pool > ,
317
354
Extension ( repository_stats_updater) : Extension < Arc < RepositoryStatsUpdater > > ,
318
355
) -> AxumResult < AxumResponse > {
@@ -352,13 +389,19 @@ pub(crate) async fn crate_details_handler(
352
389
353
390
let details = spawn_blocking ( move || {
354
391
let mut conn = pool. get ( ) ?;
355
- CrateDetails :: new (
392
+ let mut details = CrateDetails :: new (
356
393
& mut * conn,
357
394
& params. name ,
358
395
& version,
359
396
& version_or_latest,
360
397
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)
362
405
} )
363
406
. await ?
364
407
. ok_or ( AxumNope :: VersionNotFound ) ?;
@@ -1111,4 +1154,49 @@ mod tests {
1111
1154
Ok ( ( ) )
1112
1155
} ) ;
1113
1156
}
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
+ }
1114
1202
}
0 commit comments