diff --git a/crates/ruma-events/CHANGELOG.md b/crates/ruma-events/CHANGELOG.md index 9c20f697c7..5d3efa9bab 100644 --- a/crates/ruma-events/CHANGELOG.md +++ b/crates/ruma-events/CHANGELOG.md @@ -1,5 +1,14 @@ # [unreleased] +Breaking changes: + +- Take newly introduced `DirectUserIdentifier(str)` as a key for `DirectEventContent`. + This change allows to have an email or MSISDN phone number as a key for example, + which can be used when issuing invites through third-party systems. + `DirectUserIdentifier` can easily be converted to an `UserId`. + +# 0.29.1 + Bug fixes: - Markdown text constructors now detect even more markdown syntax like removed diff --git a/crates/ruma-events/src/direct.rs b/crates/ruma-events/src/direct.rs index e8d7d2c6a9..e4a3f9f160 100644 --- a/crates/ruma-events/src/direct.rs +++ b/crates/ruma-events/src/direct.rs @@ -7,23 +7,155 @@ use std::{ ops::{Deref, DerefMut}, }; -use ruma_common::{OwnedRoomId, OwnedUserId}; -use ruma_macros::EventContent; +use ruma_common::{IdParseError, OwnedRoomId, OwnedUserId, UserId}; +use ruma_macros::{EventContent, IdZst}; use serde::{Deserialize, Serialize}; +/// An user identifier, it can be a [`UserId`] or a third-party identifier +/// like an email or a phone number. +/// +/// There is no validation on this type, any string is allowed, +/// but you can use `as_user_id` or `into_user_id` to try to get an [`UserId`]. +#[repr(transparent)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, IdZst)] +pub struct DirectUserIdentifier(str); + +impl DirectUserIdentifier { + /// Get this `DirectUserIdentifier` as an [`UserId`] if it is one. + pub fn as_user_id(&self) -> Option<&UserId> { + self.0.try_into().ok() + } +} + +impl OwnedDirectUserIdentifier { + /// Get this `OwnedDirectUserIdentifier` as an [`UserId`] if it is one. + pub fn as_user_id(&self) -> Option<&UserId> { + self.0.try_into().ok() + } + + /// Get this `OwnedDirectUserIdentifier` as an [`OwnedUserId`] if it is one. + pub fn into_user_id(self) -> Option { + OwnedUserId::try_from(self).ok() + } +} + +impl TryFrom for OwnedUserId { + type Error = IdParseError; + + fn try_from(value: OwnedDirectUserIdentifier) -> Result { + value.0.try_into() + } +} + +impl TryFrom<&OwnedDirectUserIdentifier> for OwnedUserId { + type Error = IdParseError; + + fn try_from(value: &OwnedDirectUserIdentifier) -> Result { + value.0.try_into() + } +} + +impl TryFrom<&DirectUserIdentifier> for OwnedUserId { + type Error = IdParseError; + + fn try_from(value: &DirectUserIdentifier) -> Result { + value.0.try_into() + } +} + +impl<'a> TryFrom<&'a DirectUserIdentifier> for &'a UserId { + type Error = IdParseError; + + fn try_from(value: &'a DirectUserIdentifier) -> Result { + value.0.try_into() + } +} + +impl From for OwnedDirectUserIdentifier { + fn from(value: OwnedUserId) -> Self { + DirectUserIdentifier::from_borrowed(value.as_str()).to_owned() + } +} + +impl From<&OwnedUserId> for OwnedDirectUserIdentifier { + fn from(value: &OwnedUserId) -> Self { + DirectUserIdentifier::from_borrowed(value.as_str()).to_owned() + } +} + +impl From<&UserId> for OwnedDirectUserIdentifier { + fn from(value: &UserId) -> Self { + DirectUserIdentifier::from_borrowed(value.as_str()).to_owned() + } +} + +impl<'a> From<&'a UserId> for &'a DirectUserIdentifier { + fn from(value: &'a UserId) -> Self { + DirectUserIdentifier::from_borrowed(value.as_str()) + } +} + +impl PartialEq<&UserId> for &DirectUserIdentifier { + fn eq(&self, other: &&UserId) -> bool { + self.0.eq(other.as_str()) + } +} + +impl PartialEq<&DirectUserIdentifier> for &UserId { + fn eq(&self, other: &&DirectUserIdentifier) -> bool { + other.0.eq(self.as_str()) + } +} + +impl PartialEq for &DirectUserIdentifier { + fn eq(&self, other: &OwnedUserId) -> bool { + self.0.eq(other.as_str()) + } +} + +impl PartialEq<&DirectUserIdentifier> for OwnedUserId { + fn eq(&self, other: &&DirectUserIdentifier) -> bool { + other.0.eq(self.as_str()) + } +} + +impl PartialEq<&UserId> for OwnedDirectUserIdentifier { + fn eq(&self, other: &&UserId) -> bool { + self.0.eq(other.as_str()) + } +} + +impl PartialEq for &UserId { + fn eq(&self, other: &OwnedDirectUserIdentifier) -> bool { + other.0.eq(self.as_str()) + } +} + +impl PartialEq for OwnedDirectUserIdentifier { + fn eq(&self, other: &OwnedUserId) -> bool { + self.0.eq(other.as_str()) + } +} + +impl PartialEq for OwnedUserId { + fn eq(&self, other: &OwnedDirectUserIdentifier) -> bool { + other.0.eq(self.as_str()) + } +} + /// The content of an `m.direct` event. /// -/// A mapping of `UserId`s to a list of `RoomId`s which are considered *direct* for that particular -/// user. +/// A mapping of `DirectUserIdentifier`s to a list of `RoomId`s which are considered *direct* +/// for that particular user. /// /// Informs the client about the rooms that are considered direct by a user. #[derive(Clone, Debug, Default, Deserialize, Serialize, EventContent)] #[allow(clippy::exhaustive_structs)] #[ruma_event(type = "m.direct", kind = GlobalAccountData)] -pub struct DirectEventContent(pub BTreeMap>); +pub struct DirectEventContent(pub BTreeMap>); impl Deref for DirectEventContent { - type Target = BTreeMap>; + type Target = BTreeMap>; fn deref(&self) -> &Self::Target { &self.0 @@ -37,18 +169,18 @@ impl DerefMut for DirectEventContent { } impl IntoIterator for DirectEventContent { - type Item = (OwnedUserId, Vec); - type IntoIter = btree_map::IntoIter>; + type Item = (OwnedDirectUserIdentifier, Vec); + type IntoIter = btree_map::IntoIter>; fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } } -impl FromIterator<(OwnedUserId, Vec)> for DirectEventContent { +impl FromIterator<(OwnedDirectUserIdentifier, Vec)> for DirectEventContent { fn from_iter(iter: T) -> Self where - T: IntoIterator)>, + T: IntoIterator)>, { Self(BTreeMap::from_iter(iter)) } @@ -58,21 +190,26 @@ impl FromIterator<(OwnedUserId, Vec)> for DirectEventContent { mod tests { use std::collections::BTreeMap; - use ruma_common::{owned_room_id, owned_user_id}; + use ruma_common::{owned_room_id, user_id, OwnedUserId}; use serde_json::{from_value as from_json_value, json, to_value as to_json_value}; use super::{DirectEvent, DirectEventContent}; + use crate::direct::{DirectUserIdentifier, OwnedDirectUserIdentifier}; #[test] fn serialization() { let mut content = DirectEventContent(BTreeMap::new()); - let alice = owned_user_id!("@alice:ruma.io"); + let alice = user_id!("@alice:ruma.io"); + let alice_mail = "alice@ruma.io"; let rooms = vec![owned_room_id!("!1:ruma.io")]; + let mail_rooms = vec![owned_room_id!("!3:ruma.io")]; - content.insert(alice.clone(), rooms.clone()); + content.insert(alice.into(), rooms.clone()); + content.insert(alice_mail.into(), mail_rooms.clone()); let json_data = json!({ alice: rooms, + alice_mail: mail_rooms, }); assert_eq!(to_json_value(&content).unwrap(), json_data); @@ -80,20 +217,55 @@ mod tests { #[test] fn deserialization() { - let alice = owned_user_id!("@alice:ruma.io"); + let alice = user_id!("@alice:ruma.io"); + let alice_mail = "alice@ruma.io"; let rooms = vec![owned_room_id!("!1:ruma.io"), owned_room_id!("!2:ruma.io")]; + let mail_rooms = vec![owned_room_id!("!3:ruma.io")]; let json_data = json!({ "content": { - alice.to_string(): rooms, + alice: rooms, + alice_mail: mail_rooms, }, "type": "m.direct" }); let event: DirectEvent = from_json_value(json_data).unwrap(); - let direct_rooms = event.content.get(&alice).unwrap(); + let direct_rooms = event.content.get(<&DirectUserIdentifier>::from(alice)).unwrap(); assert!(direct_rooms.contains(&rooms[0])); assert!(direct_rooms.contains(&rooms[1])); + + let email_direct_rooms = + event.content.get(<&DirectUserIdentifier>::from(alice_mail)).unwrap(); + assert!(email_direct_rooms.contains(&mail_rooms[0])); + } + + #[test] + fn user_id_conversion() { + let alice_direct_uid = DirectUserIdentifier::from_borrowed("@alice:ruma.io"); + let alice_owned_user_id: OwnedUserId = alice_direct_uid + .to_owned() + .try_into() + .expect("@alice:ruma.io should be convertible into a Matrix user ID"); + assert_eq!(alice_direct_uid, alice_owned_user_id); + + let alice_direct_uid_mail = DirectUserIdentifier::from_borrowed("alice@ruma.io"); + OwnedUserId::try_from(alice_direct_uid_mail.to_owned()) + .expect_err("alice@ruma.io should not be convertible into a Matrix user ID"); + + let alice_user_id = user_id!("@alice:ruma.io"); + let alice_direct_uid_mail: &DirectUserIdentifier = alice_user_id.into(); + assert_eq!(alice_direct_uid_mail, alice_user_id); + assert_eq!(alice_direct_uid_mail, alice_user_id.to_owned()); + assert_eq!(alice_user_id, alice_direct_uid_mail); + assert_eq!(alice_user_id.to_owned(), alice_direct_uid_mail); + + let alice_user_id = user_id!("@alice:ruma.io"); + let alice_direct_uid_mail: OwnedDirectUserIdentifier = alice_user_id.into(); + assert_eq!(alice_direct_uid_mail, alice_user_id); + assert_eq!(alice_direct_uid_mail, alice_user_id.to_owned()); + assert_eq!(alice_user_id, alice_direct_uid_mail); + assert_eq!(alice_user_id.to_owned(), alice_direct_uid_mail); } }