@@ -6,7 +6,10 @@ use anyhow::{Context, Result, anyhow, bail};
66use chrono:: { DateTime , Utc } ;
77use docs_rs_types:: { KrateName , Version } ;
88use docs_rs_utils:: { APP_USER_AGENT , retry_async} ;
9- use reqwest:: header:: { ACCEPT , HeaderValue , USER_AGENT } ;
9+ use reqwest:: {
10+ StatusCode ,
11+ header:: { ACCEPT , HeaderValue , USER_AGENT } ,
12+ } ;
1013use serde:: Deserialize ;
1114use tracing:: instrument;
1215use url:: Url ;
@@ -60,25 +63,7 @@ impl RegistryApi {
6063 & self ,
6164 name : & KrateName ,
6265 version : & Version ,
63- ) -> Result < ReleaseData > {
64- let ( release_time, yanked, downloads) = self
65- . get_release_time_yanked_downloads ( name, version)
66- . await
67- . context ( format ! ( "Failed to get crate data for {name}-{version}" ) ) ?;
68-
69- Ok ( ReleaseData {
70- release_time,
71- yanked,
72- downloads,
73- } )
74- }
75-
76- /// Get release_time, yanked and downloads from the registry's API
77- async fn get_release_time_yanked_downloads (
78- & self ,
79- name : & KrateName ,
80- version : & Version ,
81- ) -> Result < ( DateTime < Utc > , bool , i32 ) > {
66+ ) -> Result < Option < ReleaseData > > {
8267 let url = {
8368 let mut url = self . api_base . clone ( ) ;
8469 url. path_segments_mut ( )
@@ -103,28 +88,43 @@ impl RegistryApi {
10388 downloads : i32 ,
10489 }
10590
106- let response: Response = retry_async (
91+ let response: Response = match retry_async (
10792 || async {
108- Ok ( self
109- . client
110- . get ( url. clone ( ) )
111- . send ( )
112- . await ?
113- . error_for_status ( ) ?)
93+ Ok (
94+ match self
95+ . client
96+ . get ( url. clone ( ) )
97+ . send ( )
98+ . await ?
99+ . error_for_status ( )
100+ {
101+ Ok ( resp) => Some ( resp) ,
102+ Err ( err) if matches ! ( err. status( ) , Some ( StatusCode :: NOT_FOUND ) ) => None ,
103+ Err ( err) => return Err ( err. into ( ) ) ,
104+ } ,
105+ )
114106 } ,
115107 self . max_retries ,
116108 )
117109 . await ?
118- . json ( )
119- . await ?;
110+ {
111+ Some ( resp) => resp. json ( ) . await ?,
112+ None => {
113+ return Ok ( None ) ;
114+ }
115+ } ;
120116
121117 let version = response
122118 . versions
123119 . into_iter ( )
124120 . find ( |data| data. num == * version)
125121 . with_context ( || anyhow ! ( "Could not find version in response" ) ) ?;
126122
127- Ok ( ( version. created_at , version. yanked , version. downloads ) )
123+ Ok ( Some ( ReleaseData {
124+ release_time : version. created_at ,
125+ yanked : version. yanked ,
126+ downloads : version. downloads ,
127+ } ) )
128128 }
129129
130130 /// Fetch owners from the registry's API
@@ -241,3 +241,74 @@ impl RegistryApi {
241241 Ok ( Search { crates, meta } )
242242 }
243243}
244+
245+ #[ cfg( test) ]
246+ mod tests {
247+ use super :: * ;
248+ use anyhow:: Result ;
249+ use docs_rs_types:: testing:: { KRATE , V1 , V2 } ;
250+ use reqwest:: header:: CONTENT_TYPE ;
251+
252+ #[ tokio:: test]
253+ async fn test_get_release_data ( ) -> Result < ( ) > {
254+ let mut server = mockito:: Server :: new_async ( ) . await ;
255+
256+ let created = Utc :: now ( ) ;
257+
258+ let _m = server
259+ . mock ( "GET" , "/api/v1/crates/krate/versions" )
260+ . with_status ( 200 )
261+ . with_header ( CONTENT_TYPE , mime:: APPLICATION_JSON . as_ref ( ) )
262+ . with_body (
263+ serde_json:: json!( {
264+ "versions" : [
265+ {
266+ "num" : V1 . to_string( ) ,
267+ "created_at" : created. to_rfc3339( ) ,
268+ "yanked" : false ,
269+ "downloads" : 42
270+ } ,
271+ {
272+ "num" : V2 . to_string( ) ,
273+ "created_at" : "2025-01-01T00:00:00Z" ,
274+ "yanked" : true ,
275+ "downloads" : 22
276+ }
277+ ]
278+ } )
279+ . to_string ( ) ,
280+ )
281+ . create_async ( )
282+ . await ;
283+
284+ let api = RegistryApi :: new ( server. url ( ) . parse ( ) . unwrap ( ) , 0 ) ?;
285+
286+ assert_eq ! (
287+ api. get_release_data( & KRATE , & V1 ) . await ?,
288+ Some ( ReleaseData {
289+ release_time: created,
290+ yanked: false ,
291+ downloads: 42
292+ } )
293+ ) ;
294+
295+ Ok ( ( ) )
296+ }
297+
298+ #[ tokio:: test]
299+ async fn test_404_in_release_data_returns_none ( ) -> Result < ( ) > {
300+ let mut server = mockito:: Server :: new_async ( ) . await ;
301+
302+ let _m = server
303+ . mock ( "GET" , "/api/v1/crates/krate/versions" )
304+ . with_status ( 404 )
305+ . create_async ( )
306+ . await ;
307+
308+ let api = RegistryApi :: new ( server. url ( ) . parse ( ) . unwrap ( ) , 0 ) ?;
309+
310+ assert_eq ! ( api. get_release_data( & KRATE , & V1 ) . await ?, None , ) ;
311+
312+ Ok ( ( ) )
313+ }
314+ }
0 commit comments