From f8492db766d186dbabbd58b7991060caf4ed12ae Mon Sep 17 00:00:00 2001 From: iinuwa Date: Thu, 18 Jun 2020 04:21:00 -0500 Subject: [PATCH] Add ServerName identifier --- ruma-events/src/direct.rs | 14 +++--- ruma-events/src/room/pinned_events.rs | 18 ++++--- ruma-identifiers/CHANGELOG.md | 25 ++++++++++ ruma-identifiers/src/event_id.rs | 31 +++++------- ruma-identifiers/src/lib.rs | 23 ++++++--- ruma-identifiers/src/room_alias_id.rs | 8 ++-- ruma-identifiers/src/room_id.rs | 30 +++++------- .../src/room_id_or_room_alias_id.rs | 8 ++-- ruma-identifiers/src/server_name.rs | 29 +++++++++++ ruma-identifiers/src/user_id.rs | 48 ++++++++----------- 10 files changed, 144 insertions(+), 90 deletions(-) diff --git a/ruma-events/src/direct.rs b/ruma-events/src/direct.rs index 746b8417..ec9ab6a9 100644 --- a/ruma-events/src/direct.rs +++ b/ruma-events/src/direct.rs @@ -36,9 +36,9 @@ impl DerefMut for DirectEventContent { #[cfg(test)] mod tests { - use std::collections::BTreeMap; + use std::{collections::BTreeMap, convert::TryFrom}; - use ruma_identifiers::{RoomId, UserId}; + use ruma_identifiers::{RoomId, ServerNameRef, UserId}; use serde_json::{from_value as from_json_value, json, to_value as to_json_value}; use super::{DirectEvent, DirectEventContent}; @@ -47,8 +47,9 @@ mod tests { #[test] fn serialization() { let mut content = DirectEventContent(BTreeMap::new()); - let alice = UserId::new("ruma.io").unwrap(); - let room = vec![RoomId::new("ruma.io").unwrap()]; + let server_name = ServerNameRef::try_from("ruma.io").unwrap(); + let alice = UserId::new(server_name); + let room = vec![RoomId::new(server_name)]; content.insert(alice.clone(), room.clone()); @@ -65,8 +66,9 @@ mod tests { #[test] fn deserialization() { - let alice = UserId::new("ruma.io").unwrap(); - let rooms = vec![RoomId::new("ruma.io").unwrap(), RoomId::new("ruma.io").unwrap()]; + let server_name = ServerNameRef::try_from("ruma.io").unwrap(); + let alice = UserId::new(server_name); + let rooms = vec![RoomId::new(server_name), RoomId::new(server_name)]; let json_data = json!({ "content": { diff --git a/ruma-events/src/room/pinned_events.rs b/ruma-events/src/room/pinned_events.rs index 2cd55177..0411330c 100644 --- a/ruma-events/src/room/pinned_events.rs +++ b/ruma-events/src/room/pinned_events.rs @@ -19,9 +19,12 @@ pub struct PinnedEventsEventContent { #[cfg(test)] mod tests { - use std::time::{Duration, UNIX_EPOCH}; + use std::{ + convert::TryFrom, + time::{Duration, UNIX_EPOCH}, + }; - use ruma_identifiers::{EventId, RoomId, UserId}; + use ruma_identifiers::{EventId, RoomId, ServerNameRef, UserId}; use serde_json::to_string; use super::PinnedEventsEventContent; @@ -30,17 +33,18 @@ mod tests { #[test] fn serialization_deserialization() { let mut content: PinnedEventsEventContent = PinnedEventsEventContent { pinned: Vec::new() }; + let server_name = ServerNameRef::try_from("example.com").unwrap(); - content.pinned.push(EventId::new("example.com").unwrap()); - content.pinned.push(EventId::new("example.com").unwrap()); + content.pinned.push(EventId::new(server_name)); + content.pinned.push(EventId::new(server_name)); let event = StateEvent { content: content.clone(), - event_id: EventId::new("example.com").unwrap(), + event_id: EventId::new(server_name), origin_server_ts: UNIX_EPOCH + Duration::from_millis(1_432_804_485_886u64), prev_content: None, - room_id: RoomId::new("example.com").unwrap(), - sender: UserId::new("example.com").unwrap(), + room_id: RoomId::new(server_name), + sender: UserId::new(server_name), state_key: "".to_string(), unsigned: UnsignedData::default(), }; diff --git a/ruma-identifiers/CHANGELOG.md b/ruma-identifiers/CHANGELOG.md index 159c9453..790468ca 100644 --- a/ruma-identifiers/CHANGELOG.md +++ b/ruma-identifiers/CHANGELOG.md @@ -8,10 +8,35 @@ Breaking changes: * Update `parse_with_server_name`s signature (instead of `Into` it now requires `Into>` of the id type). This is technically a breaking change, but extremely unlikely to affect any existing code. +* Modify identifier types to use the new `ServerName` type: + * Change signature of `new()` methods of `EventId`, `RoomId`, and `UserId` from + ```rust + fn new(&str) -> Result + //... + ``` + to + ```rust + fn new(ServerNameRef<'_>) -> Self + ``` + + * Change signature of `server_name()` for `EventId`, `RoomAliasId`, `RoomId`, `RoomIdOrAliasId`, `UserId` from + ```rust + fn server_name() -> &str + ``` + to + ```rust + fn server_name() -> ServerNameRef<'_> + ``` + +Deprecations: + +* Mark `server_name::is_valid_server_name` as deprecated in favor of `ServerName::try_from()` + Improvements: * Add `DeviceKeyId`, `DeviceKeyAlgorithm`, `ServerKeyId`, and `ServerKeyAlgorithm` +* Add `ServerName` and `ServerNameRef` types # 0.16.2 diff --git a/ruma-identifiers/src/event_id.rs b/ruma-identifiers/src/event_id.rs index da8f0013..998659d6 100644 --- a/ruma-identifiers/src/event_id.rs +++ b/ruma-identifiers/src/event_id.rs @@ -1,8 +1,8 @@ //! Matrix event identifiers. -use std::num::NonZeroU8; +use std::{convert::TryFrom, num::NonZeroU8}; -use crate::{error::Error, parse_id, validate_id}; +use crate::{error::Error, parse_id, validate_id, ServerNameRef}; /// A Matrix event ID. /// @@ -55,18 +55,15 @@ impl EventId { /// parsed as a valid host. #[cfg(feature = "rand")] #[cfg_attr(docsrs, doc(cfg(feature = "rand")))] - pub fn new(server_name: &str) -> Result + pub fn new(server_name: ServerNameRef<'_>) -> Self where String: Into, { - use crate::{generate_localpart, is_valid_server_name}; + use crate::generate_localpart; - if !is_valid_server_name(server_name) { - return Err(Error::InvalidServerName); - } let full_id = format!("${}:{}", generate_localpart(18), server_name).into(); - Ok(Self { full_id, colon_idx: NonZeroU8::new(19) }) + Self { full_id, colon_idx: NonZeroU8::new(19) } } /// Returns the event's unique ID. For the original event format as used by Matrix room @@ -87,11 +84,13 @@ impl EventId { /// Returns the server name of the event ID. /// /// Only applicable to events in the original format as used by Matrix room versions 1 and 2. - pub fn server_name(&self) -> Option<&str> + pub fn server_name(&self) -> Option> where T: AsRef, { - self.colon_idx.map(|idx| &self.full_id.as_ref()[idx.get() as usize + 1..]) + self.colon_idx.map(|idx| { + ServerNameRef::try_from(&self.full_id.as_ref()[idx.get() as usize + 1..]).unwrap() + }) } } @@ -123,7 +122,7 @@ mod tests { #[cfg(feature = "serde")] use serde_json::{from_str, to_string}; - use crate::error::Error; + use crate::{error::Error, ServerNameRef}; type EventId = super::EventId>; @@ -160,19 +159,15 @@ mod tests { #[cfg(feature = "rand")] #[test] fn generate_random_valid_event_id() { - let event_id = EventId::new("example.com").expect("Failed to generate EventId."); + let server_name = + ServerNameRef::try_from("example.com").expect("Failed to parse ServerName"); + let event_id = EventId::new(server_name); let id_str: &str = event_id.as_ref(); assert!(id_str.starts_with('$')); assert_eq!(id_str.len(), 31); } - #[cfg(feature = "rand")] - #[test] - fn generate_random_invalid_event_id() { - assert!(EventId::new("").is_err()); - } - #[cfg(feature = "serde")] #[test] fn serialize_valid_original_event_id() { diff --git a/ruma-identifiers/src/lib.rs b/ruma-identifiers/src/lib.rs index c7ee58b3..168f0fa8 100644 --- a/ruma-identifiers/src/lib.rs +++ b/ruma-identifiers/src/lib.rs @@ -11,19 +11,19 @@ #![allow(clippy::use_self)] #![cfg_attr(docsrs, feature(doc_cfg))] -use std::num::NonZeroU8; +use std::{convert::TryFrom, num::NonZeroU8}; #[cfg(feature = "serde")] use serde::de::{self, Deserialize as _, Deserializer, Unexpected}; #[doc(inline)] +#[allow(deprecated)] pub use crate::{error::Error, server_name::is_valid_server_name}; #[macro_use] mod macros; mod error; -mod server_name; pub mod device_id; pub mod device_key_id; @@ -34,6 +34,8 @@ pub mod room_id; pub mod room_id_or_room_alias_id; pub mod room_version_id; pub mod server_key_id; +#[allow(deprecated)] +pub mod server_name; pub mod user_id; /// Allowed algorithms for homeserver signing keys. @@ -127,13 +129,24 @@ pub type ServerKeyAlgorithm = key_algorithms::ServerKeyAlgorithm; /// and `Deserialize` if the `serde` feature is enabled. pub type ServerKeyId = server_key_id::ServerKeyId>; -/// An reference to a homeserver signing key identifier containing a key +/// A reference to a homeserver signing key identifier containing a key /// algorithm and version. /// /// Can be created via `TryFrom<&str>`; implements `Serialize` /// and `Deserialize` if the `serde` feature is enabled. pub type ServerKeyIdRef<'a> = server_key_id::ServerKeyId<&'a str>; +/// An owned homeserver IP address or hostname. +/// +/// Can be created via `TryFrom` and `TryFrom<&str>`; implements `Serialize` +/// and `Deserialize` if the `serde` feature is enabled. +pub type ServerName = server_name::ServerName>; + +/// A reference to a homeserver IP address or hostname. +/// +/// Can be created via `TryFrom<&str>`; implements `Serialize` +/// and `Deserialize` if the `serde` feature is enabled. +pub type ServerNameRef<'a> = server_name::ServerName<&'a str>; /// An owned user ID. /// /// Can be created via `new` (if the `rand` feature is enabled) and `TryFrom` + @@ -187,9 +200,7 @@ fn parse_id(id: &str, valid_sigils: &[char]) -> Result { return Err(Error::InvalidLocalPart); } - if !is_valid_server_name(&id[colon_idx + 1..]) { - return Err(Error::InvalidServerName); - } + server_name::ServerName::<&str>::try_from(&id[colon_idx + 1..])?; Ok(NonZeroU8::new(colon_idx as u8).unwrap()) } diff --git a/ruma-identifiers/src/room_alias_id.rs b/ruma-identifiers/src/room_alias_id.rs index 9228f28b..fc0cccf1 100644 --- a/ruma-identifiers/src/room_alias_id.rs +++ b/ruma-identifiers/src/room_alias_id.rs @@ -1,8 +1,8 @@ //! Matrix room alias identifiers. -use std::num::NonZeroU8; +use std::{convert::TryFrom, num::NonZeroU8}; -use crate::{error::Error, parse_id}; +use crate::{error::Error, parse_id, server_name::ServerName}; /// A Matrix room alias ID. /// @@ -33,8 +33,8 @@ impl> RoomAliasId { } /// Returns the server name of the room alias ID. - pub fn server_name(&self) -> &str { - &self.full_id.as_ref()[self.colon_idx.get() as usize + 1..] + pub fn server_name(&self) -> ServerName<&str> { + ServerName::try_from(&self.full_id.as_ref()[self.colon_idx.get() as usize + 1..]).unwrap() } } diff --git a/ruma-identifiers/src/room_id.rs b/ruma-identifiers/src/room_id.rs index e7be0bb2..cee4a567 100644 --- a/ruma-identifiers/src/room_id.rs +++ b/ruma-identifiers/src/room_id.rs @@ -1,8 +1,8 @@ //! Matrix room identifiers. -use std::num::NonZeroU8; +use std::{convert::TryFrom, num::NonZeroU8}; -use crate::{error::Error, parse_id}; +use crate::{error::Error, parse_id, ServerNameRef}; /// A Matrix room ID. /// @@ -33,18 +33,15 @@ impl RoomId { /// Fails if the given homeserver cannot be parsed as a valid host. #[cfg(feature = "rand")] #[cfg_attr(docsrs, doc(cfg(feature = "rand")))] - pub fn new(server_name: &str) -> Result + pub fn new(server_name: ServerNameRef<'_>) -> Self where String: Into, { - use crate::{generate_localpart, is_valid_server_name}; + use crate::generate_localpart; - if !is_valid_server_name(server_name) { - return Err(Error::InvalidServerName); - } let full_id = format!("!{}:{}", generate_localpart(18), server_name).into(); - Ok(Self { full_id, colon_idx: NonZeroU8::new(19).unwrap() }) + Self { full_id, colon_idx: NonZeroU8::new(19).unwrap() } } /// Returns the rooms's unique ID. @@ -56,11 +53,12 @@ impl RoomId { } /// Returns the server name of the room ID. - pub fn server_name(&self) -> &str + pub fn server_name(&self) -> ServerNameRef<'_> where T: AsRef, { - &self.full_id.as_ref()[self.colon_idx.get() as usize + 1..] + ServerNameRef::try_from(&self.full_id.as_ref()[self.colon_idx.get() as usize + 1..]) + .unwrap() } } @@ -85,7 +83,7 @@ mod tests { #[cfg(feature = "serde")] use serde_json::{from_str, to_string}; - use crate::error::Error; + use crate::{error::Error, ServerNameRef}; type RoomId = super::RoomId>; @@ -102,19 +100,15 @@ mod tests { #[cfg(feature = "rand")] #[test] fn generate_random_valid_room_id() { - let room_id = RoomId::new("example.com").expect("Failed to generate RoomId."); + let server_name = + ServerNameRef::try_from("example.com").expect("Failed to parse ServerName"); + let room_id = RoomId::new(server_name); let id_str: &str = room_id.as_ref(); assert!(id_str.starts_with('!')); assert_eq!(id_str.len(), 31); } - #[cfg(feature = "rand")] - #[test] - fn generate_random_invalid_room_id() { - assert!(RoomId::new("").is_err()); - } - #[cfg(feature = "serde")] #[test] fn serialize_valid_room_id() { diff --git a/ruma-identifiers/src/room_id_or_room_alias_id.rs b/ruma-identifiers/src/room_id_or_room_alias_id.rs index be88f386..3af9f604 100644 --- a/ruma-identifiers/src/room_id_or_room_alias_id.rs +++ b/ruma-identifiers/src/room_id_or_room_alias_id.rs @@ -2,7 +2,9 @@ use std::{convert::TryFrom, hint::unreachable_unchecked, num::NonZeroU8}; -use crate::{error::Error, parse_id, room_alias_id::RoomAliasId, room_id::RoomId}; +use crate::{ + error::Error, parse_id, room_alias_id::RoomAliasId, room_id::RoomId, server_name::ServerName, +}; /// A Matrix room ID or a Matrix room alias ID. /// @@ -39,8 +41,8 @@ impl> RoomIdOrAliasId { } /// Returns the server name of the room (alias) ID. - pub fn server_name(&self) -> &str { - &self.full_id.as_ref()[self.colon_idx.get() as usize + 1..] + pub fn server_name(&self) -> ServerName<&str> { + ServerName::try_from(&self.full_id.as_ref()[self.colon_idx.get() as usize + 1..]).unwrap() } /// Whether this is a room id (starts with `'!'`) diff --git a/ruma-identifiers/src/server_name.rs b/ruma-identifiers/src/server_name.rs index 65a75596..88f263bc 100644 --- a/ruma-identifiers/src/server_name.rs +++ b/ruma-identifiers/src/server_name.rs @@ -1,6 +1,35 @@ +//! Matrix-spec compliant server names. + +use crate::error::Error; + +/// A Matrix-spec compliant server name. +/// +/// It is discouraged to use this type directly – instead use one of the aliases ([`ServerName`](../type.ServerName.html) and +/// [`ServerNameRef`](../type.ServerNameRef.html)) in the crate root. +#[derive(Clone, Copy, Debug)] +pub struct ServerName { + full_id: T, +} + +fn try_from(server_name: S) -> Result, Error> +where + S: AsRef + Into, +{ + if !is_valid_server_name(server_name.as_ref()) { + return Err(Error::InvalidServerName); + } + Ok(ServerName { full_id: server_name.into() }) +} + +common_impls!(ServerName, try_from, "An IP address or hostname"); + /// Check whether a given string is a valid server name according to [the specification][]. /// +/// Deprecated. Use the `try_from()` method of [`ServerName`](server_name/struct.ServerName.html) to construct +/// a server name instead. +/// /// [the specification]: https://matrix.org/docs/spec/appendices#server-name +#[deprecated] pub fn is_valid_server_name(name: &str) -> bool { use std::net::Ipv6Addr; diff --git a/ruma-identifiers/src/user_id.rs b/ruma-identifiers/src/user_id.rs index 656ca5b2..753cda4f 100644 --- a/ruma-identifiers/src/user_id.rs +++ b/ruma-identifiers/src/user_id.rs @@ -1,8 +1,8 @@ //! Matrix user identifiers. -use std::num::NonZeroU8; +use std::{convert::TryFrom, num::NonZeroU8}; -use crate::{error::Error, is_valid_server_name, parse_id}; +use crate::{error::Error, parse_id, ServerNameRef}; /// A Matrix user ID. /// @@ -35,22 +35,17 @@ pub struct UserId { impl UserId { /// Attempts to generate a `UserId` for the given origin server with a localpart consisting of /// 12 random ASCII characters. - /// - /// Fails if the given homeserver cannot be parsed as a valid host. #[cfg(feature = "rand")] #[cfg_attr(docsrs, doc(cfg(feature = "rand")))] - pub fn new(server_name: &str) -> Result + pub fn new(server_name: ServerNameRef<'_>) -> Self where String: Into, { use crate::generate_localpart; - if !is_valid_server_name(server_name) { - return Err(Error::InvalidServerName); - } let full_id = format!("@{}:{}", generate_localpart(12).to_lowercase(), server_name).into(); - Ok(Self { full_id, colon_idx: NonZeroU8::new(13).unwrap(), is_historical: false }) + Self { full_id, colon_idx: NonZeroU8::new(13).unwrap(), is_historical: false } } /// Attempts to complete a user ID, by adding the colon + server name and `@` prefix, if not @@ -62,7 +57,7 @@ impl UserId { /// the `@` prefix. pub fn parse_with_server_name( id: impl AsRef + Into, - server_name: &str, + server_name: ServerNameRef<'_>, ) -> Result where String: Into, @@ -73,9 +68,6 @@ impl UserId { try_from(id) } else { let is_fully_conforming = localpart_is_fully_comforming(id_str)?; - if !is_valid_server_name(server_name) { - return Err(Error::InvalidServerName); - } Ok(Self { full_id: format!("@{}:{}", id_str, server_name).into(), @@ -94,11 +86,12 @@ impl UserId { } /// Returns the server name of the user ID. - pub fn server_name(&self) -> &str + pub fn server_name(&self) -> ServerNameRef<'_> where T: AsRef, { - &self.full_id.as_ref()[self.colon_idx.get() as usize + 1..] + ServerNameRef::try_from(&self.full_id.as_ref()[self.colon_idx.get() as usize + 1..]) + .unwrap() } /// Whether this user ID is a historical one, i.e. one that doesn't conform to the latest @@ -158,7 +151,7 @@ mod tests { #[cfg(feature = "serde")] use serde_json::{from_str, to_string}; - use crate::error::Error; + use crate::{error::Error, ServerNameRef}; type UserId = super::UserId>; @@ -173,7 +166,8 @@ mod tests { #[test] fn parse_valid_user_id() { - let user_id = UserId::parse_with_server_name("@carl:example.com", "example.com") + let server_name = ServerNameRef::try_from("example.com").unwrap(); + let user_id = UserId::parse_with_server_name("@carl:example.com", server_name) .expect("Failed to create UserId."); assert_eq!(user_id.as_ref(), "@carl:example.com"); assert_eq!(user_id.localpart(), "carl"); @@ -183,8 +177,9 @@ mod tests { #[test] fn parse_valid_user_id_parts() { - let user_id = UserId::parse_with_server_name("carl", "example.com") - .expect("Failed to create UserId."); + let server_name = ServerNameRef::try_from("example.com").unwrap(); + let user_id = + UserId::parse_with_server_name("carl", server_name).expect("Failed to create UserId."); assert_eq!(user_id.as_ref(), "@carl:example.com"); assert_eq!(user_id.localpart(), "carl"); assert_eq!(user_id.server_name(), "example.com"); @@ -202,7 +197,8 @@ mod tests { #[test] fn parse_valid_historical_user_id() { - let user_id = UserId::parse_with_server_name("@a%b[irc]:example.com", "example.com") + let server_name = ServerNameRef::try_from("example.com").unwrap(); + let user_id = UserId::parse_with_server_name("@a%b[irc]:example.com", server_name) .expect("Failed to create UserId."); assert_eq!(user_id.as_ref(), "@a%b[irc]:example.com"); assert_eq!(user_id.localpart(), "a%b[irc]"); @@ -212,7 +208,8 @@ mod tests { #[test] fn parse_valid_historical_user_id_parts() { - let user_id = UserId::parse_with_server_name("a%b[irc]", "example.com") + let server_name = ServerNameRef::try_from("example.com").unwrap(); + let user_id = UserId::parse_with_server_name("a%b[irc]", server_name) .expect("Failed to create UserId."); assert_eq!(user_id.as_ref(), "@a%b[irc]:example.com"); assert_eq!(user_id.localpart(), "a%b[irc]"); @@ -230,7 +227,8 @@ mod tests { #[cfg(feature = "rand")] #[test] fn generate_random_valid_user_id() { - let user_id = UserId::new("example.com").expect("Failed to generate UserId."); + let server_name = ServerNameRef::try_from("example.com").unwrap(); + let user_id = UserId::new(server_name); assert_eq!(user_id.localpart().len(), 12); assert_eq!(user_id.server_name(), "example.com"); @@ -240,12 +238,6 @@ mod tests { assert_eq!(id_str.len(), 25); } - #[cfg(feature = "rand")] - #[test] - fn generate_random_invalid_user_id() { - assert!(UserId::new("").is_err()); - } - #[cfg(feature = "serde")] #[test] fn serialize_valid_user_id() {