@@ -17,11 +17,14 @@ use crate::target_configs::stdin::StdinTarget;
1717use ambassador:: { delegatable_trait, Delegate } ;
1818use clap:: { Parser , Subcommand } ;
1919use itertools:: Itertools ;
20+ use serde:: de:: DeserializeOwned ;
2021use std:: collections:: HashSet ;
2122use std:: fmt;
2223use std:: str:: FromStr ;
2324use std:: sync:: { mpsc, Arc } ;
25+ use std:: time:: Duration ;
2426use thiserror:: Error ;
27+ use tokio:: runtime:: Runtime ;
2528use url:: Url ;
2629
2730#[ derive( Debug , PartialEq , Clone ) ]
@@ -49,6 +52,8 @@ pub enum AppError {
4952 RequestTimeout ( String ) ,
5053 #[ error( "{0}" ) ]
5154 RequestError ( String ) ,
55+ #[ error( "HTTP {status} from {url}" ) ]
56+ HttpError { status : u16 , url : String } ,
5257 #[ error( "no mirrors after filtering" ) ]
5358 NoMirrorsAfterFiltering ,
5459 #[ error( transparent) ]
@@ -330,3 +335,57 @@ impl Config {
330335 . next ( )
331336 }
332337}
338+
339+ fn convert_reqwest_error ( e : reqwest:: Error , url : & str ) -> AppError {
340+ if e. is_timeout ( ) {
341+ AppError :: RequestTimeout ( url. to_string ( ) )
342+ } else {
343+ AppError :: RequestError ( format ! ( "failed to connect to {}: {}" , url, e) )
344+ }
345+ }
346+
347+ pub fn fetch_json < T : DeserializeOwned > ( url : & str , timeout_ms : u64 ) -> Result < T , AppError > {
348+ Runtime :: new ( ) . unwrap ( ) . block_on ( async {
349+ let response = reqwest:: Client :: new ( )
350+ . get ( url)
351+ . timeout ( Duration :: from_millis ( timeout_ms) )
352+ . send ( )
353+ . await
354+ . map_err ( |e| convert_reqwest_error ( e, url) ) ?;
355+
356+ let status = response. status ( ) ;
357+ if !status. is_success ( ) {
358+ return Err ( AppError :: HttpError {
359+ status : status. as_u16 ( ) ,
360+ url : url. to_string ( ) ,
361+ } ) ;
362+ }
363+
364+ response. json :: < T > ( ) . await . map_err ( |e| {
365+ AppError :: RequestError ( format ! ( "failed to decode JSON from {}: {}" , url, e) )
366+ } )
367+ } )
368+ }
369+
370+ pub fn fetch_text ( url : & str , timeout_ms : u64 ) -> Result < String , AppError > {
371+ Runtime :: new ( ) . unwrap ( ) . block_on ( async {
372+ let response = reqwest:: Client :: new ( )
373+ . get ( url)
374+ . timeout ( Duration :: from_millis ( timeout_ms) )
375+ . send ( )
376+ . await
377+ . map_err ( |e| convert_reqwest_error ( e, url) ) ?;
378+
379+ let status = response. status ( ) ;
380+ if !status. is_success ( ) {
381+ return Err ( AppError :: HttpError {
382+ status : status. as_u16 ( ) ,
383+ url : url. to_string ( ) ,
384+ } ) ;
385+ }
386+
387+ response. text_with_charset ( "utf-8" ) . await . map_err ( |e| {
388+ AppError :: RequestError ( format ! ( "failed to read response from {}: {}" , url, e) )
389+ } )
390+ } )
391+ }
0 commit comments