@@ -16,8 +16,8 @@ use std::{collections::HashMap, num::NonZeroUsize, time::Duration};
1616
1717use crate :: {
1818 cache_key, is_bogon, Continent , CountryCurrency , CountryFlag , IpDetails ,
19- IpError , BATCH_MAX_SIZE , BATCH_REQ_TIMEOUT_DEFAULT , CONTINENTS , COUNTRIES ,
20- CURRENCIES , EU , FLAGS , VERSION ,
19+ IpError , ResproxyDetails , BATCH_MAX_SIZE , BATCH_REQ_TIMEOUT_DEFAULT ,
20+ CONTINENTS , COUNTRIES , CURRENCIES , EU , FLAGS , VERSION ,
2121} ;
2222
2323use lru:: LruCache ;
@@ -428,6 +428,52 @@ impl IpInfo {
428428 Ok ( report_url. unwrap ( ) . to_string ( ) )
429429 }
430430
431+ /// Looks up residential proxy details for a single IP address
432+ ///
433+ /// # Example
434+ ///
435+ /// ```no_run
436+ /// use ipinfo::IpInfo;
437+ ///
438+ /// #[tokio::main]
439+ /// async fn main() {
440+ /// let ipinfo = IpInfo::new(Default::default()).expect("should construct");
441+ /// let res = ipinfo.lookup_resproxy("175.107.211.204").await.expect("should run");
442+ /// }
443+ /// ```
444+ pub async fn lookup_resproxy (
445+ & self ,
446+ ip : & str ,
447+ ) -> Result < ResproxyDetails , IpError > {
448+ let response = self
449+ . client
450+ . get ( format ! ( "{BASE_URL}/resproxy/{ip}" ) )
451+ . headers ( Self :: construct_headers ( ) )
452+ . bearer_auth ( self . token . as_deref ( ) . unwrap_or_default ( ) )
453+ . send ( )
454+ . await ?;
455+
456+ // Check if we exhausted our request quota
457+ if let reqwest:: StatusCode :: TOO_MANY_REQUESTS = response. status ( ) {
458+ return Err ( err ! ( RateLimitExceededError ) ) ;
459+ }
460+
461+ // Acquire response
462+ let raw_resp = response. error_for_status ( ) ?. text ( ) . await ?;
463+
464+ // Parse the response
465+ let resp: serde_json:: Value = serde_json:: from_str ( & raw_resp) ?;
466+
467+ // Return if an error occurred
468+ if let Some ( e) = resp[ "error" ] . as_str ( ) {
469+ return Err ( err ! ( IpRequestError , e) ) ;
470+ }
471+
472+ // Parse the results
473+ let details: ResproxyDetails = serde_json:: from_str ( & raw_resp) ?;
474+ Ok ( details)
475+ }
476+
431477 // Add country details and EU status to response
432478 fn populate_static_details ( & self , details : & mut IpDetails ) {
433479 if !& details. country . is_empty ( ) {
@@ -617,4 +663,34 @@ mod tests {
617663 assert ! ( details. contains_key( "4.2.2.4" ) ) ;
618664 assert_eq ! ( details. len( ) , 2 ) ;
619665 }
666+
667+ #[ tokio:: test]
668+ async fn request_resproxy ( ) {
669+ let ipinfo = get_ipinfo_client ( ) ;
670+
671+ let details = ipinfo
672+ . lookup_resproxy ( "175.107.211.204" )
673+ . await
674+ . expect ( "should lookup resproxy" ) ;
675+
676+ assert_eq ! ( details. ip, "175.107.211.204" ) ;
677+ assert ! ( details. last_seen. is_some( ) ) ;
678+ assert ! ( details. percent_days_seen. is_some( ) ) ;
679+ assert ! ( details. service. is_some( ) ) ;
680+ }
681+
682+ #[ tokio:: test]
683+ async fn request_resproxy_empty ( ) {
684+ let ipinfo = get_ipinfo_client ( ) ;
685+
686+ let details = ipinfo
687+ . lookup_resproxy ( "8.8.8.8" )
688+ . await
689+ . expect ( "should lookup resproxy" ) ;
690+
691+ assert ! ( details. ip. is_empty( ) ) ;
692+ assert ! ( details. last_seen. is_none( ) ) ;
693+ assert ! ( details. percent_days_seen. is_none( ) ) ;
694+ assert ! ( details. service. is_none( ) ) ;
695+ }
620696}
0 commit comments