iri_string/components/
authority.rs1use crate::parser::trusted as trusted_parser;
4use crate::spec::Spec;
5use crate::types::RiReferenceStr;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14pub struct AuthorityComponents<'a> {
15 pub(crate) authority: &'a str,
17 pub(crate) host_start: usize,
19 pub(crate) host_end: usize,
21}
22
23impl<'a> AuthorityComponents<'a> {
24 pub fn from_iri<S: Spec>(iri: &'a RiReferenceStr<S>) -> Option<Self> {
26 iri.authority_str()
27 .map(trusted_parser::authority::decompose_authority)
28 }
29
30 #[must_use]
32 pub fn userinfo(&self) -> Option<&'a str> {
33 let userinfo_at = self.host_start.checked_sub(1)?;
34 debug_assert_eq!(self.authority.as_bytes()[userinfo_at], b'@');
35 Some(&self.authority[..userinfo_at])
36 }
37
38 #[inline]
40 #[must_use]
41 pub fn host(&self) -> &'a str {
42 &self.authority[self.host_start..self.host_end]
44 }
45
46 #[must_use]
48 pub fn port(&self) -> Option<&'a str> {
49 if self.host_end == self.authority.len() {
50 return None;
51 }
52 let port_colon = self.host_end;
53 debug_assert_eq!(self.authority.as_bytes()[port_colon], b':');
54 Some(&self.authority[(port_colon + 1)..])
55 }
56}
57
58#[cfg(test)]
59#[cfg(feature = "alloc")]
60mod tests {
61 use super::*;
62
63 #[cfg(all(feature = "alloc", not(feature = "std")))]
64 use alloc::string::String;
65
66 use crate::types::IriReferenceStr;
67
68 const USERINFO: &[&str] = &["", "user:password", "user"];
69
70 const PORT: &[&str] = &[
71 "",
72 "0",
73 "0000",
74 "80",
75 "1234567890123456789012345678901234567890",
76 ];
77
78 const HOST: &[&str] = &[
79 "",
80 "localhost",
81 "example.com",
82 "192.0.2.0",
83 "[2001:db8::1]",
84 "[2001:0db8:0:0:0:0:0:1]",
85 "[2001:0db8::192.0.2.255]",
86 "[v9999.this-is-futuristic-ip-address]",
87 ];
88
89 fn compose_to_relative_iri(userinfo: Option<&str>, host: &str, port: Option<&str>) -> String {
90 let mut buf = String::from("//");
91 if let Some(userinfo) = userinfo {
92 buf.push_str(userinfo);
93 buf.push('@');
94 }
95 buf.push_str(host);
96 if let Some(port) = port {
97 buf.push(':');
98 buf.push_str(port);
99 }
100 buf
101 }
102
103 #[test]
104 fn test_decompose_authority() {
105 for host in HOST.iter().copied() {
106 for userinfo in USERINFO.iter().map(|s| Some(*s)).chain(None) {
107 for port in PORT.iter().map(|s| Some(*s)).chain(None) {
108 let authority = compose_to_relative_iri(userinfo, host, port);
109 let authority =
110 IriReferenceStr::new(&authority).expect("test case should be valid");
111 let components = AuthorityComponents::from_iri(authority)
112 .expect("relative path composed for this test should contain authority");
113
114 assert_eq!(components.host(), host);
115 assert_eq!(components.userinfo(), userinfo);
116 assert_eq!(components.port(), port);
117 }
118 }
119 }
120 }
121}