ruma_common/identifiers/
room_or_alias_id.rs
1use std::hint::unreachable_unchecked;
4
5use ruma_macros::IdZst;
6use tracing::warn;
7
8use super::{server_name::ServerName, OwnedRoomAliasId, OwnedRoomId, RoomAliasId, RoomId};
9
10#[repr(transparent)]
29#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, IdZst)]
30#[ruma_id(validate = ruma_identifiers_validation::room_id_or_alias_id::validate)]
31pub struct RoomOrAliasId(str);
32
33impl RoomOrAliasId {
34 pub fn server_name(&self) -> Option<&ServerName> {
36 let colon_idx = self.as_str().find(':')?;
37 let server_name = &self.as_str()[colon_idx + 1..];
38 match server_name.try_into() {
39 Ok(parsed) => Some(parsed),
40 Err(e) => {
42 warn!(
43 target: "ruma_common::identifiers::room_id",
44 server_name,
45 "Room ID contains colon but no valid server name afterwards: {e}",
46 );
47 None
48 }
49 }
50 }
51
52 pub fn is_room_id(&self) -> bool {
54 self.variant() == Variant::RoomId
55 }
56
57 pub fn is_room_alias_id(&self) -> bool {
59 self.variant() == Variant::RoomAliasId
60 }
61
62 fn variant(&self) -> Variant {
63 match self.as_bytes().first() {
64 Some(b'!') => Variant::RoomId,
65 Some(b'#') => Variant::RoomAliasId,
66 _ => unsafe { unreachable_unchecked() },
67 }
68 }
69}
70
71#[derive(PartialEq, Eq)]
72enum Variant {
73 RoomId,
74 RoomAliasId,
75}
76
77impl<'a> From<&'a RoomId> for &'a RoomOrAliasId {
78 fn from(room_id: &'a RoomId) -> Self {
79 RoomOrAliasId::from_borrowed(room_id.as_str())
80 }
81}
82
83impl<'a> From<&'a RoomAliasId> for &'a RoomOrAliasId {
84 fn from(room_alias_id: &'a RoomAliasId) -> Self {
85 RoomOrAliasId::from_borrowed(room_alias_id.as_str())
86 }
87}
88
89impl From<OwnedRoomId> for OwnedRoomOrAliasId {
90 fn from(room_id: OwnedRoomId) -> Self {
91 RoomOrAliasId::from_borrowed(room_id.as_str()).to_owned()
93 }
94}
95
96impl From<OwnedRoomAliasId> for OwnedRoomOrAliasId {
97 fn from(room_alias_id: OwnedRoomAliasId) -> Self {
98 RoomOrAliasId::from_borrowed(room_alias_id.as_str()).to_owned()
100 }
101}
102
103impl<'a> TryFrom<&'a RoomOrAliasId> for &'a RoomId {
104 type Error = &'a RoomAliasId;
105
106 fn try_from(id: &'a RoomOrAliasId) -> Result<&'a RoomId, &'a RoomAliasId> {
107 match id.variant() {
108 Variant::RoomId => Ok(RoomId::from_borrowed(id.as_str())),
109 Variant::RoomAliasId => Err(RoomAliasId::from_borrowed(id.as_str())),
110 }
111 }
112}
113
114impl<'a> TryFrom<&'a RoomOrAliasId> for &'a RoomAliasId {
115 type Error = &'a RoomId;
116
117 fn try_from(id: &'a RoomOrAliasId) -> Result<&'a RoomAliasId, &'a RoomId> {
118 match id.variant() {
119 Variant::RoomAliasId => Ok(RoomAliasId::from_borrowed(id.as_str())),
120 Variant::RoomId => Err(RoomId::from_borrowed(id.as_str())),
121 }
122 }
123}
124
125impl TryFrom<OwnedRoomOrAliasId> for OwnedRoomId {
126 type Error = OwnedRoomAliasId;
127
128 fn try_from(id: OwnedRoomOrAliasId) -> Result<OwnedRoomId, OwnedRoomAliasId> {
129 match id.variant() {
131 Variant::RoomId => Ok(RoomId::from_borrowed(id.as_str()).to_owned()),
132 Variant::RoomAliasId => Err(RoomAliasId::from_borrowed(id.as_str()).to_owned()),
133 }
134 }
135}
136
137impl TryFrom<OwnedRoomOrAliasId> for OwnedRoomAliasId {
138 type Error = OwnedRoomId;
139
140 fn try_from(id: OwnedRoomOrAliasId) -> Result<OwnedRoomAliasId, OwnedRoomId> {
141 match id.variant() {
143 Variant::RoomAliasId => Ok(RoomAliasId::from_borrowed(id.as_str()).to_owned()),
144 Variant::RoomId => Err(RoomId::from_borrowed(id.as_str()).to_owned()),
145 }
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use super::{OwnedRoomOrAliasId, RoomOrAliasId};
152 use crate::IdParseError;
153
154 #[test]
155 fn valid_room_id_or_alias_id_with_a_room_alias_id() {
156 assert_eq!(
157 <&RoomOrAliasId>::try_from("#ruma:example.com")
158 .expect("Failed to create RoomAliasId.")
159 .as_str(),
160 "#ruma:example.com"
161 );
162 }
163
164 #[test]
165 fn valid_room_id_or_alias_id_with_a_room_id() {
166 assert_eq!(
167 <&RoomOrAliasId>::try_from("!29fhd83h92h0:example.com")
168 .expect("Failed to create RoomId.")
169 .as_str(),
170 "!29fhd83h92h0:example.com"
171 );
172 }
173
174 #[test]
175 fn missing_sigil_for_room_id_or_alias_id() {
176 assert_eq!(
177 <&RoomOrAliasId>::try_from("ruma:example.com").unwrap_err(),
178 IdParseError::MissingLeadingSigil
179 );
180 }
181
182 #[test]
183 fn serialize_valid_room_id_or_alias_id_with_a_room_alias_id() {
184 assert_eq!(
185 serde_json::to_string(
186 <&RoomOrAliasId>::try_from("#ruma:example.com")
187 .expect("Failed to create RoomAliasId.")
188 )
189 .expect("Failed to convert RoomAliasId to JSON."),
190 r##""#ruma:example.com""##
191 );
192 }
193
194 #[test]
195 fn serialize_valid_room_id_or_alias_id_with_a_room_id() {
196 assert_eq!(
197 serde_json::to_string(
198 <&RoomOrAliasId>::try_from("!29fhd83h92h0:example.com")
199 .expect("Failed to create RoomId.")
200 )
201 .expect("Failed to convert RoomId to JSON."),
202 r#""!29fhd83h92h0:example.com""#
203 );
204 }
205
206 #[test]
207 fn deserialize_valid_room_id_or_alias_id_with_a_room_alias_id() {
208 assert_eq!(
209 serde_json::from_str::<OwnedRoomOrAliasId>(r##""#ruma:example.com""##)
210 .expect("Failed to convert JSON to RoomAliasId"),
211 <&RoomOrAliasId>::try_from("#ruma:example.com").expect("Failed to create RoomAliasId.")
212 );
213 }
214
215 #[test]
216 fn deserialize_valid_room_id_or_alias_id_with_a_room_id() {
217 assert_eq!(
218 serde_json::from_str::<OwnedRoomOrAliasId>(r#""!29fhd83h92h0:example.com""#)
219 .expect("Failed to convert JSON to RoomId"),
220 <&RoomOrAliasId>::try_from("!29fhd83h92h0:example.com")
221 .expect("Failed to create RoomAliasId.")
222 );
223 }
224}