1- use std:: net:: IpAddr ;
1+ use std:: { fmt :: Display , net:: IpAddr } ;
22
33use pyo3:: {
44 basic:: CompareOp ,
@@ -201,6 +201,7 @@ impl Hostname {
201201
202202/// Represents a host, which can be either a [`Hostname`] or an [`IpAddr`].
203203#[ pyclass]
204+ #[ derive( Clone ) ]
204205pub enum Host {
205206 /// A hostname (domain name).
206207 Hostname ( Hostname ) ,
@@ -220,6 +221,26 @@ impl From<faup_rs::Host<'_>> for Host {
220221 }
221222}
222223
224+ impl Display for Host {
225+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
226+ match self {
227+ Self :: Hostname ( h) => write ! ( f, "{}" , h. hostname. clone( ) ) ,
228+ Self :: Ipv4 ( ip) => write ! ( f, "{ip}" ) ,
229+ Self :: Ipv6 ( ip) => write ! ( f, "{ip}" ) ,
230+ }
231+ }
232+ }
233+
234+ impl Host {
235+ #[ inline( always) ]
236+ fn suffix_ref ( & self ) -> Option < & Suffix > {
237+ match self {
238+ Host :: Hostname ( hostname) => hostname. suffix . as_ref ( ) ,
239+ Host :: Ipv4 ( _ip_addr) | Host :: Ipv6 ( _ip_addr) => None ,
240+ }
241+ }
242+ }
243+
223244#[ pymethods]
224245impl Host {
225246 /// Creates a new [`Host`] by parsing a host string.
@@ -359,13 +380,81 @@ impl Host {
359380 self . is_ipv4 ( ) | self . is_ipv6 ( )
360381 }
361382
362- pub fn __str__ ( & self ) -> String {
383+ /// Returns the domain part of the hostname if this host is a hostname.
384+ ///
385+ /// Returns `None` if this host is an IP address or if the hostname has no recognized domain.
386+ ///
387+ /// # Returns
388+ ///
389+ /// * `Optional[str]` - The domain part of the hostname, or `None` if not applicable.
390+ ///
391+ /// # Example
392+ ///
393+ /// >>> from pyfaup import Host
394+ /// >>> host = Host("sub.example.com")
395+ /// >>> print(host.domain()) # "example.com"
396+ /// >>> print(Host("192.168.1.1").domain()) # None
397+ pub fn domain ( & self ) -> Option < & str > {
363398 match self {
364- Self :: Hostname ( h) => h. hostname . clone ( ) ,
365- Self :: Ipv4 ( ip) => ip. to_string ( ) ,
366- Self :: Ipv6 ( ip) => ip. to_string ( ) ,
399+ Host :: Hostname ( hostname) => hostname. domain . as_ref ( ) . map ( String :: as_ref) ,
400+ Host :: Ipv4 ( _ip_addr) | Host :: Ipv6 ( _ip_addr) => None ,
367401 }
368402 }
403+
404+ /// Returns the subdomain part of the hostname if this host is a hostname.
405+ ///
406+ /// Returns `None` if this host is an IP address or if the hostname has no subdomain.
407+ ///
408+ /// # Returns
409+ ///
410+ /// * `Optional[str]` - The subdomain part of the hostname, or `None` if not applicable.
411+ ///
412+ /// # Example
413+ ///
414+ /// >>> from pyfaup import Host
415+ /// >>> host = Host("sub.example.com")
416+ /// >>> print(host.subdomain()) # "sub"
417+ /// >>> print(Host("example.com").subdomain()) # None
418+ /// >>> print(Host("192.168.1.1").subdomain()) # None
419+ pub fn subdomain ( & self ) -> Option < & str > {
420+ match self {
421+ Host :: Hostname ( hostname) => hostname. subdomain . as_ref ( ) . map ( String :: as_ref) ,
422+ Host :: Ipv4 ( _ip_addr) | Host :: Ipv6 ( _ip_addr) => None ,
423+ }
424+ }
425+
426+ /// Returns the suffix (public suffix) of the hostname if this host is a hostname.
427+ ///
428+ /// Returns `None` if this host is an IP address or if the hostname has no recognized suffix.
429+ ///
430+ /// # Returns
431+ ///
432+ /// * `Optional[Suffix]` - The suffix of the hostname, or `None` if not applicable.
433+ ///
434+ /// # Example
435+ ///
436+ /// >>> from pyfaup import Host
437+ /// >>> host = Host("sub.example.com")
438+ /// >>> print(host.suffix()) # Suffix(com, True)
439+ /// >>> print(Host("192.168.1.1").suffix()) # None
440+ pub fn suffix ( & self ) -> Option < Suffix > {
441+ self . suffix_ref ( ) . cloned ( )
442+ }
443+
444+ /// Returns the string representation of the host.
445+ ///
446+ /// # Returns
447+ ///
448+ /// * `str` - The string representation of the host.
449+ ///
450+ /// # Example
451+ ///
452+ /// >>> from pyfaup import Host
453+ /// >>> print(str(Host("example.com"))) # "example.com"
454+ /// >>> print(str(Host("192.168.1.1"))) # "192.168.1.1"
455+ pub fn __str__ ( & self ) -> String {
456+ self . to_string ( )
457+ }
369458}
370459
371460/// A parsed URL representation for Python.
@@ -405,13 +494,7 @@ pub struct Url {
405494 #[ pyo3( get) ]
406495 pub password : Option < String > ,
407496 #[ pyo3( get) ]
408- pub host : Option < String > ,
409- #[ pyo3( get) ]
410- pub subdomain : Option < String > ,
411- #[ pyo3( get) ]
412- pub domain : Option < String > ,
413- #[ pyo3( get) ]
414- pub suffix : Option < Suffix > ,
497+ pub host : Option < Host > ,
415498 #[ pyo3( get) ]
416499 pub port : Option < u16 > ,
417500 #[ pyo3( get) ]
@@ -424,10 +507,6 @@ pub struct Url {
424507
425508impl From < faup_rs:: Url < ' _ > > for Url {
426509 fn from ( value : faup_rs:: Url < ' _ > ) -> Self {
427- let mut subdomain = None ;
428- let mut domain = None ;
429- let mut suffix = None ;
430-
431510 let ( username, password) = match value. userinfo ( ) {
432511 Some ( u) => (
433512 Some ( u. username ( ) . to_string ( ) ) ,
@@ -436,31 +515,24 @@ impl From<faup_rs::Url<'_>> for Url {
436515 None => ( None , None ) ,
437516 } ;
438517
439- let host = match value. host ( ) {
440- Some ( faup_rs:: Host :: Hostname ( hostname) ) => {
441- subdomain = hostname. subdomain ( ) . map ( |s| s. into ( ) ) ;
442- domain = hostname. domain ( ) . map ( |d| d. into ( ) ) ;
443- suffix = hostname. suffix ( ) . map ( |s| s. into ( ) ) ;
444- Some ( hostname. full_name ( ) . into ( ) )
445- }
446- Some ( faup_rs:: Host :: IpV4 ( ip) ) => Some ( ip. to_string ( ) ) ,
447- Some ( faup_rs:: Host :: IpV6 ( ip, _) ) => Some ( ip. to_string ( ) ) ,
448- None => None ,
449- } ;
518+ let orig = value. as_str ( ) . into ( ) ;
519+ let scheme = value. scheme ( ) . into ( ) ;
520+ let port = value. port ( ) ;
521+ let path = value. path ( ) . map ( |p| p. into ( ) ) ;
522+ let query = value. query ( ) . map ( |q| q. into ( ) ) ;
523+ let fragment = value. fragment ( ) . map ( |f| f. into ( ) ) ;
524+ let host = value. host . map ( Host :: from) ;
450525
451526 Self {
452- orig : value . as_str ( ) . into ( ) ,
453- scheme : value . scheme ( ) . into ( ) ,
527+ orig,
528+ scheme,
454529 username,
455530 password,
456531 host,
457- subdomain,
458- domain,
459- suffix,
460- port : value. port ( ) ,
461- path : value. path ( ) . map ( |p| p. into ( ) ) ,
462- query : value. query ( ) . map ( |q| q. into ( ) ) ,
463- fragment : value. fragment ( ) . map ( |f| f. into ( ) ) ,
532+ port,
533+ path,
534+ query,
535+ fragment,
464536 }
465537 }
466538}
@@ -584,12 +656,22 @@ impl FaupCompat {
584656 let credentials = url. and_then ( |u| u. credentials ( ) ) ;
585657
586658 m. set_item ( "credentials" , credentials) ?;
587- m. set_item ( "domain" , url. and_then ( |u| u. domain . clone ( ) ) ) ?;
588- m. set_item ( "subdomain" , url. and_then ( |u| u. subdomain . clone ( ) ) ) ?;
659+ m. set_item (
660+ "domain" ,
661+ url. and_then ( |u| u. host . as_ref ( ) ) . and_then ( |h| h. domain ( ) ) ,
662+ ) ?;
663+ m. set_item (
664+ "subdomain" ,
665+ url. and_then ( |u| u. host . as_ref ( ) )
666+ . and_then ( |h| h. subdomain ( ) ) ,
667+ ) ?;
589668 m. set_item ( "fragment" , url. and_then ( |u| u. fragment . clone ( ) ) ) ?;
590669 m. set_item ( "host" , url. map ( |u| u. host . clone ( ) ) ) ?;
591670 m. set_item ( "resource_path" , url. and_then ( |u| u. path . clone ( ) ) ) ?;
592- m. set_item ( "tld" , url. and_then ( |u| u. suffix . clone ( ) ) ) ?;
671+ m. set_item (
672+ "tld" ,
673+ url. and_then ( |u| u. host . as_ref ( ) ) . and_then ( |h| h. suffix ( ) ) ,
674+ ) ?;
593675 m. set_item ( "query_string" , url. and_then ( |u| u. query . clone ( ) ) ) ?;
594676 m. set_item ( "scheme" , url. map ( |u| u. scheme . clone ( ) ) ) ?;
595677 m. set_item ( "port" , url. map ( |u| u. port ) ) ?;
@@ -603,30 +685,40 @@ impl FaupCompat {
603685 }
604686
605687 fn get_domain ( & self ) -> Option < & str > {
606- self . url . as_ref ( ) ?. domain . as_deref ( )
688+ self . url
689+ . as_ref ( )
690+ . and_then ( |u| u. host . as_ref ( ) )
691+ . and_then ( |h| h. domain ( ) )
607692 }
608693
609694 fn get_subdomain ( & self ) -> Option < & str > {
610- self . url . as_ref ( ) ?. subdomain . as_deref ( )
695+ self . url
696+ . as_ref ( )
697+ . and_then ( |u| u. host . as_ref ( ) )
698+ . and_then ( |h| h. subdomain ( ) )
611699 }
612700
613701 fn get_fragment ( & self ) -> Option < & str > {
614702 self . url . as_ref ( ) ?. fragment . as_deref ( )
615703 }
616704
617- fn get_host ( & self ) -> Option < & str > {
705+ fn get_host ( & self ) -> Option < String > {
618706 self . url
619707 . as_ref ( )
620- . map ( |u| u. host . as_ref ( ) ) ?
621- . map ( |h| h. as_ref ( ) )
708+ . and_then ( |u| u. host . as_ref ( ) )
709+ . map ( |h| h. to_string ( ) )
622710 }
623711
624712 fn get_resource_path ( & self ) -> Option < & str > {
625713 self . url . as_ref ( ) ?. path . as_deref ( )
626714 }
627715
628716 fn get_tld ( & self ) -> Option < & str > {
629- self . url . as_ref ( ) ?. suffix . as_ref ( ) . map ( |s| s. value . as_str ( ) )
717+ self . url
718+ . as_ref ( )
719+ . and_then ( |u| u. host . as_ref ( ) )
720+ . and_then ( |h| h. suffix_ref ( ) )
721+ . map ( |s| s. value . as_str ( ) )
630722 }
631723
632724 fn get_query_string ( & self ) -> Option < & str > {
0 commit comments