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 std::error::Error;
302 /// # fn main() -> Result<(), Box<dyn Error>> {
303 /// use bare_types::net::Host;
304 ///
305 /// // IPv4 loopback
306 /// let host: Host = "127.0.0.1".parse()?;
307 /// assert!(host.is_localhost());
308 ///
309 /// // IPv6 loopback
310 /// let host: Host = "::1".parse()?;
311 /// assert!(host.is_localhost());
312 ///
313 /// // localhost domain name
314 /// let host: Host = "localhost".parse()?;
315 /// assert!(host.is_localhost());
316 ///
317 /// // Not localhost
318 /// let host: Host = "example.com".parse()?;
319 /// assert!(!host.is_localhost());
320 /// # Ok(())
321 /// # }
322 /// ```
323 #[must_use]
324 pub fn is_localhost(&self) -> bool {
325 match self {
326 Self::IpAddr(ip) => ip.is_loopback(),
327 Self::DomainName(domain) => domain.as_str() == "localhost",
328 Self::Hostname(hostname) => hostname.is_localhost(),
329 }
330 }
331
332 /// Parses a string into a `Host` with automatic type detection.
333 ///
334 /// The parsing follows this priority order:
335 /// 1. Try to parse as `IpAddr`
336 /// 2. Try to parse as `DomainName`
337 /// 3. Try to parse as `Hostname`
338 ///
339 /// # Errors
340 ///
341 /// Returns `HostError::InvalidInput` if the string cannot be parsed as
342 /// an IP address, domain name, or hostname.
343 ///
344 /// # Examples
345 ///
346 /// ```rust
347 /// use bare_types::net::Host;
348 ///
349 /// // IP address is parsed first
350 /// let host = Host::parse_str("192.168.1.1")?;
351 /// assert!(host.is_ipaddr());
352 ///
353 /// // Domain name (labels can start with digits)
354 /// let host = Host::parse_str("123.example.com")?;
355 /// assert!(host.is_domainname());
356 ///
357 /// // Domain name is also parsed before hostname for letter-start labels
358 /// let host = Host::parse_str("www.example.com")?;
359 /// assert!(host.is_domainname());
360 /// # Ok::<(), bare_types::net::HostError>(())
361 /// ```
362 pub fn parse_str(s: &str) -> Result<Self, HostError> {
363 if s.is_empty() {
364 return Err(HostError::InvalidInput);
365 }
366
367 // Try parsing as IpAddr first (highest priority)
368 if let Ok(ipaddr) = s.parse::<IpAddr>() {
369 return Ok(Self::IpAddr(ipaddr));
370 }
371
372 // Try parsing as DomainName (allows digit-start labels)
373 if let Ok(domain) = DomainName::new(s) {
374 return Ok(Self::DomainName(domain));
375 }
376
377 // Try parsing as Hostname (requires letter-start labels)
378 if let Ok(hostname) = Hostname::new(s) {
379 return Ok(Self::Hostname(hostname));
380 }
381
382 Err(HostError::InvalidInput)
383 }
384}
385
386impl FromStr for Host {
387 type Err = HostError;
388
389 fn from_str(s: &str) -> Result<Self, Self::Err> {
390 Self::parse_str(s)
391 }
392}
393
394impl fmt::Display for Host {
395 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
396 match self {
397 Self::IpAddr(ipaddr) => write!(f, "{ipaddr}"),
398 Self::DomainName(domain) => write!(f, "{domain}"),
399 Self::Hostname(hostname) => write!(f, "{hostname}"),
400 }
401 }
402}
403
404#[cfg(test)]
405mod tests {
406 use super::*;
407
408 #[test]
409 fn test_from_ipaddr() {
410 let ipaddr = "192.168.1.1".parse::<IpAddr>().unwrap();
411 let host = Host::from_ipaddr(ipaddr);
412 assert!(host.is_ipaddr());
413 assert!(!host.is_domainname());
414 assert!(!host.is_hostname());
415 }
416
417 #[test]
418 fn test_from_domainname() {
419 let domain = DomainName::new("example.com").unwrap();
420 let host = Host::from_domainname(domain);
421 assert!(!host.is_ipaddr());
422 assert!(host.is_domainname());
423 assert!(!host.is_hostname());
424 }
425
426 #[test]
427 fn test_from_hostname() {
428 let hostname = Hostname::new("example.com").unwrap();
429 let host = Host::from_hostname(hostname);
430 assert!(!host.is_ipaddr());
431 assert!(!host.is_domainname());
432 assert!(host.is_hostname());
433 }
434
435 #[test]
436 fn test_parse_ipv4() {
437 let host: Host = "192.168.1.1".parse().unwrap();
438 assert!(host.is_ipaddr());
439 assert_eq!(format!("{host}"), "192.168.1.1");
440 }
441
442 #[test]
443 fn test_parse_ipv6() {
444 let host: Host = "::1".parse().unwrap();
445 assert!(host.is_ipaddr());
446 assert_eq!(format!("{host}"), "::1");
447 }
448
449 #[test]
450 fn test_parse_domainname_digit_start() {
451 let host: Host = "123.example.com".parse().unwrap();
452 assert!(host.is_domainname());
453 assert_eq!(format!("{host}"), "123.example.com");
454 }
455
456 #[test]
457 fn test_parse_hostname_letter_start() {
458 // Note: With parsing order IpAddr -> DomainName -> Hostname,
459 // letter-start labels are parsed as DomainName (not Hostname)
460 // because DomainName is tried first and also accepts letter-start labels
461 let host: Host = "www.example.com".parse().unwrap();
462 assert!(host.is_domainname());
463 assert_eq!(format!("{host}"), "www.example.com");
464 }
465
466 #[test]
467 fn test_parse_priority_ipaddr_over_domainname() {
468 // "127.0.0.1" could be a valid domain name, but IP address takes priority
469 let host: Host = "127.0.0.1".parse().unwrap();
470 assert!(host.is_ipaddr());
471 }
472
473 #[test]
474 fn test_parse_priority_domainname_over_hostname() {
475 // "123.example.com" is valid as DomainName (digit start)
476 // but invalid as Hostname (must start with letter)
477 let host: Host = "123.example.com".parse().unwrap();
478 assert!(host.is_domainname());
479 assert!(!host.is_hostname());
480 }
481
482 #[test]
483 fn test_parse_str_empty() {
484 assert!(Host::parse_str("").is_err());
485 }
486
487 #[test]
488 fn test_parse_str_invalid() {
489 assert!(Host::parse_str("-invalid").is_err());
490 assert!(Host::parse_str("example..com").is_err());
491 }
492
493 #[test]
494 fn test_as_ipaddr() {
495 let host: Host = "192.168.1.1".parse().unwrap();
496 assert!(host.as_ipaddr().is_some());
497 assert!(host.as_domainname().is_none());
498 assert!(host.as_hostname().is_none());
499 }
500
501 #[test]
502 fn test_as_domainname() {
503 let host: Host = "123.example.com".parse().unwrap();
504 assert!(host.as_ipaddr().is_none());
505 assert!(host.as_domainname().is_some());
506 assert!(host.as_hostname().is_none());
507 }
508
509 #[test]
510 fn test_as_hostname() {
511 // Note: With parsing order IpAddr -> DomainName -> Hostname,
512 // letter-start labels are parsed as DomainName (not Hostname)
513 let host: Host = "www.example.com".parse().unwrap();
514 assert!(host.as_ipaddr().is_none());
515 assert!(host.as_domainname().is_some());
516 assert!(host.as_hostname().is_none());
517 }
518
519 #[test]
520 fn test_equality_ipaddr() {
521 let host1: Host = "192.168.1.1".parse().unwrap();
522 let host2: Host = "192.168.1.1".parse().unwrap();
523 let host3: Host = "192.168.1.2".parse().unwrap();
524
525 assert_eq!(host1, host2);
526 assert_ne!(host1, host3);
527 }
528
529 #[test]
530 fn test_equality_domainname() {
531 let host1: Host = "123.example.com".parse().unwrap();
532 let host2: Host = "123.example.com".parse().unwrap();
533 let host3: Host = "456.example.com".parse().unwrap();
534
535 assert_eq!(host1, host2);
536 assert_ne!(host1, host3);
537 }
538
539 #[test]
540 fn test_equality_hostname() {
541 // Note: With parsing order IpAddr -> DomainName -> Hostname,
542 // letter-start labels are parsed as DomainName (not Hostname)
543 let host1: Host = "www.example.com".parse().unwrap();
544 let host2: Host = "www.example.com".parse().unwrap();
545 let host3: Host = "api.example.com".parse().unwrap();
546
547 assert_eq!(host1, host2);
548 assert_ne!(host1, host3);
549 }
550
551 #[test]
552 fn test_equality_different_types() {
553 let host1: Host = "192.168.1.1".parse().unwrap();
554 let host2: Host = "www.example.com".parse().unwrap();
555
556 assert_ne!(host1, host2);
557 }
558
559 #[test]
560 fn test_clone() {
561 let host: Host = "www.example.com".parse().unwrap();
562 let host2 = host.clone();
563 assert_eq!(host, host2);
564 }
565
566 #[test]
567 fn test_display_ipaddr() {
568 let host: Host = "192.168.1.1".parse().unwrap();
569 assert_eq!(format!("{host}"), "192.168.1.1");
570 }
571
572 #[test]
573 fn test_display_domainname() {
574 let host: Host = "123.example.com".parse().unwrap();
575 assert_eq!(format!("{host}"), "123.example.com");
576 }
577
578 #[test]
579 fn test_display_hostname() {
580 let host: Host = "www.example.com".parse().unwrap();
581 assert_eq!(format!("{host}"), "www.example.com");
582 }
583
584 #[test]
585 fn test_debug() {
586 let host: Host = "www.example.com".parse().unwrap();
587 let debug = format!("{:?}", host);
588 // Note: With parsing order IpAddr -> DomainName -> Hostname,
589 // letter-start labels are parsed as DomainName (not Hostname)
590 assert!(debug.contains("DomainName"));
591 }
592
593 #[test]
594 fn test_hash() {
595 use core::hash::Hash;
596 use core::hash::Hasher;
597
598 #[derive(Default)]
599 struct SimpleHasher(u64);
600
601 impl Hasher for SimpleHasher {
602 fn finish(&self) -> u64 {
603 self.0
604 }
605
606 fn write(&mut self, bytes: &[u8]) {
607 for byte in bytes {
608 self.0 = self.0.wrapping_mul(31).wrapping_add(*byte as u64);
609 }
610 }
611 }
612
613 let host1: Host = "www.example.com".parse().unwrap();
614 let host2: Host = "www.example.com".parse().unwrap();
615 let host3: Host = "api.example.com".parse().unwrap();
616
617 let mut hasher1 = SimpleHasher::default();
618 let mut hasher2 = SimpleHasher::default();
619 let mut hasher3 = SimpleHasher::default();
620
621 host1.hash(&mut hasher1);
622 host2.hash(&mut hasher2);
623 host3.hash(&mut hasher3);
624
625 assert_eq!(hasher1.finish(), hasher2.finish());
626 assert_ne!(hasher1.finish(), hasher3.finish());
627 }
628
629 #[test]
630 fn test_parse_ipv4_private() {
631 let host: Host = "10.0.0.1".parse().unwrap();
632 assert!(host.is_ipaddr());
633 }
634
635 #[test]
636 fn test_parse_ipv6_loopback() {
637 let host: Host = "::1".parse().unwrap();
638 assert!(host.is_ipaddr());
639 }
640
641 #[test]
642 fn test_parse_ipv6_full() {
643 let host: Host = "2001:0db8:85a3:0000:0000:8a2e:0370:7334".parse().unwrap();
644 assert!(host.is_ipaddr());
645 }
646
647 #[test]
648 fn test_parse_domainname_numeric_label() {
649 let host: Host = "123.456.789".parse().unwrap();
650 assert!(host.is_domainname());
651 }
652
653 #[test]
654 fn test_parse_hostname_multi_label() {
655 // Note: With parsing order IpAddr -> DomainName -> Hostname,
656 // letter-start labels are parsed as DomainName (not Hostname)
657 let host: Host = "api.v1.example.com".parse().unwrap();
658 assert!(host.is_domainname());
659 }
660
661 #[test]
662 fn test_error_display() {
663 let err = HostError::InvalidInput;
664 assert_eq!(format!("{err}"), "invalid host");
665 }
666
667 #[test]
668 fn test_parse_str_method() {
669 let host = Host::parse_str("192.168.1.1").unwrap();
670 assert!(host.is_ipaddr());
671
672 // Note: With parsing order IpAddr -> DomainName -> Hostname,
673 // letter-start labels are parsed as DomainName (not Hostname)
674 let host = Host::parse_str("www.example.com").unwrap();
675 assert!(host.is_domainname());
676 }
677
678 #[test]
679 fn test_case_insensitive_hostname() {
680 // Note: With parsing order IpAddr -> DomainName -> Hostname,
681 // letter-start labels are parsed as DomainName (not Hostname)
682 let host1: Host = "WWW.EXAMPLE.COM".parse().unwrap();
683 let host2: Host = "www.example.com".parse().unwrap();
684 assert_eq!(host1, host2);
685 }
686
687 #[test]
688 fn test_case_insensitive_domainname() {
689 let host1: Host = "123.EXAMPLE.COM".parse().unwrap();
690 let host2: Host = "123.example.com".parse().unwrap();
691 assert_eq!(host1, host2);
692 }
693
694 #[test]
695 fn test_localhost_hostname() {
696 // Note: With parsing order IpAddr -> DomainName -> Hostname,
697 // letter-start labels are parsed as DomainName (not Hostname)
698 let host: Host = "localhost".parse().unwrap();
699 assert!(host.is_domainname());
700 }
701
702 #[test]
703 fn test_is_localhost() {
704 // IPv4 loopback
705 let host: Host = "127.0.0.1".parse().unwrap();
706 assert!(host.is_localhost());
707
708 // IPv6 loopback
709 let host: Host = "::1".parse().unwrap();
710 assert!(host.is_localhost());
711
712 // localhost domain name
713 let host: Host = "localhost".parse().unwrap();
714 assert!(host.is_localhost());
715
716 // Not localhost
717 let host: Host = "example.com".parse().unwrap();
718 assert!(!host.is_localhost());
719
720 let host: Host = "192.168.1.1".parse().unwrap();
721 assert!(!host.is_localhost());
722 }
723
724 #[test]
725 fn test_numeric_only_domainname() {
726 let host: Host = "123".parse().unwrap();
727 assert!(host.is_domainname());
728 }
729
730 #[test]
731 fn test_mixed_alphanumeric_hostname() {
732 // Note: With parsing order IpAddr -> DomainName -> Hostname,
733 // letter-start labels are parsed as DomainName (not Hostname)
734 let host: Host = "api-v1.example.com".parse().unwrap();
735 assert!(host.is_domainname());
736 }
737
738 #[test]
739 fn test_from_hostname_variant() {
740 // Even though parsing prioritizes DomainName, we can still create
741 // Host variants directly from Hostname
742 let hostname = Hostname::new("example.com").unwrap();
743 let host = Host::from_hostname(hostname);
744 assert!(host.is_hostname());
745 assert!(
746 host.as_hostname()
747 .map(|h: &Hostname| h.is_localhost())
748 .unwrap_or(false)
749 == false
750 );
751 }
752}