diff --git a/Cargo.toml b/Cargo.toml index 3842f5d3..1c34e993 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,6 @@ edition = "2018" diesel = { version = "1.4.3", optional = true } rand = "0.7.2" serde = "1.0.102" -url = "2.1.0" [dev-dependencies] serde_json = "1.0.41" diff --git a/src/diesel_integration.rs b/src/diesel_integration.rs index 2daf80c4..92194179 100644 --- a/src/diesel_integration.rs +++ b/src/diesel_integration.rs @@ -16,7 +16,7 @@ macro_rules! diesel_impl { DB: Backend, { fn to_sql(&self, out: &mut Output<'_, W, DB>) -> SerializeResult { - ToSql::::to_sql(&self.to_string(), out) + ToSql::::to_sql(self.as_ref(), out) } } diff --git a/src/error.rs b/src/error.rs index 397bd7be..087a8200 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,11 +1,6 @@ //! Error conditions. -use std::{ - error::Error as StdError, - fmt::{Display, Formatter, Result as FmtResult}, -}; - -use url::ParseError; +use std::fmt::{self, Display, Formatter}; /// An error encountered when trying to parse an invalid ID string. #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] @@ -14,6 +9,8 @@ pub enum Error { /// /// Only relevant for user IDs. InvalidCharacters, + /// The localpart of the ID string is not valid (because it is empty). + InvalidLocalPart, /// The domain part of the the ID string is not a valid IP address or DNS name. InvalidHost, /// The ID exceeds 255 bytes (or 32 codepoints for a room version ID.) @@ -27,9 +24,10 @@ pub enum Error { } impl Display for Error { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - let message = match *self { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let message = match self { Error::InvalidCharacters => "localpart contains invalid characters", + Error::InvalidLocalPart => "localpart is empty", Error::InvalidHost => "server name is not a valid IP address or domain name", Error::MaximumLengthExceeded => "ID exceeds 255 bytes", Error::MinimumLengthNotSatisfied => "ID must be at least 4 characters", @@ -41,10 +39,4 @@ impl Display for Error { } } -impl StdError for Error {} - -impl From for Error { - fn from(_: ParseError) -> Self { - Error::InvalidHost - } -} +impl std::error::Error for Error {} diff --git a/src/event_id.rs b/src/event_id.rs index e9eb73e6..97c1545a 100644 --- a/src/event_id.rs +++ b/src/event_id.rs @@ -1,16 +1,11 @@ //! Matrix event identifiers. -use std::{ - convert::TryFrom, - fmt::{Display, Formatter, Result as FmtResult}, -}; +use std::{borrow::Cow, convert::TryFrom, num::NonZeroU8}; #[cfg(feature = "diesel")] use diesel::sql_types::Text; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use url::Host; -use crate::{deserialize_id, display, error::Error, generate_localpart, parse_id}; +use crate::{error::Error, generate_localpart, parse_id, validate_id}; /// A Matrix event ID. /// @@ -31,45 +26,26 @@ use crate::{deserialize_id, display, error::Error, generate_localpart, parse_id} /// # use ruma_identifiers::EventId; /// // Original format /// assert_eq!( -/// EventId::try_from("$h29iv0s8:example.com").unwrap().to_string(), +/// EventId::try_from("$h29iv0s8:example.com").unwrap().as_ref(), /// "$h29iv0s8:example.com" /// ); /// // Room version 3 format /// assert_eq!( -/// EventId::try_from("$acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk").unwrap().to_string(), +/// EventId::try_from("$acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk").unwrap().as_ref(), /// "$acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk" /// ); /// // Room version 4 format /// assert_eq!( -/// EventId::try_from("$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg").unwrap().to_string(), +/// EventId::try_from("$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg").unwrap().as_ref(), /// "$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg" /// ); /// ``` -#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[derive(Clone, Debug)] #[cfg_attr(feature = "diesel", derive(FromSqlRow, QueryId, AsExpression, SqlType))] #[cfg_attr(feature = "diesel", sql_type = "Text")] -pub struct EventId(Format); - -/// Different event ID formats from the different Matrix room versions. -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -enum Format { - /// The original format as used by Matrix room versions 1 and 2. - Original(Original), - /// The format used by Matrix room version 3. - Base64(String), - /// The format used by Matrix room version 4. - UrlSafeBase64(String), -} - -/// An event in the original format as used by Matrix room versions 1 and 2. -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -struct Original { - /// The hostname of the homeserver. - pub hostname: Host, - /// The event's unique ID. - pub localpart: String, - /// The network port of the homeserver. - pub port: u16, +pub struct EventId { + full_id: String, + colon_idx: Option, } impl EventId { @@ -77,86 +53,39 @@ impl EventId { /// of 18 random ASCII characters. This should only be used for events in the original format /// as used by Matrix room versions 1 and 2. /// - /// Fails if the homeserver cannot be parsed as a valid host. + /// Does not currently ever fail, but may fail in the future if the homeserver cannot be parsed + /// parsed as a valid host. pub fn new(homeserver_host: &str) -> Result { - let event_id = format!("${}:{}", generate_localpart(18), homeserver_host); - let (localpart, host, port) = parse_id('$', &event_id)?; + let full_id = format!("${}:{}", generate_localpart(18), homeserver_host); - Ok(Self(Format::Original(Original { - hostname: host, - localpart: localpart.to_string(), - port, - }))) + Ok(Self { + full_id, + colon_idx: NonZeroU8::new(19), + }) } - /// Returns a `Host` for the event ID, containing the server name (minus the port) of the + /// Returns the host of the event ID, containing the server name (including the port) of the /// originating homeserver. Only applicable to events in the original format as used by Matrix /// room versions 1 and 2. - /// - /// The host can be either a domain name, an IPv4 address, or an IPv6 address. - pub fn hostname(&self) -> Option<&Host> { - if let Format::Original(original) = &self.0 { - Some(&original.hostname) - } else { - None - } + pub fn hostname(&self) -> Option<&str> { + self.colon_idx + .map(|idx| &self.full_id[idx.get() as usize + 1..]) } /// Returns the event's unique ID. For the original event format as used by Matrix room /// versions 1 and 2, this is the "localpart" that precedes the homeserver. For later formats, /// this is the entire ID without the leading $ sigil. pub fn localpart(&self) -> &str { - match &self.0 { - Format::Original(original) => &original.localpart, - Format::Base64(id) | Format::UrlSafeBase64(id) => id, - } - } + let idx = match self.colon_idx { + Some(idx) => idx.get() as usize, + None => self.full_id.len(), + }; - /// Returns the port the originating homeserver can be accessed on. Only applicable to events - /// in the original format as used by Matrix room versions 1 and 2. - pub fn port(&self) -> Option { - if let Format::Original(original) = &self.0 { - Some(original.port) - } else { - None - } + &self.full_id[1..idx] } } -impl Display for EventId { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - match &self.0 { - Format::Original(original) => display( - f, - '$', - &original.localpart, - &original.hostname, - original.port, - ), - Format::Base64(id) | Format::UrlSafeBase64(id) => write!(f, "${}", id), - } - } -} - -impl Serialize for EventId { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&self.to_string()) - } -} - -impl<'de> Deserialize<'de> for EventId { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - deserialize_id(deserializer, "a Matrix event ID as a string") - } -} - -impl TryFrom<&str> for EventId { +impl TryFrom> for EventId { type Error = Error; /// Attempts to create a new Matrix event ID from a string representation. @@ -164,25 +93,27 @@ impl TryFrom<&str> for EventId { /// If using the original event format as used by Matrix room versions 1 and 2, the string must /// include the leading $ sigil, the localpart, a literal colon, and a valid homeserver /// hostname. - fn try_from(event_id: &str) -> Result { + fn try_from(event_id: Cow<'_, str>) -> Result { if event_id.contains(':') { - let (localpart, host, port) = parse_id('$', event_id)?; + let colon_idx = parse_id(&event_id, &['$'])?; - Ok(Self(Format::Original(Original { - hostname: host, - localpart: localpart.to_owned(), - port, - }))) - } else if !event_id.starts_with('$') { - Err(Error::MissingSigil) - } else if event_id.contains(|chr| chr == '+' || chr == '/') { - Ok(Self(Format::Base64(event_id[1..].to_string()))) + Ok(Self { + full_id: event_id.into_owned(), + colon_idx: Some(colon_idx), + }) } else { - Ok(Self(Format::UrlSafeBase64(event_id[1..].to_string()))) + validate_id(&event_id, &['$'])?; + + Ok(Self { + full_id: event_id.into_owned(), + colon_idx: None, + }) } } } +common_impls!(EventId, "a Matrix event ID"); + #[cfg(test)] mod tests { use std::convert::TryFrom; @@ -197,7 +128,7 @@ mod tests { assert_eq!( EventId::try_from("$39hvsi03hlne:example.com") .expect("Failed to create EventId.") - .to_string(), + .as_ref(), "$39hvsi03hlne:example.com" ); } @@ -207,7 +138,7 @@ mod tests { assert_eq!( EventId::try_from("$acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk") .expect("Failed to create EventId.") - .to_string(), + .as_ref(), "$acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk" ) } @@ -217,25 +148,24 @@ mod tests { assert_eq!( EventId::try_from("$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg") .expect("Failed to create EventId.") - .to_string(), + .as_ref(), "$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg" ) } #[test] fn generate_random_valid_event_id() { - let event_id = EventId::new("example.com") - .expect("Failed to generate EventId.") - .to_string(); + let event_id = EventId::new("example.com").expect("Failed to generate EventId."); + let id_str: &str = event_id.as_ref(); - assert!(event_id.to_string().starts_with('$')); - assert_eq!(event_id.len(), 31); + assert!(id_str.starts_with('$')); + assert_eq!(id_str.len(), 31); } - #[test] + /*#[test] fn generate_random_invalid_event_id() { assert!(EventId::new("").is_err()); - } + }*/ #[test] fn serialize_valid_original_event_id() { @@ -306,8 +236,8 @@ mod tests { assert_eq!( EventId::try_from("$39hvsi03hlne:example.com:443") .expect("Failed to create EventId.") - .to_string(), - "$39hvsi03hlne:example.com" + .as_ref(), + "$39hvsi03hlne:example.com:443" ); } @@ -316,7 +246,7 @@ mod tests { assert_eq!( EventId::try_from("$39hvsi03hlne:example.com:5000") .expect("Failed to create EventId.") - .to_string(), + .as_ref(), "$39hvsi03hlne:example.com:5000" ); } @@ -345,7 +275,7 @@ mod tests { ); } - #[test] + /*#[test] fn invalid_event_id_host() { assert_eq!( EventId::try_from("$39hvsi03hlne:/").unwrap_err(), @@ -359,5 +289,5 @@ mod tests { EventId::try_from("$39hvsi03hlne:example.com:notaport").unwrap_err(), Error::InvalidHost ); - } + }*/ } diff --git a/src/lib.rs b/src/lib.rs index 1fdcd391..666799e0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,17 +14,10 @@ #[cfg_attr(feature = "diesel", macro_use)] extern crate diesel; -use std::{ - borrow::Cow, - convert::TryFrom, - fmt::{Formatter, Result as FmtResult}, -}; +use std::{borrow::Cow, convert::TryFrom, num::NonZeroU8}; use rand::{distributions::Alphanumeric, thread_rng, Rng}; use serde::de::{self, Deserialize as _, Deserializer, Unexpected}; -use url::Url; - -pub use url::Host; #[doc(inline)] pub use crate::device_id::DeviceId; @@ -33,6 +26,9 @@ pub use crate::{ room_id_or_room_alias_id::RoomIdOrAliasId, room_version_id::RoomVersionId, user_id::UserId, }; +#[macro_use] +mod macros; + pub mod device_id; #[cfg(feature = "diesel")] mod diesel_integration; @@ -51,23 +47,6 @@ const MAX_BYTES: usize = 255; /// This is an optimization and not required by the spec. The shortest possible valid ID is a sigil /// + a single character local ID + a colon + a single character hostname. const MIN_CHARS: usize = 4; -/// The number of bytes in a valid sigil. -const SIGIL_BYTES: usize = 1; - -/// `Display` implementation shared by identifier types. -fn display( - f: &mut Formatter<'_>, - sigil: char, - localpart: &str, - hostname: &Host, - port: u16, -) -> FmtResult { - if port == 443 { - write!(f, "{}{}:{}", sigil, localpart, hostname) - } else { - write!(f, "{}{}:{}:{}", sigil, localpart, hostname, port) - } -} /// Generates a random identifier localpart. fn generate_localpart(length: usize) -> String { @@ -77,8 +56,8 @@ fn generate_localpart(length: usize) -> String { .collect() } -/// Checks if an identifier is within the acceptable byte lengths. -fn validate_id(id: &str) -> Result<(), Error> { +/// Checks if an identifier is valid. +fn validate_id(id: &str, valid_sigils: &[char]) -> Result<(), Error> { if id.len() > MAX_BYTES { return Err(Error::MaximumLengthExceeded); } @@ -87,35 +66,27 @@ fn validate_id(id: &str) -> Result<(), Error> { return Err(Error::MinimumLengthNotSatisfied); } - Ok(()) -} - -/// Parses the localpart, host, and port from a string identifier. -fn parse_id(required_sigil: char, id: &str) -> Result<(&str, Host, u16), Error> { - validate_id(id)?; - - if !id.starts_with(required_sigil) { + if !valid_sigils.contains(&id.chars().next().unwrap()) { return Err(Error::MissingSigil); } - let delimiter_index = match id.find(':') { - Some(index) => index, - None => return Err(Error::MissingDelimiter), - }; + Ok(()) +} - let localpart = &id[1..delimiter_index]; - let raw_host = &id[delimiter_index + SIGIL_BYTES..]; - let url_string = format!("https://{}", raw_host); - let url = Url::parse(&url_string)?; +/// Checks an identifier that contains a localpart and hostname for validity, +/// and returns the index of the colon that separates the two. +fn parse_id(id: &str, valid_sigils: &[char]) -> Result { + validate_id(id, valid_sigils)?; - let host = match url.host() { - Some(host) => host.to_owned(), - None => return Err(Error::InvalidHost), - }; + let colon_idx = id.find(':').ok_or(Error::MissingDelimiter)?; + if colon_idx == id.len() - 1 { + return Err(Error::InvalidHost); + } - let port = url.port().unwrap_or(443); - - Ok((localpart, host, port)) + match NonZeroU8::new(colon_idx as u8) { + Some(idx) => Ok(idx), + None => Err(Error::InvalidLocalPart), + } } /// Deserializes any type of id using the provided TryFrom implementation. diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 00000000..8289823d --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,84 @@ +macro_rules! common_impls { + ($id:ident, $desc:literal) => { + impl ::std::convert::From<$id> for ::std::string::String { + fn from(id: $id) -> Self { + id.full_id + } + } + + impl ::std::convert::TryFrom<&str> for $id { + type Error = crate::error::Error; + + fn try_from(s: &str) -> Result { + Self::try_from(::std::borrow::Cow::Borrowed(s)) + } + } + + impl ::std::convert::TryFrom for $id { + type Error = crate::error::Error; + + fn try_from(s: String) -> Result { + Self::try_from(::std::borrow::Cow::Owned(s)) + } + } + + impl ::std::convert::AsRef for $id { + fn as_ref(&self) -> &str { + &self.full_id + } + } + + impl ::std::fmt::Display for $id { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + write!(f, "{}", self.full_id) + } + } + + impl ::std::cmp::PartialEq for $id { + fn eq(&self, other: &Self) -> bool { + self.full_id == other.full_id + } + } + + impl ::std::cmp::Eq for $id {} + + impl ::std::cmp::PartialOrd for $id { + fn partial_cmp(&self, other: &Self) -> Option<::std::cmp::Ordering> { + <::std::string::String as ::std::cmp::PartialOrd>::partial_cmp( + &self.full_id, + &other.full_id, + ) + } + } + + impl ::std::cmp::Ord for $id { + fn cmp(&self, other: &Self) -> ::std::cmp::Ordering { + <::std::string::String as ::std::cmp::Ord>::cmp(&self.full_id, &other.full_id) + } + } + + impl ::std::hash::Hash for $id { + fn hash(&self, state: &mut H) { + self.full_id.hash(state); + } + } + + impl ::serde::Serialize for $id { + fn serialize(&self, serializer: S) -> Result + where + S: ::serde::Serializer, + { + serializer.serialize_str(&self.full_id) + } + } + + impl<'de> ::serde::Deserialize<'de> for $id { + fn deserialize(deserializer: D) -> Result + where + D: ::serde::Deserializer<'de>, + { + crate::deserialize_id(deserializer, $desc) + } + } + }; +} diff --git a/src/room_alias_id.rs b/src/room_alias_id.rs index 480b94e1..54c05a37 100644 --- a/src/room_alias_id.rs +++ b/src/room_alias_id.rs @@ -1,16 +1,11 @@ //! Matrix room alias identifiers. -use std::{ - convert::TryFrom, - fmt::{Display, Formatter, Result as FmtResult}, -}; +use std::{borrow::Cow, convert::TryFrom, num::NonZeroU8}; #[cfg(feature = "diesel")] use diesel::sql_types::Text; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use url::Host; -use crate::{deserialize_id, display, error::Error, parse_id}; +use crate::{error::Error, parse_id}; /// A Matrix room alias ID. /// @@ -21,84 +16,49 @@ use crate::{deserialize_id, display, error::Error, parse_id}; /// # use std::convert::TryFrom; /// # use ruma_identifiers::RoomAliasId; /// assert_eq!( -/// RoomAliasId::try_from("#ruma:example.com").unwrap().to_string(), +/// RoomAliasId::try_from("#ruma:example.com").unwrap().as_ref(), /// "#ruma:example.com" /// ); /// ``` -#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[derive(Clone, Debug)] #[cfg_attr(feature = "diesel", derive(FromSqlRow, QueryId, AsExpression, SqlType))] #[cfg_attr(feature = "diesel", sql_type = "Text")] pub struct RoomAliasId { - /// The alias for the room. - alias: String, - /// The hostname of the homeserver. - hostname: Host, - /// The network port of the homeserver. - port: u16, + full_id: String, + colon_idx: NonZeroU8, } impl RoomAliasId { - /// Returns a `Host` for the room alias ID, containing the server name (minus the port) of + /// Returns the host of the room alias ID, containing the server name (including the port) of /// the originating homeserver. - /// - /// The host can be either a domain name, an IPv4 address, or an IPv6 address. - pub fn hostname(&self) -> &Host { - &self.hostname + pub fn hostname(&self) -> &str { + &self.full_id[self.colon_idx.get() as usize + 1..] } /// Returns the room's alias. pub fn alias(&self) -> &str { - &self.alias - } - - /// Returns the port the originating homeserver can be accessed on. - pub fn port(&self) -> u16 { - self.port + &self.full_id[1..self.colon_idx.get() as usize] } } -impl Display for RoomAliasId { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - display(f, '#', &self.alias, &self.hostname, self.port) - } -} - -impl Serialize for RoomAliasId { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&self.to_string()) - } -} - -impl<'de> Deserialize<'de> for RoomAliasId { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - deserialize_id(deserializer, "a Matrix room alias ID as a string") - } -} - -impl TryFrom<&str> for RoomAliasId { +impl TryFrom> for RoomAliasId { type Error = Error; /// Attempts to create a new Matrix room alias ID from a string representation. /// - /// The string must include the leading # sigil, the alias, a literal colon, and a valid - /// server name. - fn try_from(room_id: &str) -> Result { - let (alias, host, port) = parse_id('#', room_id)?; + /// The string must include the leading # sigil, the alias, a literal colon, and a server name. + fn try_from(room_id: Cow<'_, str>) -> Result { + let colon_idx = parse_id(&room_id, &['#'])?; Ok(Self { - alias: alias.to_owned(), - hostname: host, - port, + full_id: room_id.into_owned(), + colon_idx, }) } } +common_impls!(RoomAliasId, "a Matrix room alias ID"); + #[cfg(test)] mod tests { use std::convert::TryFrom; @@ -113,7 +73,7 @@ mod tests { assert_eq!( RoomAliasId::try_from("#ruma:example.com") .expect("Failed to create RoomAliasId.") - .to_string(), + .as_ref(), "#ruma:example.com" ); } @@ -143,8 +103,8 @@ mod tests { assert_eq!( RoomAliasId::try_from("#ruma:example.com:443") .expect("Failed to create RoomAliasId.") - .to_string(), - "#ruma:example.com" + .as_ref(), + "#ruma:example.com:443" ); } @@ -153,7 +113,7 @@ mod tests { assert_eq!( RoomAliasId::try_from("#ruma:example.com:5000") .expect("Failed to create RoomAliasId.") - .to_string(), + .as_ref(), "#ruma:example.com:5000" ); } @@ -163,7 +123,7 @@ mod tests { assert_eq!( RoomAliasId::try_from("#老虎£я:example.com") .expect("Failed to create RoomAliasId.") - .to_string(), + .as_ref(), "#老虎£я:example.com" ); } @@ -184,7 +144,7 @@ mod tests { ); } - #[test] + /*#[test] fn invalid_room_alias_id_host() { assert_eq!( RoomAliasId::try_from("#ruma:/").unwrap_err(), @@ -198,5 +158,5 @@ mod tests { RoomAliasId::try_from("#ruma:example.com:notaport").unwrap_err(), Error::InvalidHost ); - } + }*/ } diff --git a/src/room_id.rs b/src/room_id.rs index 606ba6ae..c80d3c54 100644 --- a/src/room_id.rs +++ b/src/room_id.rs @@ -1,16 +1,11 @@ //! Matrix room identifiers. -use std::{ - convert::TryFrom, - fmt::{Display, Formatter, Result as FmtResult}, -}; +use std::{borrow::Cow, convert::TryFrom, num::NonZeroU8}; #[cfg(feature = "diesel")] use diesel::sql_types::Text; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use url::Host; -use crate::{deserialize_id, display, error::Error, generate_localpart, parse_id}; +use crate::{error::Error, generate_localpart, parse_id}; /// A Matrix room ID. /// @@ -21,20 +16,16 @@ use crate::{deserialize_id, display, error::Error, generate_localpart, parse_id} /// # use std::convert::TryFrom; /// # use ruma_identifiers::RoomId; /// assert_eq!( -/// RoomId::try_from("!n8f893n9:example.com").unwrap().to_string(), +/// RoomId::try_from("!n8f893n9:example.com").unwrap().as_ref(), /// "!n8f893n9:example.com" /// ); /// ``` -#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[derive(Clone, Debug)] #[cfg_attr(feature = "diesel", derive(FromSqlRow, QueryId, AsExpression, SqlType))] #[cfg_attr(feature = "diesel", sql_type = "Text")] pub struct RoomId { - /// The hostname of the homeserver. - hostname: Host, - /// The room's unique ID. - localpart: String, - /// The network port of the homeserver. - port: u16, + full_id: String, + colon_idx: NonZeroU8, } impl RoomId { @@ -43,77 +34,45 @@ impl RoomId { /// /// Fails if the given homeserver cannot be parsed as a valid host. pub fn new(homeserver_host: &str) -> Result { - let room_id = format!("!{}:{}", generate_localpart(18), homeserver_host); - let (localpart, host, port) = parse_id('!', &room_id)?; + let full_id = format!("!{}:{}", generate_localpart(18), homeserver_host); Ok(Self { - hostname: host, - localpart: localpart.to_string(), - port, + full_id, + colon_idx: NonZeroU8::new(19).unwrap(), }) } - /// Returns a `Host` for the room ID, containing the server name (minus the port) of the + /// Returns the host of the room ID, containing the server name (including the port) of the /// originating homeserver. - /// - /// The host can be either a domain name, an IPv4 address, or an IPv6 address. - pub fn hostname(&self) -> &Host { - &self.hostname + pub fn hostname(&self) -> &str { + &self.full_id[self.colon_idx.get() as usize + 1..] } /// Returns the rooms's unique ID. pub fn localpart(&self) -> &str { - &self.localpart - } - - /// Returns the port the originating homeserver can be accessed on. - pub fn port(&self) -> u16 { - self.port + &self.full_id[1..self.colon_idx.get() as usize] } } -impl Display for RoomId { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - display(f, '!', &self.localpart, &self.hostname, self.port) - } -} - -impl Serialize for RoomId { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&self.to_string()) - } -} - -impl<'de> Deserialize<'de> for RoomId { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - deserialize_id(deserializer, "a Matrix room ID as a string") - } -} - -impl TryFrom<&str> for RoomId { +impl TryFrom> for RoomId { type Error = Error; /// Attempts to create a new Matrix room ID from a string representation. /// - /// The string must include the leading ! sigil, the localpart, a literal colon, and a valid - /// server name. - fn try_from(room_id: &str) -> Result { - let (localpart, host, port) = parse_id('!', room_id)?; + /// The string must include the leading ! sigil, the localpart, a literal colon, and a server + /// name. + fn try_from(room_id: Cow<'_, str>) -> Result { + let colon_idx = parse_id(&room_id, &['!'])?; Ok(Self { - hostname: host, - localpart: localpart.to_owned(), - port, + full_id: room_id.into_owned(), + colon_idx, }) } } +common_impls!(RoomId, "a Matrix room ID"); + #[cfg(test)] mod tests { use std::convert::TryFrom; @@ -128,25 +87,24 @@ mod tests { assert_eq!( RoomId::try_from("!29fhd83h92h0:example.com") .expect("Failed to create RoomId.") - .to_string(), + .as_ref(), "!29fhd83h92h0:example.com" ); } #[test] fn generate_random_valid_room_id() { - let room_id = RoomId::new("example.com") - .expect("Failed to generate RoomId.") - .to_string(); + let room_id = RoomId::new("example.com").expect("Failed to generate RoomId."); + let id_str: &str = room_id.as_ref(); - assert!(room_id.to_string().starts_with('!')); - assert_eq!(room_id.len(), 31); + assert!(id_str.starts_with('!')); + assert_eq!(id_str.len(), 31); } - #[test] + /*#[test] fn generate_random_invalid_room_id() { assert!(RoomId::new("").is_err()); - } + }*/ #[test] fn serialize_valid_room_id() { @@ -173,8 +131,8 @@ mod tests { assert_eq!( RoomId::try_from("!29fhd83h92h0:example.com:443") .expect("Failed to create RoomId.") - .to_string(), - "!29fhd83h92h0:example.com" + .as_ref(), + "!29fhd83h92h0:example.com:443" ); } @@ -183,7 +141,7 @@ mod tests { assert_eq!( RoomId::try_from("!29fhd83h92h0:example.com:5000") .expect("Failed to create RoomId.") - .to_string(), + .as_ref(), "!29fhd83h92h0:example.com:5000" ); } @@ -204,7 +162,7 @@ mod tests { ); } - #[test] + /*#[test] fn invalid_room_id_host() { assert_eq!( RoomId::try_from("!29fhd83h92h0:/").unwrap_err(), @@ -218,5 +176,5 @@ mod tests { RoomId::try_from("!29fhd83h92h0:example.com:notaport").unwrap_err(), Error::InvalidHost ); - } + }*/ } diff --git a/src/room_id_or_room_alias_id.rs b/src/room_id_or_room_alias_id.rs index 93b7e4bb..a83b019e 100644 --- a/src/room_id_or_room_alias_id.rs +++ b/src/room_id_or_room_alias_id.rs @@ -1,17 +1,11 @@ //! Matrix identifiers for places where a room ID or room alias ID are used interchangeably. -use std::{ - convert::TryFrom, - fmt::{Display, Formatter, Result as FmtResult}, -}; +use std::{borrow::Cow, convert::TryFrom, hint::unreachable_unchecked, num::NonZeroU8}; #[cfg(feature = "diesel")] use diesel::sql_types::Text; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use crate::{ - deserialize_id, display, error::Error, room_alias_id::RoomAliasId, room_id::RoomId, validate_id, -}; +use crate::{error::Error, parse_id}; /// A Matrix room ID or a Matrix room alias ID. /// @@ -23,73 +17,61 @@ use crate::{ /// # use std::convert::TryFrom; /// # use ruma_identifiers::RoomIdOrAliasId; /// assert_eq!( -/// RoomIdOrAliasId::try_from("#ruma:example.com").unwrap().to_string(), +/// RoomIdOrAliasId::try_from("#ruma:example.com").unwrap().as_ref(), /// "#ruma:example.com" /// ); /// /// assert_eq!( -/// RoomIdOrAliasId::try_from("!n8f893n9:example.com").unwrap().to_string(), +/// RoomIdOrAliasId::try_from("!n8f893n9:example.com").unwrap().as_ref(), /// "!n8f893n9:example.com" /// ); /// ``` -#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[derive(Clone, Debug)] #[cfg_attr(feature = "diesel", derive(FromSqlRow, QueryId, AsExpression, SqlType))] #[cfg_attr(feature = "diesel", sql_type = "Text")] -pub enum RoomIdOrAliasId { - /// A Matrix room alias ID. - RoomAliasId(RoomAliasId), - /// A Matrix room ID. - RoomId(RoomId), +pub struct RoomIdOrAliasId { + full_id: String, + colon_idx: NonZeroU8, } -impl Display for RoomIdOrAliasId { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - match *self { - RoomIdOrAliasId::RoomAliasId(ref room_alias_id) => display( - f, - '#', - room_alias_id.alias(), - room_alias_id.hostname(), - room_alias_id.port(), - ), - RoomIdOrAliasId::RoomId(ref room_id) => display( - f, - '!', - room_id.localpart(), - room_id.hostname(), - room_id.port(), - ), +impl RoomIdOrAliasId { + /// Returns the host of the room (alias) ID, containing the server name (including the port) of + /// the originating homeserver. + pub fn hostname(&self) -> &str { + &self.full_id[self.colon_idx.get() as usize + 1..] + } + + /// Returns the local part (everything after the `!` or `#` and before the first colon). + pub fn localpart(&self) -> &str { + &self.full_id[1..self.colon_idx.get() as usize] + } + + /// Whether this is a room id (starts with `'!'`) + pub fn is_room_id(&self) -> bool { + self.variant() == Variant::RoomId + } + + /// Whether this is a room alias id (starts with `'#'`) + pub fn is_room_alias_id(&self) -> bool { + self.variant() == Variant::RoomAliasId + } + + fn variant(&self) -> Variant { + match self.full_id.bytes().next() { + Some(b'!') => Variant::RoomId, + Some(b'#') => Variant::RoomAliasId, + _ => unsafe { unreachable_unchecked() }, } } } -impl Serialize for RoomIdOrAliasId { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - match *self { - RoomIdOrAliasId::RoomAliasId(ref room_alias_id) => { - serializer.serialize_str(&room_alias_id.to_string()) - } - RoomIdOrAliasId::RoomId(ref room_id) => serializer.serialize_str(&room_id.to_string()), - } - } +#[derive(PartialEq)] +enum Variant { + RoomId, + RoomAliasId, } -impl<'de> Deserialize<'de> for RoomIdOrAliasId { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - deserialize_id( - deserializer, - "a Matrix room ID or room alias ID as a string", - ) - } -} - -impl TryFrom<&str> for RoomIdOrAliasId { +impl TryFrom> for RoomIdOrAliasId { type Error = Error; /// Attempts to create a new Matrix room ID or a room alias ID from a string representation. @@ -97,27 +79,17 @@ impl TryFrom<&str> for RoomIdOrAliasId { /// The string must either include the leading ! sigil, the localpart, a literal colon, and a /// valid homeserver host or include the leading # sigil, the alias, a literal colon, and a /// valid homeserver host. - fn try_from(room_id_or_alias_id: &str) -> Result { - validate_id(room_id_or_alias_id)?; - - let mut chars = room_id_or_alias_id.chars(); - - let sigil = chars.next().expect("ID missing first character."); - - match sigil { - '#' => { - let room_alias_id = RoomAliasId::try_from(room_id_or_alias_id)?; - Ok(RoomIdOrAliasId::RoomAliasId(room_alias_id)) - } - '!' => { - let room_id = RoomId::try_from(room_id_or_alias_id)?; - Ok(RoomIdOrAliasId::RoomId(room_id)) - } - _ => Err(Error::MissingSigil), - } + fn try_from(room_id_or_alias_id: Cow<'_, str>) -> Result { + let colon_idx = parse_id(&room_id_or_alias_id, &['#', '!'])?; + Ok(Self { + full_id: room_id_or_alias_id.into_owned(), + colon_idx, + }) } } +common_impls!(RoomIdOrAliasId, "a Matrix room ID or room alias ID"); + #[cfg(test)] mod tests { use std::convert::TryFrom; @@ -132,7 +104,7 @@ mod tests { assert_eq!( RoomIdOrAliasId::try_from("#ruma:example.com") .expect("Failed to create RoomAliasId.") - .to_string(), + .as_ref(), "#ruma:example.com" ); } @@ -142,7 +114,7 @@ mod tests { assert_eq!( RoomIdOrAliasId::try_from("!29fhd83h92h0:example.com") .expect("Failed to create RoomId.") - .to_string(), + .as_ref(), "!29fhd83h92h0:example.com" ); } diff --git a/src/room_version_id.rs b/src/room_version_id.rs index 85772263..77de552e 100644 --- a/src/room_version_id.rs +++ b/src/room_version_id.rs @@ -1,8 +1,9 @@ //! Matrix room version identifiers. use std::{ + borrow::Cow, convert::TryFrom, - fmt::{Display, Formatter, Result as FmtResult}, + fmt::{self, Display, Formatter}, }; #[cfg(feature = "diesel")] @@ -22,7 +23,7 @@ const MAX_CODE_POINTS: usize = 32; /// ``` /// # use std::convert::TryFrom; /// # use ruma_identifiers::RoomVersionId; -/// assert_eq!(RoomVersionId::try_from("1").unwrap().to_string(), "1"); +/// assert_eq!(RoomVersionId::try_from("1").unwrap().as_ref(), "1"); /// ``` #[derive(Clone, Debug, Eq, Hash, PartialEq)] #[cfg_attr(feature = "diesel", derive(FromSqlRow, QueryId, AsExpression, SqlType))] @@ -31,7 +32,7 @@ pub struct RoomVersionId(InnerRoomVersionId); /// Possibile values for room version, distinguishing between official Matrix versions and custom /// versions. -#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] enum InnerRoomVersionId { /// A version 1 room. Version1, @@ -79,8 +80,8 @@ impl RoomVersionId { } /// Creates a custom room version ID from the given string slice. - pub fn custom(id: &str) -> Self { - Self(InnerRoomVersionId::Custom(id.to_string())) + pub fn custom(id: String) -> Self { + Self(InnerRoomVersionId::Custom(id)) } /// Whether or not this room version is an official one specified by the Matrix protocol. @@ -122,18 +123,35 @@ impl RoomVersionId { } } -impl Display for RoomVersionId { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - let message = match self.0 { +impl From for String { + fn from(id: RoomVersionId) -> Self { + match id.0 { + InnerRoomVersionId::Version1 => "1".to_owned(), + InnerRoomVersionId::Version2 => "2".to_owned(), + InnerRoomVersionId::Version3 => "3".to_owned(), + InnerRoomVersionId::Version4 => "4".to_owned(), + InnerRoomVersionId::Version5 => "5".to_owned(), + InnerRoomVersionId::Custom(version) => version, + } + } +} + +impl AsRef for RoomVersionId { + fn as_ref(&self) -> &str { + match &self.0 { InnerRoomVersionId::Version1 => "1", InnerRoomVersionId::Version2 => "2", InnerRoomVersionId::Version3 => "3", InnerRoomVersionId::Version4 => "4", InnerRoomVersionId::Version5 => "5", - InnerRoomVersionId::Custom(ref version) => version, - }; + InnerRoomVersionId::Custom(version) => version, + } + } +} - write!(f, "{}", message) +impl Display for RoomVersionId { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_ref()) } } @@ -142,7 +160,7 @@ impl Serialize for RoomVersionId { where S: Serializer, { - serializer.serialize_str(&self.to_string()) + serializer.serialize_str(self.as_ref()) } } @@ -155,12 +173,12 @@ impl<'de> Deserialize<'de> for RoomVersionId { } } -impl TryFrom<&str> for RoomVersionId { +impl TryFrom> for RoomVersionId { type Error = Error; /// Attempts to create a new Matrix room version ID from a string representation. - fn try_from(room_version_id: &str) -> Result { - let version = match room_version_id { + fn try_from(room_version_id: Cow<'_, str>) -> Result { + let version = match &room_version_id as &str { "1" => Self(InnerRoomVersionId::Version1), "2" => Self(InnerRoomVersionId::Version2), "3" => Self(InnerRoomVersionId::Version3), @@ -172,7 +190,7 @@ impl TryFrom<&str> for RoomVersionId { } else if custom.chars().count() > MAX_CODE_POINTS { return Err(Error::MaximumLengthExceeded); } else { - Self(InnerRoomVersionId::Custom(custom.to_string())) + Self(InnerRoomVersionId::Custom(room_version_id.into_owned())) } } }; @@ -181,6 +199,22 @@ impl TryFrom<&str> for RoomVersionId { } } +impl TryFrom<&str> for RoomVersionId { + type Error = crate::error::Error; + + fn try_from(s: &str) -> Result { + Self::try_from(Cow::Borrowed(s)) + } +} + +impl TryFrom for RoomVersionId { + type Error = crate::error::Error; + + fn try_from(s: String) -> Result { + Self::try_from(Cow::Owned(s)) + } +} + #[cfg(test)] mod tests { use std::convert::TryFrom; @@ -195,7 +229,7 @@ mod tests { assert_eq!( RoomVersionId::try_from("1") .expect("Failed to create RoomVersionId.") - .to_string(), + .as_ref(), "1" ); } @@ -205,7 +239,7 @@ mod tests { assert_eq!( RoomVersionId::try_from("2") .expect("Failed to create RoomVersionId.") - .to_string(), + .as_ref(), "2" ); } @@ -215,7 +249,7 @@ mod tests { assert_eq!( RoomVersionId::try_from("3") .expect("Failed to create RoomVersionId.") - .to_string(), + .as_ref(), "3" ); } @@ -225,7 +259,7 @@ mod tests { assert_eq!( RoomVersionId::try_from("4") .expect("Failed to create RoomVersionId.") - .to_string(), + .as_ref(), "4" ); } @@ -235,7 +269,7 @@ mod tests { assert_eq!( RoomVersionId::try_from("5") .expect("Failed to create RoomVersionId.") - .to_string(), + .as_ref(), "5" ); } @@ -245,7 +279,7 @@ mod tests { assert_eq!( RoomVersionId::try_from("io.ruma.1") .expect("Failed to create RoomVersionId.") - .to_string(), + .as_ref(), "io.ruma.1" ); } @@ -320,7 +354,7 @@ mod tests { assert!(RoomVersionId::version_3().is_version_3()); assert!(RoomVersionId::version_4().is_version_4()); assert!(RoomVersionId::version_5().is_version_5()); - assert!(RoomVersionId::custom("foo").is_custom()); + assert!(RoomVersionId::custom("foo".into()).is_custom()); } #[test] diff --git a/src/user_id.rs b/src/user_id.rs index a07dd134..d0fb1ea5 100644 --- a/src/user_id.rs +++ b/src/user_id.rs @@ -1,16 +1,11 @@ //! Matrix user identifiers. -use std::{ - convert::TryFrom, - fmt::{Display, Formatter, Result as FmtResult}, -}; +use std::{borrow::Cow, convert::TryFrom, num::NonZeroU8}; #[cfg(feature = "diesel")] use diesel::sql_types::Text; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use url::Host; -use crate::{deserialize_id, display, error::Error, generate_localpart, parse_id}; +use crate::{error::Error, generate_localpart, parse_id}; /// A Matrix user ID. /// @@ -21,20 +16,16 @@ use crate::{deserialize_id, display, error::Error, generate_localpart, parse_id} /// # use std::convert::TryFrom; /// # use ruma_identifiers::UserId; /// assert_eq!( -/// UserId::try_from("@carl:example.com").unwrap().to_string(), +/// UserId::try_from("@carl:example.com").unwrap().as_ref(), /// "@carl:example.com" /// ); /// ``` -#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[derive(Clone, Debug)] #[cfg_attr(feature = "diesel", derive(FromSqlRow, QueryId, AsExpression, SqlType))] #[cfg_attr(feature = "diesel", sql_type = "Text")] pub struct UserId { - /// The hostname of the homeserver. - hostname: Host, - /// The user's unique ID. - localpart: String, - /// The network port of the homeserver. - port: u16, + full_id: String, + colon_idx: NonZeroU8, /// Whether this user id is a historical one. /// /// A historical user id is one that is not legal per the regular user id rules, but was @@ -49,37 +40,29 @@ impl UserId { /// /// Fails if the given homeserver cannot be parsed as a valid host. pub fn new(homeserver_host: &str) -> Result { - let user_id = format!( + let full_id = format!( "@{}:{}", generate_localpart(12).to_lowercase(), homeserver_host ); - let (localpart, host, port) = parse_id('@', &user_id)?; + let colon_idx = parse_id(&full_id, &['@'])?; Ok(Self { - hostname: host, - localpart: localpart.to_string(), - port, + full_id, + colon_idx, is_historical: false, }) } - /// Returns a `Host` for the user ID, containing the server name (minus the port) of the + /// Returns the host of the user ID, containing the server name (including the port) of the /// originating homeserver. - /// - /// The host can be either a domain name, an IPv4 address, or an IPv6 address. - pub fn hostname(&self) -> &Host { - &self.hostname + pub fn hostname(&self) -> &str { + &self.full_id[self.colon_idx.get() as usize + 1..] } /// Returns the user's localpart. pub fn localpart(&self) -> &str { - &self.localpart - } - - /// Returns the port the originating homeserver can be accessed on. - pub fn port(&self) -> u16 { - self.port + &self.full_id[1..self.colon_idx.get() as usize] } /// Whether this user ID is a historical one, i.e. one that doesn't conform to the latest @@ -90,39 +73,16 @@ impl UserId { } } -impl Display for UserId { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - display(f, '@', &self.localpart, &self.hostname, self.port) - } -} - -impl Serialize for UserId { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&self.to_string()) - } -} - -impl<'de> Deserialize<'de> for UserId { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - deserialize_id(deserializer, "a Matrix user ID as a string") - } -} - -impl TryFrom<&str> for UserId { +impl TryFrom> for UserId { type Error = Error; /// Attempts to create a new Matrix user ID from a string representation. /// - /// The string must include the leading @ sigil, the localpart, a literal colon, and a valid - /// server name. - fn try_from(user_id: &str) -> Result { - let (localpart, host, port) = parse_id('@', user_id)?; + /// The string must include the leading @ sigil, the localpart, a literal colon, and a server + /// name. + fn try_from(user_id: Cow<'_, str>) -> Result { + let colon_idx = parse_id(&user_id, &['@'])?; + let localpart = &user_id[1..colon_idx.get() as usize]; // See https://matrix.org/docs/spec/appendices#user-identifiers let is_fully_conforming = localpart.bytes().all(|b| match b { @@ -138,14 +98,15 @@ impl TryFrom<&str> for UserId { } Ok(Self { - hostname: host, - port, - localpart: localpart.to_owned(), + full_id: user_id.into_owned(), + colon_idx, is_historical: !is_fully_conforming, }) } } +common_impls!(UserId, "a Matrix user ID"); + #[cfg(test)] mod tests { use std::convert::TryFrom; @@ -158,32 +119,31 @@ mod tests { #[test] fn valid_user_id() { let user_id = UserId::try_from("@carl:example.com").expect("Failed to create UserId."); - assert_eq!(user_id.to_string(), "@carl:example.com"); + assert_eq!(user_id.as_ref(), "@carl:example.com"); assert!(!user_id.is_historical()); } #[test] fn valid_historical_user_id() { let user_id = UserId::try_from("@a%b[irc]:example.com").expect("Failed to create UserId."); - assert_eq!(user_id.to_string(), "@a%b[irc]:example.com"); + assert_eq!(user_id.as_ref(), "@a%b[irc]:example.com"); assert!(user_id.is_historical()); } #[test] fn downcase_user_id() { let user_id = UserId::try_from("@CARL:example.com").expect("Failed to create UserId."); - assert_eq!(user_id.to_string(), "@CARL:example.com"); + assert_eq!(user_id.as_ref(), "@CARL:example.com"); assert!(user_id.is_historical()); } #[test] fn generate_random_valid_user_id() { - let user_id = UserId::new("example.com") - .expect("Failed to generate UserId.") - .to_string(); + let user_id = UserId::new("example.com").expect("Failed to generate UserId."); + let id_str: &str = user_id.as_ref(); - assert!(user_id.to_string().starts_with('@')); - assert_eq!(user_id.len(), 25); + assert!(id_str.starts_with('@')); + assert_eq!(id_str.len(), 25); } #[test] @@ -213,15 +173,15 @@ mod tests { assert_eq!( UserId::try_from("@carl:example.com:443") .expect("Failed to create UserId.") - .to_string(), - "@carl:example.com" + .as_ref(), + "@carl:example.com:443" ); } #[test] fn valid_user_id_with_non_standard_port() { let user_id = UserId::try_from("@carl:example.com:5000").expect("Failed to create UserId."); - assert_eq!(user_id.to_string(), "@carl:example.com:5000"); + assert_eq!(user_id.as_ref(), "@carl:example.com:5000"); assert!(!user_id.is_historical()); } @@ -249,7 +209,7 @@ mod tests { ); } - #[test] + /*#[test] fn invalid_user_id_host() { assert_eq!(UserId::try_from("@carl:/").unwrap_err(), Error::InvalidHost); } @@ -260,5 +220,5 @@ mod tests { UserId::try_from("@carl:example.com:notaport").unwrap_err(), Error::InvalidHost ); - } + }*/ }