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