1+ use std:: net:: IpAddr ;
2+
13use pyo3:: { exceptions:: PyValueError , prelude:: * , types:: PyDict } ;
24
35struct Error ( faup_rs:: Error ) ;
@@ -14,6 +16,113 @@ impl From<Error> for PyErr {
1416 }
1517}
1618
19+ #[ pyclass]
20+ #[ derive( Clone ) ]
21+ pub struct Hostname {
22+ hostname : String ,
23+ #[ pyo3( get) ]
24+ subdomain : Option < String > ,
25+ #[ pyo3( get) ]
26+ domain : Option < String > ,
27+ #[ pyo3( get) ]
28+ suffix : Option < String > ,
29+ }
30+
31+ impl From < faup_rs:: Hostname < ' _ > > for Hostname {
32+ fn from ( value : faup_rs:: Hostname < ' _ > ) -> Self {
33+ Self {
34+ hostname : value. full_name ( ) . to_string ( ) ,
35+ subdomain : value. subdomain ( ) . map ( |s| s. into ( ) ) ,
36+ domain : value. domain ( ) . map ( |s| s. into ( ) ) ,
37+ suffix : value. suffix ( ) . map ( |s| s. into ( ) ) ,
38+ }
39+ }
40+ }
41+
42+ #[ pymethods]
43+ impl Hostname {
44+ #[ new]
45+ pub fn new ( hn : & str ) -> PyResult < Self > {
46+ let h = faup_rs:: Host :: parse ( hn) . map_err ( |e| PyValueError :: new_err ( e. to_string ( ) ) ) ?;
47+ match h {
48+ faup_rs:: Host :: Hostname ( h) => Ok ( h. into ( ) ) ,
49+ faup_rs:: Host :: Ip ( _) => Err ( PyValueError :: new_err ( "invalid hostname" ) ) ,
50+ }
51+ }
52+
53+ pub fn __str__ ( & self ) -> & str {
54+ self . hostname . as_str ( )
55+ }
56+ }
57+
58+ #[ pyclass]
59+ pub enum Host {
60+ /// A hostname (domain name).
61+ Hostname ( Hostname ) ,
62+ /// An IP address (either IPv4 or IPv6).
63+ Ip ( IpAddr ) ,
64+ }
65+
66+ impl From < faup_rs:: Host < ' _ > > for Host {
67+ fn from ( value : faup_rs:: Host ) -> Self {
68+ match value {
69+ faup_rs:: Host :: Hostname ( h) => Host :: Hostname ( h. into ( ) ) ,
70+ faup_rs:: Host :: Ip ( ip) => Host :: Ip ( ip) ,
71+ }
72+ }
73+ }
74+
75+ #[ pymethods]
76+ impl Host {
77+ #[ new]
78+ pub fn new ( s : & str ) -> PyResult < Self > {
79+ faup_rs:: Host :: parse ( s)
80+ . map ( Host :: from)
81+ . map_err ( |e| PyValueError :: new_err ( e. to_string ( ) ) )
82+ }
83+
84+ pub fn try_into_hostname ( & self ) -> PyResult < Hostname > {
85+ match self {
86+ Host :: Hostname ( h) => Ok ( h. clone ( ) ) ,
87+ Host :: Ip ( _) => Err ( PyValueError :: new_err ( "host object is not a hostname" ) ) ,
88+ }
89+ }
90+
91+ pub fn try_into_ip ( & self ) -> PyResult < String > {
92+ match self {
93+ Host :: Hostname ( _) => Err ( PyValueError :: new_err ( "host object is not an ip address" ) ) ,
94+ Host :: Ip ( ip) => Ok ( ip. to_string ( ) ) ,
95+ }
96+ }
97+
98+ #[ inline( always) ]
99+ pub fn is_hostname ( & self ) -> bool {
100+ matches ! ( self , Host :: Hostname ( _) )
101+ }
102+
103+ #[ inline( always) ]
104+ pub fn is_ipv4 ( & self ) -> bool {
105+ matches ! ( self , Host :: Ip ( IpAddr :: V4 ( _) ) )
106+ }
107+
108+ #[ inline( always) ]
109+ pub fn is_ipv6 ( & self ) -> bool {
110+ matches ! ( self , Host :: Ip ( IpAddr :: V6 ( _) ) )
111+ }
112+
113+ #[ inline( always) ]
114+ pub fn is_ip_addr ( & self ) -> bool {
115+ self . is_ipv4 ( ) | self . is_ipv6 ( )
116+ }
117+
118+ pub fn __str__ ( & self ) -> String {
119+ match self {
120+ Self :: Hostname ( h) => h. hostname . clone ( ) ,
121+ Self :: Ip ( ip) => ip. to_string ( ) ,
122+ }
123+ }
124+ }
125+
17126/// A parsed URL representation for Python.
18127///
19128/// This class provides access to all components of a parsed URL, including scheme,
@@ -144,6 +253,9 @@ impl Url {
144253 . map_err ( |e| PyValueError :: new_err ( e. to_string ( ) ) )
145254 }
146255
256+ pub fn __str__ ( & self ) -> & str {
257+ self . orig . as_ref ( )
258+ }
147259}
148260
149261/// A compatibility class that mimics the FAUP (Fast URL Parser) Python API.
@@ -284,12 +396,10 @@ impl FaupCompat {
284396 domain
285397 . strip_suffix ( tld)
286398 . and_then ( |dom| dom. strip_suffix ( '.' ) )
287- }
288- else {
399+ } else {
289400 None
290401 }
291402 }
292-
293403}
294404
295405/// A Python module implemented in Rust for URL parsing.
@@ -312,6 +422,8 @@ impl FaupCompat {
312422#[ pymodule]
313423fn pyfaup ( m : & Bound < ' _ , PyModule > ) -> PyResult < ( ) > {
314424 m. add_class :: < Url > ( ) ?;
425+ m. add_class :: < Host > ( ) ?;
426+ m. add_class :: < Hostname > ( ) ?;
315427 m. add_class :: < FaupCompat > ( ) ?;
316428
317429 Ok ( ( ) )
0 commit comments