@@ -349,6 +349,91 @@ pub fn download_first_available_(
349349 download_first_available__ ( client_, urls, local_path, etag)
350350}
351351
352+ /// What a conditional fetch found. See [`fetch_optional_if_modified_`].
353+ pub enum ConditionalFetch {
354+ /// The server has no such resource (404).
355+ NotFound ,
356+ /// The server confirmed the caller's copy is still current (304). There is
357+ /// no body to go with this.
358+ NotModified ,
359+ /// New content, with the `ETag` identifying it if the server sent one.
360+ Fetched {
361+ bytes : Vec < u8 > ,
362+ etag : Option < String > ,
363+ } ,
364+ }
365+
366+ /// Conditionally fetch a resource into memory.
367+ ///
368+ /// Like `download_optional_if_newer_`, but nothing is written to disk and
369+ /// there is no TTL check: the caller decides when to call this and what to
370+ /// keep. That is what you want when the downloaded bytes are not the artifact
371+ /// being cached — the binary indices are parsed and stored in a different
372+ /// format entirely, so writing the response out only to delete it again would
373+ /// be pure overhead.
374+ ///
375+ /// Passing `etag` sends `If-None-Match`. Only do that while you still hold the
376+ /// content it describes: a 304 comes with no body, so asking for one you
377+ /// cannot use leaves you with nothing.
378+ pub fn fetch_optional_if_modified_ (
379+ url : & str ,
380+ etag : Option < & str > ,
381+ client : Option < & reqwest:: Client > ,
382+ ) -> Result < ConditionalFetch , Box < dyn Error > > {
383+ let client_ = match client {
384+ Some ( c) => c,
385+ None => & reqwest:: Client :: new ( ) ,
386+ } ;
387+ fetch_optional_if_modified__ ( client_, url, etag)
388+ }
389+
390+ #[ tokio:: main]
391+ async fn fetch_optional_if_modified__ (
392+ client : & reqwest:: Client ,
393+ url : & str ,
394+ etag : Option < & str > ,
395+ ) -> Result < ConditionalFetch , Box < dyn Error > > {
396+ fetch_optional_if_modified ( client, url, etag) . await
397+ }
398+
399+ async fn fetch_optional_if_modified (
400+ client : & reqwest:: Client ,
401+ url : & str ,
402+ etag : Option < & str > ,
403+ ) -> Result < ConditionalFetch , Box < dyn Error > > {
404+ let mut req = client. get ( url) ;
405+ if let Some ( etag) = etag {
406+ req = req. header ( "If-None-Match" , etag) ;
407+ }
408+ info ! ( "Checking for updates for {}" , url) ;
409+ let resp = req. send ( ) . await ?;
410+
411+ match resp. status ( ) {
412+ StatusCode :: NOT_MODIFIED => Ok ( ConditionalFetch :: NotModified ) ,
413+
414+ StatusCode :: OK => {
415+ let etag = resp
416+ . headers ( )
417+ . get ( "etag" )
418+ . and_then ( |v| v. to_str ( ) . ok ( ) )
419+ . map ( |s| s. to_string ( ) ) ;
420+ let bytes = resp. bytes ( ) . await ?. to_vec ( ) ;
421+ Ok ( ConditionalFetch :: Fetched { bytes, etag } )
422+ }
423+
424+ StatusCode :: NOT_FOUND => {
425+ debug ! ( "No such resource (404): {}" , url) ;
426+ Ok ( ConditionalFetch :: NotFound )
427+ }
428+
429+ status => {
430+ OUTPUT . error ( & format ! ( "Failed to download {}, status: {}" , url, status) ) ;
431+ error ! ( "Failed to download {}, status: {}" , url, status) ;
432+ bail ! ( "Failed to download {}, status: {}" , url, status) ;
433+ }
434+ }
435+ }
436+
352437/// Like `download_if_newer`, but a 404 is a normal outcome rather than an
353438/// error: it returns `Ok(None)` instead of failing and printing to the
354439/// terminal. Used for optional per-package metadata that simply may not exist
0 commit comments