bare_types/net/host.rs
1//! Host type for network programming.
2//!
3//! This module provides a unified `Host` enum that can represent
4//! IP addresses, domain names, or hostnames with automatic parsing.
5//!
6//! # Parsing Order
7//!
8//! When parsing a string, the `Host` type attempts to parse in this order:
9//!
10//! 1. **`IpAddr`** - IPv4 or IPv6 addresses (e.g., "192.168.1.1", "`::1`")
11//! 2. **`DomainName`** - RFC 1035 domain names (e.g., "example.com")
12//! 3. **`Hostname`** - RFC 1123 hostnames (e.g., "localhost")
13//!
14//! # Examples
15//!
16//! ```rust
17//! use bare_types::net::Host;
18//!
19//! // Parse an IP address
20//! let host: Host = "192.168.1.1".parse().unwrap();
21//! assert!(host.is_ipaddr());
22//!
23//! // Parse a domain name (labels can start with digits)
24//! let host: Host = "123.example.com".parse().unwrap();
25//! assert!(host.is_domainname());
26//!
27//! // Create a hostname directly (labels must start with letters)
28//! let hostname = "localhost".parse::<bare_types::net::Hostname>().unwrap();
29//! let host = Host::from_hostname(hostname);
30//! assert!(host.is_hostname());
31//! ```
32
33use core::fmt;
34use core::str::FromStr;
35
36use super::{DomainName, Hostname, IpAddr};
37
38#[cfg(feature = "serde")]
39use serde::{Deserialize, Serialize};
40
41#[cfg(feature = "arbitrary")]
42use arbitrary::Arbitrary;
43
44/// Error type for host parsing.
45#[derive(Debug, Clone, PartialEq, Eq)]
46pub enum HostError {
47 /// Invalid host input
48 InvalidInput,
49}
50
51impl fmt::Display for HostError {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 match self {
54 Self::InvalidInput => write!(f, "invalid host"),
55 }
56 }
57}
58
59#[cfg(feature = "std")]
60impl std::error::Error for HostError {}
61
62/// A network host that can be an IP address, domain name, or hostname.
63///
64/// This enum provides a unified type for representing network hosts,
65/// with automatic parsing that follows a specific priority order.
66///
67/// # Parsing Priority
68///
69/// When parsing from a string, the following order is used:
70///
71/// 1. **`IpAddr`**: IPv4 (e.g., "192.168.1.1") or IPv6 (e.g., "`::1`")
72/// 2. **`DomainName`**: RFC 1035 domain names (labels can start with digits)
73/// 3. **`Hostname`**: RFC 1123 hostnames (labels must start with letters)
74///
75/// # Examples
76///
77/// ```rust
78/// use bare_types::net::Host;
79///
80/// // Create from IP address
81/// let ipaddr = "192.168.1.1".parse::<bare_types::net::IpAddr>().unwrap();
82/// let host = Host::from_ipaddr(ipaddr);
83/// assert!(host.is_ipaddr());
84///
85/// // Create from domain name
86/// let domain = "123.example.com".parse::<bare_types::net::DomainName>().unwrap();
87/// let host = Host::from_domainname(domain);
88/// assert!(host.is_domainname());
89///
90/// // Create from hostname
91/// let hostname = "localhost".parse::<bare_types::net::Hostname>().unwrap();
92/// let host = Host::from_hostname(hostname);
93/// assert!(host.is_hostname());
94///
95/// // Parse with automatic detection
96/// let host: Host = "192.168.1.1".parse().unwrap();
97/// assert!(host.is_ipaddr());
98///
99/// let host: Host = "example.com".parse().unwrap();
100/// assert!(host.is_domainname());
101/// ```
102#[derive(Debug, Clone, PartialEq, Eq, Hash)]
103#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
104pub enum Host {
105 /// An IP address (IPv4 or IPv6)
106 IpAddr(IpAddr),
107 /// A domain name (RFC 1035, labels can start with digits)
108 DomainName(DomainName),
109 /// A hostname (RFC 1123, labels must start with letters)
110 Hostname(Hostname),
111}
112
113#[cfg(feature = "arbitrary")]
114impl<'a> Arbitrary<'a> for Host {
115 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
116 let choice = u8::arbitrary(u)? % 3;
117 match choice {
118 0 => Ok(Self::IpAddr(IpAddr::arbitrary(u)?)),
119 1 => Ok(Self::DomainName(DomainName::arbitrary(u)?)),
120 _ => Ok(Self::Hostname(Hostname::arbitrary(u)?)),
121 }
122 }
123}
124
125impl Host {
126 /// Creates a `Host` from an IP address.
127 ///
128 /// # Examples
129 ///
130 /// ```rust
131 /// use bare_types::net::Host;
132 ///
133 /// let ipaddr = "192.168.1.1".parse::<bare_types::net::IpAddr>()?;
134 /// let host = Host::from_ipaddr(ipaddr);
135 /// assert!(host.is_ipaddr());
136 /// # Ok::<(), bare_types::net::IpAddrError>(())
137 /// ```
138 #[must_use]
139 #[inline]
140 pub const fn from_ipaddr(ipaddr: IpAddr) -> Self {
141 Self::IpAddr(ipaddr)
142 }
143
144 /// Creates a `Host` from a domain name.
145 ///
146 /// # Examples
147 ///
148 /// ```rust
149 /// use bare_types::net::Host;
150 ///
151 /// let domain = "example.com".parse::<bare_types::net::DomainName>()?;
152 /// let host = Host::from_domainname(domain);
153 /// assert!(host.is_domainname());
154 /// # Ok::<(), bare_types::net::DomainNameError>(())
155 /// ```
156 #[must_use]
157 #[inline]
158 pub const fn from_domainname(domain: DomainName) -> Self {
159 Self::DomainName(domain)
160 }
161
162 /// Creates a `Host` from a hostname.
163 ///
164 /// # Examples
165 ///
166 /// ```rust
167 /// use bare_types::net::Host;
168 ///
169 /// let hostname = "example.com".parse::<bare_types::net::Hostname>()?;
170 /// let host = Host::from_hostname(hostname);
171 /// assert!(host.is_hostname());
172 /// # Ok::<(), bare_types::net::HostnameError>(())
173 /// ```
174 #[must_use]
175 #[inline]
176 pub const fn from_hostname(hostname: Hostname) -> Self {
177 Self::Hostname(hostname)
178 }
179
180 /// Returns `true` if this host is an IP address.
181 ///
182 /// # Examples
183 ///
184 /// ```rust
185 /// use bare_types::net::Host;
186 ///
187 /// let host: Host = "192.168.1.1".parse()?;
188 /// assert!(host.is_ipaddr());
189 /// # Ok::<(), bare_types::net::HostError>(())
190 /// ```
191 #[must_use]
192 #[inline]
193 pub const fn is_ipaddr(&self) -> bool {
194 matches!(self, Self::IpAddr(_))
195 }
196
197 /// Returns `true` if this host is a domain name.
198 ///
199 /// # Examples
200 ///
201 /// ```rust
202 /// use bare_types::net::Host;
203 ///
204 /// let host: Host = "123.example.com".parse()?;
205 /// assert!(host.is_domainname());
206 /// # Ok::<(), bare_types::net::HostError>(())
207 /// ```
208 #[must_use]
209 #[inline]
210 pub const fn is_domainname(&self) -> bool {
211 matches!(self, Self::DomainName(_))
212 }
213
214 /// Returns `true` if this host is a hostname.
215 ///
216 /// # Examples
217 ///
218 /// ```rust
219 /// use bare_types::net::Host;
220 ///
221 /// let hostname = "localhost".parse::<bare_types::net::Hostname>()?;
222 /// let host = Host::from_hostname(hostname);
223 /// assert!(host.is_hostname());
224 /// # Ok::<(), bare_types::net::HostnameError>(())
225 /// ```
226 #[must_use]
227 #[inline]
228 pub const fn is_hostname(&self) -> bool {
229 matches!(self, Self::Hostname(_))
230 }
231
232 /// Returns a reference to the IP address if this is an IP address.
233 ///
234 /// # Examples
235 ///
236 /// ```rust
237 /// use bare_types::net::Host;
238 ///
239 /// let host: Host = "192.168.1.1".parse()?;
240 /// assert!(host.as_ipaddr().is_some());
241 /// # Ok::<(), bare_types::net::HostError>(())
242 /// ```
243 #[must_use]
244 #[inline]
245 pub const fn as_ipaddr(&self) -> Option<&IpAddr> {
246 match self {
247 Self::IpAddr(ipaddr) => Some(ipaddr),
248 _ => None,
249 }
250 }
251
252 /// Returns a reference to the domain name if this is a domain name.
253 ///
254 /// # Examples
255 ///
256 /// ```rust
257 /// use bare_types::net::Host;
258 ///
259 /// let host: Host = "123.example.com".parse()?;
260 /// assert!(host.as_domainname().is_some());
261 /// # Ok::<(), bare_types::net::HostError>(())
262 /// ```
263 #[must_use]
264 #[inline]
265 pub const fn as_domainname(&self) -> Option<&DomainName> {
266 match self {
267 Self::DomainName(domain) => Some(domain),
268 _ => None,
269 }
270 }
271
272 /// Returns a reference to the hostname if this is a hostname.
273 ///
274 /// # Examples
275 ///
276 /// ```rust
277 /// use bare_types::net::Host;
278 ///
279 /// let hostname = "localhost".parse::<bare_types::net::Hostname>()?;
280 /// let host = Host::from_hostname(hostname);
281 /// assert!(host.as_hostname().is_some());
282 /// # Ok::<(), bare_types::net::HostnameError>(())
283 /// ```
284 #[must_use]
285 #[inline]
286 pub const fn as_hostname(&self) -> Option<&Hostname> {
287 match self {
288 Self::Hostname(hostname) => Some(hostname),
289 _ => None,
290 }
291 }
292
293 /// Returns `true` if this host represents localhost.
294 ///
295 /// For IP addresses, this checks if it's a loopback address.
296 /// For domain names and hostnames, this checks if it's "localhost".
297 ///
298 /// # Examples
299 ///
300 /// ```rust
301 /// use bare_types::net::Host;
302 ///
303 /// // IPv4 loopback
304 /// let host: Host = "127.0.0.1".parse()?;
305 /// assert!(host.is_localhost());
306 ///
307 /// // IPv6 loopback
308 /// let host: Host = "::1".parse()?;
309 /// assert!(host.is_localhost());
310 ///
311 /// // localhost domain name
312 /// let host: Host = "localhost".parse()?;
313 /// assert!(host.is_localhost());
314 ///
315 /// // Not localhost
316 /// let host: Host = "example.com".parse()?;
317 /// assert!(!host.is_localhost());
318 /// # Ok::<(), Box<dyn std::error::Error>>(())
319 /// ```
320 #[must_use]
321 pub fn is_localhost(&self) -> bool {
322 match self {
323 Self::IpAddr(ip) => ip.is_loopback(),
324 Self::DomainName(domain) => domain.as_str() == "localhost",
325 Self::Hostname(hostname) => hostname.is_localhost(),
326 }
327 }
328
329 /// Parses a string into a `Host` with automatic type detection.
330 ///
331 /// The parsing follows this priority order:
332 /// 1. Try to parse as `IpAddr`
333 /// 2. Try to parse as `DomainName`
334 /// 3. Try to parse as `Hostname`
335 ///
336 /// # Errors
337 ///
338 /// Returns `HostError::InvalidInput` if the string cannot be parsed as
339 /// an IP address, domain name, or hostname.
340 ///
341 /// # Examples
342 ///
343 /// ```rust
344 /// use bare_types::net::Host;
345 ///
346 /// // IP address is parsed first
347 /// let host = Host::parse_str("192.168.1.1")?;
348 /// assert!(host.is_ipaddr());
349 ///
350 /// // Domain name (labels can start with digits)
351 /// let host = Host::parse_str("123.example.com")?;
352 /// assert!(host.is_domainname());
353 ///
354 /// // Domain name is also parsed before hostname for letter-start labels
355 /// let host = Host::parse_str("www.example.com")?;
356 /// assert!(host.is_domainname());
357 /// # Ok::<(), bare_types::net::HostError>(())
358 /// ```
359 pub fn parse_str(s: &str) -> Result<Self, HostError> {
360 if s.is_empty() {
361 return Err(HostError::InvalidInput);
362 }
363
364 // Try parsing as IpAddr first (highest priority)
365 if let Ok(ipaddr) = s.parse::<IpAddr>() {
366 return Ok(Self::IpAddr(ipaddr));
367 }
368
369 // Try parsing as DomainName (allows digit-start labels)
370 if let Ok(domain) = DomainName::new(s) {
371 return Ok(Self::DomainName(domain));
372 }
373
374 // Try parsing as Hostname (requires letter-start labels)
375 if let Ok(hostname) = Hostname::new(s) {
376 return Ok(Self::Hostname(hostname));
377 }
378
379 Err(HostError::InvalidInput)
380 }
381}
382
383impl FromStr for Host {
384 type Err = HostError;
385
386 fn from_str(s: &str) -> Result<Self, Self::Err> {
387 Self::parse_str(s)
388 }
389}
390
391impl fmt::Display for Host {
392 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
393 match self {
394 Self::IpAddr(ipaddr) => write!(f, "{ipaddr}"),
395 Self::DomainName(domain) => write!(f, "{domain}"),
396 Self::Hostname(hostname) => write!(f, "{hostname}"),
397 }
398 }
399}
400
401#[cfg(test)]
402mod tests {
403 use super::*;
404
405 #[test]
406 fn test_from_ipaddr() {
407 let ipaddr = "192.168.1.1".parse::<IpAddr>().unwrap();
408 let host = Host::from_ipaddr(ipaddr);
409 assert!(host.is_ipaddr());
410 assert!(!host.is_domainname());
411 assert!(!host.is_hostname());
412 }
413
414 #[test]
415 fn test_from_domainname() {
416 let domain = DomainName::new("example.com").unwrap();
417 let host = Host::from_domainname(domain);
418 assert!(!host.is_ipaddr());
419 assert!(host.is_domainname());
420 assert!(!host.is_hostname());
421 }
422
423 #[test]
424 fn test_from_hostname() {
425 let hostname = Hostname::new("example.com").unwrap();
426 let host = Host::from_hostname(hostname);
427 assert!(!host.is_ipaddr());
428 assert!(!host.is_domainname());
429 assert!(host.is_hostname());
430 }
431
432 #[test]
433 fn test_parse_ipv4() {
434 let host: Host = "192.168.1.1".parse().unwrap();
435 assert!(host.is_ipaddr());
436 assert_eq!(format!("{host}"), "192.168.1.1");
437 }
438
439 #[test]
440 fn test_parse_ipv6() {
441 let host: Host = "::1".parse().unwrap();
442 assert!(host.is_ipaddr());
443 assert_eq!(format!("{host}"), "::1");
444 }
445
446 #[test]
447 fn test_parse_domainname_digit_start() {
448 let host: Host = "123.example.com".parse().unwrap();
449 assert!(host.is_domainname());
450 assert_eq!(format!("{host}"), "123.example.com");
451 }
452
453 #[test]
454 fn test_parse_hostname_letter_start() {
455 // Note: With parsing order IpAddr -> DomainName -> Hostname,
456 // letter-start labels are parsed as DomainName (not Hostname)
457 // because DomainName is tried first and also accepts letter-start labels
458 let host: Host = "www.example.com".parse().unwrap();
459 assert!(host.is_domainname());
460 assert_eq!(format!("{host}"), "www.example.com");
461 }
462
463 #[test]
464 fn test_parse_priority_ipaddr_over_domainname() {
465 // "127.0.0.1" could be a valid domain name, but IP address takes priority
466 let host: Host = "127.0.0.1".parse().unwrap();
467 assert!(host.is_ipaddr());
468 }
469
470 #[test]
471 fn test_parse_priority_domainname_over_hostname() {
472 // "123.example.com" is valid as DomainName (digit start)
473 // but invalid as Hostname (must start with letter)
474 let host: Host = "123.example.com".parse().unwrap();
475 assert!(host.is_domainname());
476 assert!(!host.is_hostname());
477 }
478
479 #[test]
480 fn test_parse_str_empty() {
481 assert!(Host::parse_str("").is_err());
482 }
483
484 #[test]
485 fn test_parse_str_invalid() {
486 assert!(Host::parse_str("-invalid").is_err());
487 assert!(Host::parse_str("example..com").is_err());
488 }
489
490 #[test]
491 fn test_as_ipaddr() {
492 let host: Host = "192.168.1.1".parse().unwrap();
493 assert!(host.as_ipaddr().is_some());
494 assert!(host.as_domainname().is_none());
495 assert!(host.as_hostname().is_none());
496 }
497
498 #[test]
499 fn test_as_domainname() {
500 let host: Host = "123.example.com".parse().unwrap();
501 assert!(host.as_ipaddr().is_none());
502 assert!(host.as_domainname().is_some());
503 assert!(host.as_hostname().is_none());
504 }
505
506 #[test]
507 fn test_as_hostname() {
508 // Note: With parsing order IpAddr -> DomainName -> Hostname,
509 // letter-start labels are parsed as DomainName (not Hostname)
510 let host: Host = "www.example.com".parse().unwrap();
511 assert!(host.as_ipaddr().is_none());
512 assert!(host.as_domainname().is_some());
513 assert!(host.as_hostname().is_none());
514 }
515
516 #[test]
517 fn test_equality_ipaddr() {
518 let host1: Host = "192.168.1.1".parse().unwrap();
519 let host2: Host = "192.168.1.1".parse().unwrap();
520 let host3: Host = "192.168.1.2".parse().unwrap();
521
522 assert_eq!(host1, host2);
523 assert_ne!(host1, host3);
524 }
525
526 #[test]
527 fn test_equality_domainname() {
528 let host1: Host = "123.example.com".parse().unwrap();
529 let host2: Host = "123.example.com".parse().unwrap();
530 let host3: Host = "456.example.com".parse().unwrap();
531
532 assert_eq!(host1, host2);
533 assert_ne!(host1, host3);
534 }
535
536 #[test]
537 fn test_equality_hostname() {
538 // Note: With parsing order IpAddr -> DomainName -> Hostname,
539 // letter-start labels are parsed as DomainName (not Hostname)
540 let host1: Host = "www.example.com".parse().unwrap();
541 let host2: Host = "www.example.com".parse().unwrap();
542 let host3: Host = "api.example.com".parse().unwrap();
543
544 assert_eq!(host1, host2);
545 assert_ne!(host1, host3);
546 }
547
548 #[test]
549 fn test_equality_different_types() {
550 let host1: Host = "192.168.1.1".parse().unwrap();
551 let host2: Host = "www.example.com".parse().unwrap();
552
553 assert_ne!(host1, host2);
554 }
555
556 #[test]
557 fn test_clone() {
558 let host: Host = "www.example.com".parse().unwrap();
559 let host2 = host.clone();
560 assert_eq!(host, host2);
561 }
562
563 #[test]
564 fn test_display_ipaddr() {
565 let host: Host = "192.168.1.1".parse().unwrap();
566 assert_eq!(format!("{host}"), "192.168.1.1");
567 }
568
569 #[test]
570 fn test_display_domainname() {
571 let host: Host = "123.example.com".parse().unwrap();
572 assert_eq!(format!("{host}"), "123.example.com");
573 }
574
575 #[test]
576 fn test_display_hostname() {
577 let host: Host = "www.example.com".parse().unwrap();
578 assert_eq!(format!("{host}"), "www.example.com");
579 }
580
581 #[test]
582 fn test_debug() {
583 let host: Host = "www.example.com".parse().unwrap();
584 let debug = format!("{:?}", host);
585 // Note: With parsing order IpAddr -> DomainName -> Hostname,
586 // letter-start labels are parsed as DomainName (not Hostname)
587 assert!(debug.contains("DomainName"));
588 }
589
590 #[test]
591 fn test_hash() {
592 use core::hash::Hash;
593 use core::hash::Hasher;
594
595 #[derive(Default)]
596 struct SimpleHasher(u64);
597
598 impl Hasher for SimpleHasher {
599 fn finish(&self) -> u64 {
600 self.0
601 }
602
603 fn write(&mut self, bytes: &[u8]) {
604 for byte in bytes {
605 self.0 = self.0.wrapping_mul(31).wrapping_add(*byte as u64);
606 }
607 }
608 }
609
610 let host1: Host = "www.example.com".parse().unwrap();
611 let host2: Host = "www.example.com".parse().unwrap();
612 let host3: Host = "api.example.com".parse().unwrap();
613
614 let mut hasher1 = SimpleHasher::default();
615 let mut hasher2 = SimpleHasher::default();
616 let mut hasher3 = SimpleHasher::default();
617
618 host1.hash(&mut hasher1);
619 host2.hash(&mut hasher2);
620 host3.hash(&mut hasher3);
621
622 assert_eq!(hasher1.finish(), hasher2.finish());
623 assert_ne!(hasher1.finish(), hasher3.finish());
624 }
625
626 #[test]
627 fn test_parse_ipv4_private() {
628 let host: Host = "10.0.0.1".parse().unwrap();
629 assert!(host.is_ipaddr());
630 }
631
632 #[test]
633 fn test_parse_ipv6_loopback() {
634 let host: Host = "::1".parse().unwrap();
635 assert!(host.is_ipaddr());
636 }
637
638 #[test]
639 fn test_parse_ipv6_full() {
640 let host: Host = "2001:0db8:85a3:0000:0000:8a2e:0370:7334".parse().unwrap();
641 assert!(host.is_ipaddr());
642 }
643
644 #[test]
645 fn test_parse_domainname_numeric_label() {
646 let host: Host = "123.456.789".parse().unwrap();
647 assert!(host.is_domainname());
648 }
649
650 #[test]
651 fn test_parse_hostname_multi_label() {
652 // Note: With parsing order IpAddr -> DomainName -> Hostname,
653 // letter-start labels are parsed as DomainName (not Hostname)
654 let host: Host = "api.v1.example.com".parse().unwrap();
655 assert!(host.is_domainname());
656 }
657
658 #[test]
659 fn test_error_display() {
660 let err = HostError::InvalidInput;
661 assert_eq!(format!("{err}"), "invalid host");
662 }
663
664 #[test]
665 fn test_parse_str_method() {
666 let host = Host::parse_str("192.168.1.1").unwrap();
667 assert!(host.is_ipaddr());
668
669 // Note: With parsing order IpAddr -> DomainName -> Hostname,
670 // letter-start labels are parsed as DomainName (not Hostname)
671 let host = Host::parse_str("www.example.com").unwrap();
672 assert!(host.is_domainname());
673 }
674
675 #[test]
676 fn test_case_insensitive_hostname() {
677 // Note: With parsing order IpAddr -> DomainName -> Hostname,
678 // letter-start labels are parsed as DomainName (not Hostname)
679 let host1: Host = "WWW.EXAMPLE.COM".parse().unwrap();
680 let host2: Host = "www.example.com".parse().unwrap();
681 assert_eq!(host1, host2);
682 }
683
684 #[test]
685 fn test_case_insensitive_domainname() {
686 let host1: Host = "123.EXAMPLE.COM".parse().unwrap();
687 let host2: Host = "123.example.com".parse().unwrap();
688 assert_eq!(host1, host2);
689 }
690
691 #[test]
692 fn test_localhost_hostname() {
693 // Note: With parsing order IpAddr -> DomainName -> Hostname,
694 // letter-start labels are parsed as DomainName (not Hostname)
695 let host: Host = "localhost".parse().unwrap();
696 assert!(host.is_domainname());
697 }
698
699 #[test]
700 fn test_is_localhost() {
701 // IPv4 loopback
702 let host: Host = "127.0.0.1".parse().unwrap();
703 assert!(host.is_localhost());
704
705 // IPv6 loopback
706 let host: Host = "::1".parse().unwrap();
707 assert!(host.is_localhost());
708
709 // localhost domain name
710 let host: Host = "localhost".parse().unwrap();
711 assert!(host.is_localhost());
712
713 // Not localhost
714 let host: Host = "example.com".parse().unwrap();
715 assert!(!host.is_localhost());
716
717 let host: Host = "192.168.1.1".parse().unwrap();
718 assert!(!host.is_localhost());
719 }
720
721 #[test]
722 fn test_numeric_only_domainname() {
723 let host: Host = "123".parse().unwrap();
724 assert!(host.is_domainname());
725 }
726
727 #[test]
728 fn test_mixed_alphanumeric_hostname() {
729 // Note: With parsing order IpAddr -> DomainName -> Hostname,
730 // letter-start labels are parsed as DomainName (not Hostname)
731 let host: Host = "api-v1.example.com".parse().unwrap();
732 assert!(host.is_domainname());
733 }
734
735 #[test]
736 fn test_from_hostname_variant() {
737 // Even though parsing prioritizes DomainName, we can still create
738 // Host variants directly from Hostname
739 let hostname = Hostname::new("example.com").unwrap();
740 let host = Host::from_hostname(hostname);
741 assert!(host.is_hostname());
742 assert!(
743 host.as_hostname()
744 .map(|h: &Hostname| h.is_localhost())
745 .unwrap_or(false)
746 == false
747 );
748 }
749}