Skip to main content

rama_net/address/
authority.rs

1use core::{
2    fmt,
3    net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
4};
5
6use crate::std::{
7    self as std,
8    borrow::{Cow, ToOwned},
9    string::String,
10    vec::Vec,
11};
12
13use super::{Domain, DomainAddress, Host, SocketAddress};
14use crate::address::{HostRef, HostWithOptPort, HostWithPort, OptPort, UserInfo, UserInfoRef};
15
16use rama_core::error::{BoxError, BoxErrorExt as _, ErrorContext};
17use rama_utils::macros::generate_set_and_with;
18
19/// A [`Host`] with optionally a port and/or user-info ([`UserInfo`]).
20///
21/// `user_info` is the raw RFC 3986 §3.2.1 view (opaque, possibly
22/// percent-encoded bytes — the *wire* layer). For an HTTP Basic-Auth
23/// credential (the *decoded, logical* layer) call [`UserInfo::to_basic`],
24/// which percent-decodes and validates; [`From<Basic>`](UserInfo) is the
25/// percent-encoding reverse. The two types are kept distinct on purpose.
26///
27/// ## Examples
28///
29/// - example.com
30/// - 127.0.0.1
31/// - example.com:80
32/// - 127.0.0.1:80
33/// - joe@example.com:80
34/// - joe:secret@example.com
35#[derive(Debug, Clone, PartialEq, Eq, Hash)]
36pub struct Authority {
37    pub user_info: Option<UserInfo>,
38    pub address: HostWithOptPort,
39}
40
41impl Authority {
42    /// Creates a new [`Authority`] from a [`HostWithOptPort`].
43    #[must_use]
44    #[inline(always)]
45    pub const fn new(addr: HostWithOptPort) -> Self {
46        Self {
47            address: addr,
48            user_info: None,
49        }
50    }
51
52    /// Creates a new [`Authority`] from a [`HostWithOptPort`] and user-info
53    /// ([`UserInfo`]).
54    ///
55    /// Not `const fn` — `UserInfo` wraps `Bytes` which has no const
56    /// constructor; use the builder ([`Self::with_user_info`]) plus
57    /// [`UserInfo::from_static`] for the const-friendly path.
58    #[must_use]
59    #[inline(always)]
60    pub fn new_with_user_info(addr: HostWithOptPort, user_info: UserInfo) -> Self {
61        Self {
62            address: addr,
63            user_info: Some(user_info),
64        }
65    }
66
67    /// Compile-time constructor for a domain-only [`Authority`]
68    /// (no user-info, no explicit port). Panics at compile time when
69    /// `s` isn't a valid domain.
70    #[must_use]
71    pub const fn from_static(s: &'static str) -> Self {
72        Self::new(HostWithOptPort::new(Host::from_static(s)))
73    }
74
75    /// creates a new local ipv4 [`Authority`] without a port.
76    ///
77    /// # Example
78    ///
79    /// ```
80    /// use rama_net::address::Authority;
81    ///
82    /// let addr = Authority::local_ipv4();
83    /// assert_eq!("127.0.0.1", addr.to_string());
84    /// ```
85    #[must_use]
86    #[inline(always)]
87    pub const fn local_ipv4() -> Self {
88        Self::new(HostWithOptPort::local_ipv4())
89    }
90
91    /// creates a new local ipv4 [`Authority`] with the given port
92    ///
93    /// # Example
94    ///
95    /// ```
96    /// use rama_net::address::Authority;
97    ///
98    /// let addr = Authority::local_ipv4_with_port(8080);
99    /// assert_eq!("127.0.0.1:8080", addr.to_string());
100    /// ```
101    #[must_use]
102    #[inline(always)]
103    pub const fn local_ipv4_with_port(port: u16) -> Self {
104        Self::new(HostWithOptPort::local_ipv4_with_port(port))
105    }
106
107    /// creates a new local ipv6 [`Authority`] without a port.
108    ///
109    /// # Example
110    ///
111    /// IPv6 addresses always render with `[…]` brackets even with no
112    /// port — see [`HostWithOptPort`]'s `Display` impl for the
113    /// rationale.
114    ///
115    /// ```
116    /// use rama_net::address::Authority;
117    ///
118    /// let addr = Authority::local_ipv6();
119    /// assert_eq!("[::1]", addr.to_string());
120    /// ```
121    #[must_use]
122    #[inline(always)]
123    pub const fn local_ipv6() -> Self {
124        Self::new(HostWithOptPort::local_ipv6())
125    }
126
127    /// creates a new local ipv6 [`Authority`] with the given port.
128    ///
129    /// # Example
130    ///
131    /// ```
132    /// use rama_net::address::Authority;
133    ///
134    /// let addr = Authority::local_ipv6_with_port(8080);
135    /// assert_eq!("[::1]:8080", addr.to_string());
136    /// ```
137    #[must_use]
138    #[inline(always)]
139    pub const fn local_ipv6_with_port(port: u16) -> Self {
140        Self::new(HostWithOptPort::local_ipv6_with_port(port))
141    }
142
143    /// creates a default ipv4 [`Authority`] without a port
144    ///
145    /// # Example
146    ///
147    /// ```
148    /// use rama_net::address::Authority;
149    ///
150    /// let addr = Authority::default_ipv4_with_port(8080);
151    /// assert_eq!("0.0.0.0:8080", addr.to_string());
152    /// ```
153    #[must_use]
154    #[inline(always)]
155    pub const fn default_ipv4() -> Self {
156        Self::new(HostWithOptPort::default_ipv4())
157    }
158
159    /// creates a default ipv4 [`Authority`] with the given port
160    ///
161    /// # Example
162    ///
163    /// ```
164    /// use rama_net::address::Authority;
165    ///
166    /// let addr = Authority::default_ipv4_with_port(8080);
167    /// assert_eq!("0.0.0.0:8080", addr.to_string());
168    /// ```
169    #[must_use]
170    #[inline(always)]
171    pub const fn default_ipv4_with_port(port: u16) -> Self {
172        Self::new(HostWithOptPort::default_ipv4_with_port(port))
173    }
174
175    /// creates a new default ipv6 [`Authority`] without a port.
176    ///
177    /// IPv6 addresses always render with `[…]` brackets even with no
178    /// port — see [`HostWithOptPort`]'s `Display` impl for the
179    /// rationale.
180    ///
181    /// # Example
182    ///
183    /// ```
184    /// use rama_net::address::Authority;
185    ///
186    /// let addr = Authority::default_ipv6();
187    /// assert_eq!("[::]", addr.to_string());
188    /// ```
189    #[must_use]
190    #[inline(always)]
191    pub const fn default_ipv6() -> Self {
192        Self::new(HostWithOptPort::default_ipv6())
193    }
194
195    /// creates a new default ipv6 [`Authority`] with the given port.
196    ///
197    /// # Example
198    ///
199    /// ```
200    /// use rama_net::address::Authority;
201    ///
202    /// let addr = Authority::default_ipv6_with_port(8080);
203    /// assert_eq!("[::]:8080", addr.to_string());
204    /// ```
205    #[must_use]
206    #[inline(always)]
207    pub const fn default_ipv6_with_port(port: u16) -> Self {
208        Self::new(HostWithOptPort::default_ipv6_with_port(port))
209    }
210
211    /// creates a new broadcast ipv4 [`Authority`] without a port
212    ///
213    /// # Example
214    ///
215    /// ```
216    /// use rama_net::address::Authority;
217    ///
218    /// let addr = Authority::broadcast_ipv4();
219    /// assert_eq!("255.255.255.255", addr.to_string());
220    /// ```
221    #[must_use]
222    #[inline(always)]
223    pub const fn broadcast_ipv4() -> Self {
224        Self::new(HostWithOptPort::broadcast_ipv4())
225    }
226
227    /// creates a new broadcast ipv4 [`Authority`] with the given port
228    ///
229    /// # Example
230    ///
231    /// ```
232    /// use rama_net::address::Authority;
233    ///
234    /// let addr = Authority::broadcast_ipv4_with_port(8080);
235    /// assert_eq!("255.255.255.255:8080", addr.to_string());
236    /// ```
237    #[must_use]
238    #[inline(always)]
239    pub const fn broadcast_ipv4_with_port(port: u16) -> Self {
240        Self::new(HostWithOptPort::broadcast_ipv4_with_port(port))
241    }
242
243    /// Creates a new example domain [`Authority`] without a port.
244    #[must_use]
245    #[inline(always)]
246    pub const fn example_domain() -> Self {
247        Self::new(HostWithOptPort::example_domain())
248    }
249
250    /// Creates a new example domain [`HostWithOptPort`] for the `http` default port.
251    #[must_use]
252    #[inline(always)]
253    pub const fn example_domain_http() -> Self {
254        Self::new(HostWithOptPort::example_domain_http())
255    }
256
257    /// Creates a new example domain [`HostWithOptPort`] for the `https` default port.
258    #[must_use]
259    #[inline(always)]
260    pub const fn example_domain_https() -> Self {
261        Self::new(HostWithOptPort::example_domain_https())
262    }
263
264    /// Creates a new example domain [`HostWithOptPort`] for the given port.
265    #[must_use]
266    #[inline(always)]
267    pub const fn example_domain_with_port(port: u16) -> Self {
268        Self::new(HostWithOptPort::example_domain_with_port(port))
269    }
270
271    /// Creates a new localhost domain [`HostWithOptPort`] without a port.
272    #[must_use]
273    #[inline(always)]
274    pub const fn localhost_domain() -> Self {
275        Self::new(HostWithOptPort::localhost_domain())
276    }
277
278    /// Creates a new localhost domain [`HostWithOptPort`] for the `http` default port.
279    #[must_use]
280    #[inline(always)]
281    pub const fn localhost_domain_http() -> Self {
282        Self::new(HostWithOptPort::localhost_domain_http())
283    }
284
285    /// Creates a new localhost domain [`HostWithOptPort`] for the `https` default port.
286    #[must_use]
287    #[inline(always)]
288    pub const fn localhost_domain_https() -> Self {
289        Self::new(HostWithOptPort::localhost_domain_https())
290    }
291
292    /// Creates a new localhost domain [`HostWithOptPort`] for the given port.
293    #[must_use]
294    #[inline(always)]
295    pub const fn localhost_domain_with_port(port: u16) -> Self {
296        Self::new(HostWithOptPort::localhost_domain_with_port(port))
297    }
298
299    generate_set_and_with! {
300        /// Set [`Host`] of [`Authority`]. Accepts any [`Into<Host>`] —
301        /// [`Domain`], [`IpAddr`](core::net::IpAddr), and so on.
302        pub fn host(mut self, host: impl Into<Host>) -> Self {
303            self.address.set_host(host.into());
304            self
305        }
306    }
307
308    generate_set_and_with! {
309        /// Set the port of [`Authority`]. Accepts `u16`, `OptPort`, or
310        /// `Option<u16>` via [`Into<OptPort>`]. Pass `OptPort::Unset`
311        /// to clear.
312        pub fn port(mut self, port: impl Into<OptPort>) -> Self {
313            self.address.port = port.into();
314            self
315        }
316    }
317
318    /// Relaxed view of the port — `Set(n) → Some(n)`, everything else
319    /// `None`. Use when the `Unset` vs `Empty` distinction doesn't matter.
320    #[must_use]
321    #[inline]
322    pub const fn port_u16(&self) -> Option<u16> {
323        self.address.port.as_u16()
324    }
325
326    generate_set_and_with! {
327        /// (un)set user-info ([`UserInfo`]) of [`Authority`]
328        pub fn user_info(mut self, user_info: Option<UserInfo>) -> Self {
329            self.user_info = user_info;
330            self
331        }
332    }
333
334    /// Borrowed view.
335    #[must_use]
336    #[inline]
337    pub fn view(&self) -> AuthorityRef<'_> {
338        AuthorityRef::from(self)
339    }
340}
341
342impl<'a> From<&'a Authority> for AuthorityRef<'a> {
343    fn from(a: &'a Authority) -> Self {
344        Self::new(
345            a.user_info.as_ref().map(UserInfoRef::from),
346            HostRef::from(&a.address.host),
347            a.address.port,
348        )
349    }
350}
351
352impl From<(Domain, u16)> for Authority {
353    #[inline(always)]
354    fn from((domain, port): (Domain, u16)) -> Self {
355        (Host::Name(domain), port).into()
356    }
357}
358
359impl From<(IpAddr, u16)> for Authority {
360    #[inline(always)]
361    fn from((ip, port): (IpAddr, u16)) -> Self {
362        (Host::Address(ip), port).into()
363    }
364}
365
366impl From<(Ipv4Addr, u16)> for Authority {
367    #[inline(always)]
368    fn from((ip, port): (Ipv4Addr, u16)) -> Self {
369        (Host::Address(IpAddr::V4(ip)), port).into()
370    }
371}
372
373impl From<([u8; 4], u16)> for Authority {
374    #[inline(always)]
375    fn from((ip, port): ([u8; 4], u16)) -> Self {
376        (Host::Address(IpAddr::V4(ip.into())), port).into()
377    }
378}
379
380impl From<(Ipv6Addr, u16)> for Authority {
381    #[inline(always)]
382    fn from((ip, port): (Ipv6Addr, u16)) -> Self {
383        (Host::Address(IpAddr::V6(ip)), port).into()
384    }
385}
386
387impl From<([u8; 16], u16)> for Authority {
388    #[inline(always)]
389    fn from((ip, port): ([u8; 16], u16)) -> Self {
390        (Host::Address(IpAddr::V6(ip.into())), port).into()
391    }
392}
393
394impl From<Host> for Authority {
395    #[inline(always)]
396    fn from(host: Host) -> Self {
397        Self::new(HostWithOptPort::new(host))
398    }
399}
400
401impl From<Domain> for Authority {
402    #[inline(always)]
403    fn from(domain: Domain) -> Self {
404        Host::Name(domain).into()
405    }
406}
407
408impl From<IpAddr> for Authority {
409    #[inline(always)]
410    fn from(ip: IpAddr) -> Self {
411        Host::Address(ip).into()
412    }
413}
414
415impl From<Ipv4Addr> for Authority {
416    #[inline(always)]
417    fn from(ip: Ipv4Addr) -> Self {
418        Host::Address(IpAddr::V4(ip)).into()
419    }
420}
421
422impl From<Ipv6Addr> for Authority {
423    #[inline(always)]
424    fn from(ip: Ipv6Addr) -> Self {
425        Host::Address(IpAddr::V6(ip)).into()
426    }
427}
428
429impl From<(Host, u16)> for Authority {
430    #[inline(always)]
431    fn from((host, port): (Host, u16)) -> Self {
432        Self::new(HostWithOptPort::new_with_port(host, port))
433    }
434}
435
436impl From<Authority> for Host {
437    #[inline(always)]
438    fn from(authority: Authority) -> Self {
439        authority.address.host
440    }
441}
442
443impl From<SocketAddr> for Authority {
444    #[inline(always)]
445    fn from(addr: SocketAddr) -> Self {
446        Self::new(HostWithOptPort::new_with_port(
447            Host::Address(addr.ip()),
448            addr.port(),
449        ))
450    }
451}
452
453impl From<&SocketAddr> for Authority {
454    #[inline(always)]
455    fn from(addr: &SocketAddr) -> Self {
456        Self::new(HostWithOptPort::new_with_port(
457            Host::Address(addr.ip()),
458            addr.port(),
459        ))
460    }
461}
462
463impl From<HostWithOptPort> for Authority {
464    #[inline(always)]
465    fn from(addr: HostWithOptPort) -> Self {
466        Self {
467            user_info: None,
468            address: addr,
469        }
470    }
471}
472
473impl From<Authority> for HostWithOptPort {
474    #[inline(always)]
475    fn from(addr: Authority) -> Self {
476        addr.address
477    }
478}
479
480impl From<HostWithPort> for Authority {
481    #[inline(always)]
482    fn from(addr: HostWithPort) -> Self {
483        Self {
484            user_info: None,
485            address: addr.into(),
486        }
487    }
488}
489
490impl From<SocketAddress> for Authority {
491    #[inline(always)]
492    fn from(addr: SocketAddress) -> Self {
493        let SocketAddress { ip_addr, port } = addr;
494        Self::new(HostWithOptPort::new_with_port(Host::Address(ip_addr), port))
495    }
496}
497
498impl From<&SocketAddress> for Authority {
499    #[inline(always)]
500    fn from(addr: &SocketAddress) -> Self {
501        Self::new(HostWithOptPort::new_with_port(
502            Host::Address(addr.ip_addr),
503            addr.port,
504        ))
505    }
506}
507
508impl From<DomainAddress> for Authority {
509    #[inline(always)]
510    fn from(addr: DomainAddress) -> Self {
511        let DomainAddress { domain, port } = addr;
512        Self::from((domain, port))
513    }
514}
515
516impl fmt::Display for Authority {
517    #[inline(always)]
518    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
519        fmt::Display::fmt(&self.view(), f)
520    }
521}
522
523impl core::str::FromStr for Authority {
524    type Err = BoxError;
525
526    fn from_str(s: &str) -> Result<Self, Self::Err> {
527        Self::try_from(s)
528    }
529}
530
531impl TryFrom<String> for Authority {
532    type Error = BoxError;
533
534    #[inline(always)]
535    fn try_from(s: String) -> Result<Self, Self::Error> {
536        try_from_maybe_borrowed_str(s.into())
537    }
538}
539
540impl TryFrom<&str> for Authority {
541    type Error = BoxError;
542
543    #[inline(always)]
544    fn try_from(s: &str) -> Result<Self, Self::Error> {
545        try_from_maybe_borrowed_str(s.into())
546    }
547}
548
549/// Reg-name fallback for inputs `Domain::try_from` rejects but the
550/// URI reg-name grammar accepts (pct-encoded / sub-delim / raw UTF-8).
551/// Validates against the same byte set as the URI parser without
552/// constructing a `Uri`.
553fn try_as_uninterpreted_host(host_str: &str) -> Result<Host, BoxError> {
554    let host = super::UninterpretedHost::try_from_reg_name_str(host_str)
555        .context("parse authority host as reg-name")?;
556    Ok(Host::Uninterpreted(host))
557}
558
559fn try_from_maybe_borrowed_str(maybe_borrowed: Cow<'_, str>) -> Result<Authority, BoxError> {
560    let mut s = maybe_borrowed.as_ref();
561
562    if s.is_empty() {
563        return Err(BoxError::from_static_str(
564            "empty string is invalid authority",
565        ));
566    }
567
568    // Split on the *last* `@`. See [`super::parse_utils::find_userinfo_split`]
569    // for the rationale (curl / browsers / `url` crate parity, plus the
570    // observation that `@` is not in the strict RFC 3986 userinfo grammar
571    // and so a permissive consumer that wanted to use it MUST place it
572    // before the boundary, not after).
573    let mut user_info = None;
574    if let Some(idx) = crate::address::parse_utils::find_userinfo_split(s.as_bytes()) {
575        // Graceful path: the last-`@` split deliberately leaves
576        // earlier `@`s inside the userinfo region (curl / browser /
577        // url-crate parity). `UserInfo::try_from(&str)` is strict and
578        // would reject those, so we do explicit control-byte
579        // screening here and bypass via `from_bytes_unchecked` —
580        // mirroring the URI parser's authority handler.
581        let ui_bytes = &s.as_bytes()[..idx];
582        if ui_bytes.iter().any(|&b| b < 0x20 || b == 0x7F) {
583            return Err(BoxError::from_static_str(
584                "userinfo contains control character",
585            ));
586        }
587        user_info = Some(UserInfo::from_bytes_unchecked(
588            rama_core::bytes::Bytes::copy_from_slice(ui_bytes),
589        ));
590        s = &s[idx + 1..];
591    }
592
593    let host;
594    let mut port = OptPort::Unset;
595
596    // Standalone bracketed IP-literal (no trailing port): `[::1]` or
597    // `[v1.fe80::a]`. Without this fast-path the colon-split below
598    // treats the final `:` inside the address as a port separator.
599    if s.starts_with('[') && s.ends_with(']') {
600        let inside = &s[1..s.len() - 1];
601        if inside.is_empty() {
602            return Err(BoxError::from_static_str("empty bracketed IP-literal"));
603        }
604        // IPvFuture: `[v1.xxx]` — stored as `Uninterpreted(bracketed=true)`
605        // verbatim, matching the URI authority parser's shape.
606        if matches!(inside.as_bytes().first(), Some(b'v' | b'V')) {
607            crate::uri::parser::authority::validate_ipvfuture(inside.as_bytes())
608                .map_err(BoxError::from)
609                .context("parse bracketed IPvFuture")?;
610            return Ok(Authority {
611                user_info,
612                address: HostWithOptPort {
613                    host: Host::Uninterpreted(super::UninterpretedHost::from_validated_bytes(
614                        rama_core::bytes::Bytes::copy_from_slice(inside.as_bytes()),
615                        true,
616                    )),
617                    port: OptPort::Unset,
618                },
619            });
620        }
621        if crate::address::parse_utils::ipv6_bracket_has_zone(inside.as_bytes()) {
622            return Err(BoxError::from_static_str(
623                "ipv6 zone identifiers (RFC 6874) are not supported",
624            ));
625        }
626        let addr = inside
627            .parse::<Ipv6Addr>()
628            .context("parse bracketed ipv6 authority host without port")?;
629        return Ok(Authority {
630            user_info,
631            address: HostWithOptPort {
632                host: Host::Address(IpAddr::V6(addr)),
633                port: OptPort::Unset,
634            },
635        });
636    }
637
638    if let Some(last_colon) = s.as_bytes().iter().rposition(|c| *c == b':') {
639        let first_part = &s[..last_colon];
640        if first_part.contains(':') {
641            // ipv6 (bare or bracketed, possibly with trailing port)
642            let (addr, parsed_port) =
643                crate::address::parse_utils::parse_bracketed_ipv6_with_port(s, last_colon)
644                    .context("authority: parse ipv6 host")?;
645            host = Host::Address(IpAddr::V6(addr));
646            port = parsed_port;
647        } else {
648            // Reject `:port` (empty host before colon). The URI authority
649            // parser rejects the same shape — keep the eager paths
650            // symmetric.
651            if first_part.is_empty() {
652                return Err(BoxError::from_static_str(
653                    "empty host before ':port' is invalid",
654                ));
655            }
656            let port_bytes = &s.as_bytes()[last_colon + 1..];
657            port = if port_bytes.is_empty() {
658                OptPort::Empty
659            } else {
660                OptPort::Set(
661                    crate::address::parse_utils::parse_port_bytes(port_bytes)
662                        .context("parse authority port string as u16")?,
663                )
664            };
665
666            // try ipv4 first, domain afterwards, then Uninterpreted fallback
667            host = if let Ok(ipv4) = first_part.parse::<Ipv4Addr>() {
668                Host::Address(IpAddr::V4(ipv4))
669            } else {
670                let mut owned_vec = if user_info.is_some() {
671                    s.as_bytes().to_vec()
672                } else {
673                    maybe_borrowed.into_owned().into_bytes()
674                };
675                owned_vec.truncate(last_colon);
676                let owned_str = String::from_utf8(owned_vec)
677                    .context("interpret authority host as utf-8 str")?;
678                match Domain::try_from(owned_str.as_str()) {
679                    Ok(domain) => Host::Name(domain),
680                    // Pct-encoded reg-name, sub-delim reg-name, etc. — the
681                    // URI parser accepts these and the `Host` enum can
682                    // represent them. Route through the URI authority-
683                    // form parser so the byte-set validation matches.
684                    Err(_) => try_as_uninterpreted_host(&owned_str)?,
685                }
686            };
687        };
688    } else {
689        // no port, so either IpAddr, Domain, or Uninterpreted fallback
690        host = if let Ok(ip) = s.parse::<IpAddr>() {
691            Host::Address(ip)
692        } else {
693            let owned_str = if user_info.is_some() {
694                s.to_owned()
695            } else {
696                maybe_borrowed.into_owned()
697            };
698            match Domain::try_from(owned_str.as_str()) {
699                Ok(domain) => Host::Name(domain),
700                Err(_) => try_as_uninterpreted_host(&owned_str)?,
701            }
702        };
703    }
704
705    Ok(Authority {
706        user_info,
707        address: HostWithOptPort { host, port },
708    })
709}
710
711impl TryFrom<Vec<u8>> for Authority {
712    type Error = BoxError;
713
714    fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
715        let s = String::from_utf8(bytes).context("parse authority from bytes")?;
716        s.try_into()
717    }
718}
719
720impl TryFrom<&[u8]> for Authority {
721    type Error = BoxError;
722
723    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
724        let s = core::str::from_utf8(bytes).context("parse authority from bytes")?;
725        s.try_into()
726    }
727}
728
729/// Borrowed view of an [`Authority`] — userinfo + host + port, each
730/// borrowing into the underlying buffer. Mirrors the [`HostRef`] /
731/// [`DomainRef`](crate::address::DomainRef) /
732/// [`UserInfoRef`](super::UserInfoRef) pattern for the rest of the
733/// address types.
734///
735/// Constructed by [`Uri::authority`](crate::uri::Uri::authority) and
736/// — eventually — by [`Authority`]'s own borrow accessor.
737///
738/// `PartialEq` / `Eq` / `Hash` follow the same component-wise rules as
739/// the owned [`Authority`] (case-insensitive host via `HostRef`'s impl,
740/// strict equality on userinfo / port), so the two types are
741/// interchangeable as collection keys.
742#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
743pub struct AuthorityRef<'a> {
744    pub(crate) userinfo: Option<UserInfoRef<'a>>,
745    pub(crate) host: HostRef<'a>,
746    pub(crate) port: OptPort,
747}
748
749impl<'a> AuthorityRef<'a> {
750    /// `pub(crate)` constructor — only [`Uri::authority`] and
751    /// internal helpers should build one.
752    #[must_use]
753    #[inline]
754    pub(crate) const fn new(
755        userinfo: Option<UserInfoRef<'a>>,
756        host: HostRef<'a>,
757        port: OptPort,
758    ) -> Self {
759        Self {
760            userinfo,
761            host,
762            port,
763        }
764    }
765
766    /// Userinfo component, or `None` if the authority has no `@`
767    /// (RFC 3986 §3.2.1 userinfo is optional).
768    ///
769    /// `Some("")` (an empty userinfo before the `@`) is distinct from
770    /// `None` — preserved for wire fidelity.
771    #[must_use]
772    pub fn userinfo(&self) -> Option<UserInfoRef<'a>> {
773        self.userinfo
774    }
775
776    /// The host component. Always present — every well-formed
777    /// authority has a host.
778    #[must_use]
779    pub fn host(&self) -> HostRef<'a> {
780        self.host
781    }
782
783    /// The port marker. Distinguishes wire-level `Unset` / `Empty` /
784    /// `Set(u16)`. Most callers want [`port_u16`](Self::port_u16) which
785    /// collapses to `Option<u16>`.
786    #[must_use]
787    pub const fn port(&self) -> OptPort {
788        self.port
789    }
790
791    /// Relaxed view of the port — `Set(n) → Some(n)`, everything else
792    /// `None`. Use when the `Unset` vs `Empty` distinction doesn't matter.
793    #[must_use]
794    #[inline]
795    pub const fn port_u16(&self) -> Option<u16> {
796        self.port.as_u16()
797    }
798
799    /// Promote this borrowed view to an owned [`Authority`] by copying
800    /// the underlying bytes. Mirrors the `into_owned` family on the
801    /// other borrowed views.
802    #[must_use]
803    pub fn into_owned(self) -> Authority {
804        Authority {
805            user_info: self.userinfo.map(|u| u.into_owned()),
806            address: HostWithOptPort {
807                host: self.host.into_owned(),
808                port: self.port,
809            },
810        }
811    }
812}
813
814impl fmt::Display for AuthorityRef<'_> {
815    /// Renders `[userinfo@]host[:port]`. Matches [`Authority`]'s
816    /// `Display` byte-for-byte.
817    ///
818    /// IPv6 hosts are **always** bracketed (`[ip]`), regardless of
819    /// whether a port follows — same rule [`HostWithOptPort`]'s
820    /// `Display` uses. Without brackets, `::1:8080` would be ambiguous between
821    /// "address `::1` + port `8080`" and "address `::1:8080`, no
822    /// port". We bracket inline here rather than delegating to
823    /// `HostRef`'s `Display` because that formatter is a standalone-
824    /// host renderer that doesn't compose with `:port`.
825    ///
826    /// Note: userinfo emission is the *Display* contract — wire writers
827    /// for HTTP request-targets strip userinfo separately
828    /// (`write_http_authority_form` / `write_h2_authority` on
829    /// [`crate::uri::Uri`]).
830    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
831        if let Some(ui) = self.userinfo {
832            write!(f, "{ui}@")?;
833        }
834        match self.host {
835            HostRef::Address(IpAddr::V6(ip)) => write!(f, "[{ip}]")?,
836            _ => self.host.fmt(f)?,
837        }
838        self.port.fmt(f)
839    }
840}
841
842use rama_utils::macros::serde_str::impl_serde_str;
843
844impl_serde_str!(display Authority);
845
846#[cfg(test)]
847mod tests {
848    use super::*;
849
850    #[expect(clippy::needless_pass_by_value)]
851    fn assert_eq(
852        s: &str,
853        authority: Authority,
854        user_info: Option<UserInfo>,
855        host: &str,
856        port: Option<u16>,
857    ) {
858        assert_eq!(authority.user_info, user_info, "parsing: {s}");
859        assert_eq!(authority.address.host, host, "parsing: {s}");
860        assert_eq!(authority.address.port.as_u16(), port, "parsing: {s}");
861    }
862
863    #[test]
864    fn test_parse_valid() {
865        for (s, (expected_user_info, expected_host, expected_port)) in [
866            ("example.com", (None, "example.com", None)),
867            (
868                "user@example.com",
869                (Some(UserInfo::from_static("user")), "example.com", None),
870            ),
871            (
872                "user:password@example.com",
873                (
874                    Some(UserInfo::from_static("user:password")),
875                    "example.com",
876                    None,
877                ),
878            ),
879            ("example.com:80", (None, "example.com", Some(80))),
880            // Empty port (`host:`) — `OptPort::Empty`, surfaces as
881            // `None` in the relaxed `.as_u16()` view. See the dedicated
882            // round-trip test below for the Display check.
883            ("example.com:", (None, "example.com", None)),
884            (
885                "user@example.com:80",
886                (Some(UserInfo::from_static("user")), "example.com", Some(80)),
887            ),
888            (
889                "user:secret@example.com:80",
890                (
891                    Some(UserInfo::from_static("user:secret")),
892                    "example.com",
893                    Some(80),
894                ),
895            ),
896            (
897                "user@::1",
898                (Some(UserInfo::from_static("user")), "::1", None),
899            ),
900            (
901                "user:password@::1",
902                (Some(UserInfo::from_static("user:password")), "::1", None),
903            ),
904            ("::1", (None, "::1", None)),
905            ("[::1]:80", (None, "::1", Some(80))),
906            (
907                "user@[::1]:80",
908                (Some(UserInfo::from_static("user")), "::1", Some(80)),
909            ),
910            (
911                "user:password@[::1]:80",
912                (
913                    Some(UserInfo::from_static("user:password")),
914                    "::1",
915                    Some(80),
916                ),
917            ),
918            ("127.0.0.1", (None, "127.0.0.1", None)),
919            (
920                "user@127.0.0.1",
921                (Some(UserInfo::from_static("user")), "127.0.0.1", None),
922            ),
923            (
924                "user:password@127.0.0.1",
925                (
926                    Some(UserInfo::from_static("user:password")),
927                    "127.0.0.1",
928                    None,
929                ),
930            ),
931            ("127.0.0.1:80", (None, "127.0.0.1", Some(80))),
932            (
933                "user@127.0.0.1:80",
934                (Some(UserInfo::from_static("user")), "127.0.0.1", Some(80)),
935            ),
936            (
937                "user:secret@127.0.0.1:80",
938                (
939                    Some(UserInfo::from_static("user:secret")),
940                    "127.0.0.1",
941                    Some(80),
942                ),
943            ),
944            (
945                "2001:db8:3333:4444:5555:6666:7777:8888",
946                (None, "2001:db8:3333:4444:5555:6666:7777:8888", None),
947            ),
948            (
949                "user@2001:db8:3333:4444:5555:6666:7777:8888",
950                (
951                    Some(UserInfo::from_static("user")),
952                    "2001:db8:3333:4444:5555:6666:7777:8888",
953                    None,
954                ),
955            ),
956            (
957                "user:secret@2001:db8:3333:4444:5555:6666:7777:8888",
958                (
959                    Some(UserInfo::from_static("user:secret")),
960                    "2001:db8:3333:4444:5555:6666:7777:8888",
961                    None,
962                ),
963            ),
964            (
965                "[2001:db8:3333:4444:5555:6666:7777:8888]:80",
966                (None, "2001:db8:3333:4444:5555:6666:7777:8888", Some(80)),
967            ),
968            (
969                "user@[2001:db8:3333:4444:5555:6666:7777:8888]:80",
970                (
971                    Some(UserInfo::from_static("user")),
972                    "2001:db8:3333:4444:5555:6666:7777:8888",
973                    Some(80),
974                ),
975            ),
976            (
977                "user:secret@[2001:db8:3333:4444:5555:6666:7777:8888]:80",
978                (
979                    Some(UserInfo::from_static("user:secret")),
980                    "2001:db8:3333:4444:5555:6666:7777:8888",
981                    Some(80),
982                ),
983            ),
984        ] {
985            let msg = format!("parsing '{s}'");
986
987            assert_eq(
988                s,
989                s.parse().expect(&msg),
990                expected_user_info.clone(),
991                expected_host,
992                expected_port,
993            );
994            assert_eq(
995                s,
996                s.try_into().expect(&msg),
997                expected_user_info.clone(),
998                expected_host,
999                expected_port,
1000            );
1001            assert_eq(
1002                s,
1003                s.to_owned().try_into().expect(&msg),
1004                expected_user_info.clone(),
1005                expected_host,
1006                expected_port,
1007            );
1008            assert_eq(
1009                s,
1010                s.as_bytes().try_into().expect(&msg),
1011                expected_user_info.clone(),
1012                expected_host,
1013                expected_port,
1014            );
1015            assert_eq(
1016                s,
1017                s.as_bytes().to_vec().try_into().expect(&msg),
1018                expected_user_info.clone(),
1019                expected_host,
1020                expected_port,
1021            );
1022        }
1023    }
1024
1025    #[test]
1026    fn test_parse_invalid() {
1027        for s in [
1028            "",
1029            // Empty host with port — eager and lazy paths agree on
1030            // rejection.
1031            ":80",
1032            ":foo@:80",
1033            // Empty bracketed IP-literal.
1034            "[]",
1035            "[2001:db8:3333:4444:5555:6666:7777:8888",
1036            "2001:db8:3333:4444:5555:6666:7777:8888]",
1037            "example.com:-1",
1038            "example.com:999999",
1039            "[127.0.0.1]:80",
1040            "2001:db8:3333:4444:5555:6666:7777:8888:80",
1041            // IPv6 zone identifiers (RFC 9844 `%25en0` wire form) — rejected
1042            // by both eager and lazy paths with the same `parse_utils`
1043            // helper.
1044            "[fe80::1%25en0]",
1045            "[fe80::1%25en0]:8080",
1046            "user@[fe80::1%25en0]:8080",
1047        ] {
1048            let msg = format!("parsing '{s}'");
1049            assert!(s.parse::<Authority>().is_err(), "{msg}");
1050            assert!(Authority::try_from(s).is_err(), "{msg}");
1051            assert!(Authority::try_from(s.to_owned()).is_err(), "{msg}");
1052            assert!(Authority::try_from(s.as_bytes()).is_err(), "{msg}");
1053            assert!(Authority::try_from(s.as_bytes().to_vec()).is_err(), "{msg}");
1054        }
1055    }
1056
1057    #[test]
1058    fn ipv6_zone_rejection_has_clear_message() {
1059        // The eager path now surfaces a specific message instead of letting
1060        // `Ipv6Addr::parse` fail opaquely on `%25`. Consumers can match on
1061        // the substring "zone identifiers" for diagnostics.
1062        let err = Authority::try_from("[fe80::1%25en0]:8080").unwrap_err();
1063        let msg = format!("{err}");
1064        assert!(
1065            msg.contains("zone identifier") || msg.contains("zone identifiers"),
1066            "expected zone-identifier message, got: {msg}"
1067        );
1068    }
1069
1070    #[test]
1071    fn test_parse_display() {
1072        for (s, expected) in [
1073            ("example.com", "example.com"),
1074            ("user@example.com", "user@example.com"),
1075            ("user:secret@example.com", "user:secret@example.com"),
1076            ("example.com:80", "example.com:80"),
1077            ("user@example.com:80", "user@example.com:80"),
1078            ("user:secret@example.com:80", "user:secret@example.com:80"),
1079            ("[::1]:80", "[::1]:80"),
1080            ("user@[::1]:80", "user@[::1]:80"),
1081            ("secret:user@[::1]:80", "secret:user@[::1]:80"),
1082            // IPv6 hosts ALWAYS render with `[…]` brackets — even
1083            // when no port is present — to avoid the `::1:8080`
1084            // ambiguity. See `HostWithOptPort::Display` for the
1085            // single-source-of-truth rationale.
1086            ("::1", "[::1]"),
1087            ("user@::1", "user@[::1]"),
1088            ("user:secret@::1", "user:secret@[::1]"),
1089            ("127.0.0.1:80", "127.0.0.1:80"),
1090            ("user@127.0.0.1:80", "user@127.0.0.1:80"),
1091            ("user:secret@127.0.0.1:80", "user:secret@127.0.0.1:80"),
1092            ("127.0.0.1", "127.0.0.1"),
1093            ("user@127.0.0.1", "user@127.0.0.1"),
1094            ("user:secret@127.0.0.1", "user:secret@127.0.0.1"),
1095        ] {
1096            let msg = format!("parsing '{s}'");
1097            let authority: Authority = s.parse().expect(&msg);
1098            assert_eq!(authority.to_string(), expected, "{msg}");
1099        }
1100    }
1101
1102    /// Regression: when an authority contains multiple `@`, the userinfo
1103    /// section runs up to the *last* `@` (RFC 3986 §3.2 / §3.2.1).
1104    /// Splitting on the first `@` mis-parsed `user@name:pass@host:80` as
1105    /// userinfo=`user`, host=`name:pass@host` and rejected it.
1106    #[test]
1107    fn regression_authority_userinfo_splits_on_last_at() {
1108        let auth = Authority::try_from("user@name:pass@example.com:80").unwrap();
1109        assert_eq!(auth.address.host, "example.com");
1110        assert_eq!(auth.address.port, OptPort::Set(80));
1111        let ui = auth.user_info.as_ref().expect("userinfo present");
1112        let (user, pass) = ui.split_user_password();
1113        assert_eq!(user, b"user@name");
1114        assert_eq!(pass, Some(&b"pass"[..]));
1115    }
1116
1117    /// Regression: pct-encoded reg-names are accepted by both the URI
1118    /// parser and the eager `Authority::try_from` path — uniform host
1119    /// shape across the public API.
1120    #[test]
1121    fn authority_try_from_accepts_pct_encoded_reg_name() {
1122        let from_uri = crate::uri::Uri::parse_authority_form("exa%6Dple.com")
1123            .unwrap()
1124            .authority()
1125            .unwrap()
1126            .into_owned();
1127        let direct = Authority::try_from("exa%6Dple.com").unwrap();
1128        assert_eq!(direct, from_uri);
1129        // ...and with a port.
1130        let from_uri_p = crate::uri::Uri::parse_authority_form("exa%6Dple.com:443")
1131            .unwrap()
1132            .authority()
1133            .unwrap()
1134            .into_owned();
1135        let direct_p = Authority::try_from("exa%6Dple.com:443").unwrap();
1136        assert_eq!(direct_p, from_uri_p);
1137    }
1138
1139    /// `:port` with empty host is rejected at the eager parser, matching
1140    /// the URI authority-form parser's behavior. Without this the eager
1141    /// and lazy paths disagree.
1142    #[test]
1143    fn authority_try_from_rejects_empty_host_with_port() {
1144        Authority::try_from(":80").unwrap_err();
1145        Authority::try_from(":foo@:80").unwrap_err();
1146        // Confirm the URI parser agrees.
1147        crate::uri::Uri::parse_authority_form(":80").unwrap_err();
1148    }
1149
1150    /// Standalone bracketed IPvFuture (`[vN.X]`) parses as
1151    /// `Host::Uninterpreted(bracketed=true)`, mirroring the URI parser.
1152    #[test]
1153    fn authority_try_from_bracketed_ipvfuture() {
1154        let direct = Authority::try_from("[v1.fe80::a]").unwrap();
1155        let from_uri = crate::uri::Uri::parse_authority_form("[v1.fe80::a]")
1156            .unwrap()
1157            .authority()
1158            .unwrap()
1159            .into_owned();
1160        assert_eq!(direct, from_uri);
1161        assert!(matches!(direct.address.host, Host::Uninterpreted(_)));
1162    }
1163
1164    /// Standalone bracketed IPv6 (no trailing port) parses as a typed
1165    /// `Host::Address`, not as `Host::Uninterpreted`.
1166    #[test]
1167    fn authority_try_from_bracketed_ipv6_no_port_is_typed_address() {
1168        let auth = Authority::try_from("[::1]").unwrap();
1169        assert!(
1170            matches!(auth.address.host, Host::Address(IpAddr::V6(_))),
1171            "expected typed IPv6 Address, got {:?}",
1172            auth.address.host
1173        );
1174        assert_eq!(auth.address.port, OptPort::Unset);
1175        // Display round-trips with brackets.
1176        assert_eq!(auth.to_string(), "[::1]");
1177    }
1178
1179    /// Regression: RFC 6874 IPv6 zone-ids must never be accepted in an
1180    /// authority position. See `host::tests::regression_host_rejects_ipv6_zone_id`
1181    /// for the rationale; this guards the higher-level `Authority` entry
1182    /// points so a future change to lower-level parsing can't silently
1183    /// re-allow them.
1184    #[test]
1185    fn regression_authority_rejects_ipv6_zone_id() {
1186        for input in [
1187            "[fe80::1%eth0]:80",
1188            "[fe80::1%25eth0]:80",
1189            "user@[fe80::1%eth0]:80",
1190        ] {
1191            assert!(
1192                Authority::try_from(input).is_err(),
1193                "authority should reject zone-id input {input:?}",
1194            );
1195        }
1196    }
1197
1198    // ---- AuthorityRef::Display parity -----------------------
1199
1200    #[test]
1201    fn authority_ref_display_matches_owned() {
1202        for input in [
1203            "example.com",
1204            "example.com:443",
1205            "user@example.com:80",
1206            "user:secret@example.com:8080",
1207            "127.0.0.1:8080",
1208            "[2001:db8::1]:443",
1209        ] {
1210            let owned: Authority = input.parse().unwrap();
1211            let ref_view = AuthorityRef::new(
1212                owned.user_info.as_ref().map(UserInfoRef::from),
1213                HostRef::from(&owned.address.host),
1214                owned.address.port,
1215            );
1216            assert_eq!(
1217                ref_view.to_string(),
1218                owned.to_string(),
1219                "AuthorityRef Display must match Authority for {input:?}"
1220            );
1221        }
1222    }
1223}