1use std::{cmp::Ordering, str::FromStr};
4
5use ruma_macros::DisplayAsRefStr;
6use serde::{Deserialize, Deserializer, Serialize, Serializer};
7
8use super::IdParseError;
9use crate::room_version_rules::RoomVersionRules;
10
11#[derive(Clone, Debug, PartialEq, Eq, Hash, DisplayAsRefStr)]
32#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
33pub enum RoomVersionId {
34 V1,
36
37 V2,
39
40 V3,
42
43 V4,
45
46 V5,
48
49 V6,
51
52 V7,
54
55 V8,
57
58 V9,
60
61 V10,
63
64 V11,
66
67 #[cfg(feature = "unstable-hydra")]
69 HydraV11,
70
71 V12,
73
74 #[cfg(feature = "unstable-msc2870")]
78 MSC2870,
79
80 #[doc(hidden)]
81 _Custom(CustomRoomVersion),
82}
83
84impl RoomVersionId {
85 pub fn as_str(&self) -> &str {
87 match &self {
90 Self::V1 => "1",
91 Self::V2 => "2",
92 Self::V3 => "3",
93 Self::V4 => "4",
94 Self::V5 => "5",
95 Self::V6 => "6",
96 Self::V7 => "7",
97 Self::V8 => "8",
98 Self::V9 => "9",
99 Self::V10 => "10",
100 Self::V11 => "11",
101 #[cfg(feature = "unstable-hydra")]
102 Self::HydraV11 => "org.matrix.hydra.11",
103 Self::V12 => "12",
104 #[cfg(feature = "unstable-msc2870")]
105 Self::MSC2870 => "org.matrix.msc2870",
106 Self::_Custom(version) => version.as_str(),
107 }
108 }
109
110 pub fn as_bytes(&self) -> &[u8] {
112 self.as_str().as_bytes()
113 }
114
115 pub fn rules(&self) -> Option<RoomVersionRules> {
120 Some(match self {
121 Self::V1 => RoomVersionRules::V1,
122 Self::V2 => RoomVersionRules::V2,
123 Self::V3 => RoomVersionRules::V3,
124 Self::V4 => RoomVersionRules::V4,
125 Self::V5 => RoomVersionRules::V5,
126 Self::V6 => RoomVersionRules::V6,
127 Self::V7 => RoomVersionRules::V7,
128 Self::V8 => RoomVersionRules::V8,
129 Self::V9 => RoomVersionRules::V9,
130 Self::V10 => RoomVersionRules::V10,
131 Self::V11 => RoomVersionRules::V11,
132 #[cfg(feature = "unstable-hydra")]
133 Self::HydraV11 => RoomVersionRules::HYDRA_V11,
134 Self::V12 => RoomVersionRules::V12,
135 #[cfg(feature = "unstable-msc2870")]
136 Self::MSC2870 => RoomVersionRules::MSC2870,
137 Self::_Custom(_) => return None,
138 })
139 }
140}
141
142impl From<RoomVersionId> for String {
143 fn from(id: RoomVersionId) -> Self {
144 match id {
145 RoomVersionId::_Custom(version) => version.into(),
146 id => id.as_str().to_owned(),
147 }
148 }
149}
150
151impl AsRef<str> for RoomVersionId {
152 fn as_ref(&self) -> &str {
153 self.as_str()
154 }
155}
156
157impl AsRef<[u8]> for RoomVersionId {
158 fn as_ref(&self) -> &[u8] {
159 self.as_bytes()
160 }
161}
162
163impl PartialOrd for RoomVersionId {
164 fn partial_cmp(&self, other: &RoomVersionId) -> Option<Ordering> {
170 Some(self.cmp(other))
171 }
172}
173
174impl Ord for RoomVersionId {
175 fn cmp(&self, other: &Self) -> Ordering {
181 self.as_str().cmp(other.as_str())
182 }
183}
184
185impl Serialize for RoomVersionId {
186 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
187 where
188 S: Serializer,
189 {
190 serializer.serialize_str(self.as_str())
191 }
192}
193
194impl<'de> Deserialize<'de> for RoomVersionId {
195 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
196 where
197 D: Deserializer<'de>,
198 {
199 super::deserialize_id(deserializer, "a Matrix room version ID as a string")
200 }
201}
202
203fn try_from<S>(room_version_id: S) -> Result<RoomVersionId, IdParseError>
205where
206 S: AsRef<str> + Into<Box<str>>,
207{
208 let version = match room_version_id.as_ref() {
209 "1" => RoomVersionId::V1,
210 "2" => RoomVersionId::V2,
211 "3" => RoomVersionId::V3,
212 "4" => RoomVersionId::V4,
213 "5" => RoomVersionId::V5,
214 "6" => RoomVersionId::V6,
215 "7" => RoomVersionId::V7,
216 "8" => RoomVersionId::V8,
217 "9" => RoomVersionId::V9,
218 "10" => RoomVersionId::V10,
219 "11" => RoomVersionId::V11,
220 #[cfg(feature = "unstable-hydra")]
221 "org.matrix.hydra.11" => RoomVersionId::HydraV11,
222 "12" => RoomVersionId::V12,
223 #[cfg(feature = "unstable-msc2870")]
224 "org.matrix.msc2870" => RoomVersionId::MSC2870,
225 custom => {
226 ruma_identifiers_validation::room_version_id::validate(custom)?;
227 RoomVersionId::_Custom(CustomRoomVersion(room_version_id.into()))
228 }
229 };
230
231 Ok(version)
232}
233
234impl FromStr for RoomVersionId {
235 type Err = IdParseError;
236
237 fn from_str(s: &str) -> Result<Self, Self::Err> {
238 try_from(s)
239 }
240}
241
242impl TryFrom<&str> for RoomVersionId {
243 type Error = IdParseError;
244
245 fn try_from(s: &str) -> Result<Self, Self::Error> {
246 try_from(s)
247 }
248}
249
250impl TryFrom<String> for RoomVersionId {
251 type Error = IdParseError;
252
253 fn try_from(s: String) -> Result<Self, Self::Error> {
254 try_from(s)
255 }
256}
257
258impl PartialEq<&str> for RoomVersionId {
259 fn eq(&self, other: &&str) -> bool {
260 self.as_str() == *other
261 }
262}
263
264impl PartialEq<RoomVersionId> for &str {
265 fn eq(&self, other: &RoomVersionId) -> bool {
266 *self == other.as_str()
267 }
268}
269
270impl PartialEq<String> for RoomVersionId {
271 fn eq(&self, other: &String) -> bool {
272 self.as_str() == other
273 }
274}
275
276impl PartialEq<RoomVersionId> for String {
277 fn eq(&self, other: &RoomVersionId) -> bool {
278 self == other.as_str()
279 }
280}
281
282#[derive(Clone, Debug, PartialEq, Eq, Hash)]
283#[doc(hidden)]
284#[allow(unknown_lints, unnameable_types)]
285pub struct CustomRoomVersion(Box<str>);
286
287#[doc(hidden)]
288impl CustomRoomVersion {
289 pub fn as_str(&self) -> &str {
291 &self.0
292 }
293}
294
295#[doc(hidden)]
296impl From<CustomRoomVersion> for String {
297 fn from(v: CustomRoomVersion) -> Self {
298 v.0.into()
299 }
300}
301
302#[doc(hidden)]
303impl AsRef<str> for CustomRoomVersion {
304 fn as_ref(&self) -> &str {
305 self.as_str()
306 }
307}
308
309#[cfg(test)]
310mod tests {
311 use super::RoomVersionId;
312 use crate::IdParseError;
313
314 #[test]
315 fn valid_version_1_room_version_id() {
316 assert_eq!(
317 RoomVersionId::try_from("1").expect("Failed to create RoomVersionId.").as_str(),
318 "1"
319 );
320 }
321
322 #[test]
323 fn valid_version_2_room_version_id() {
324 assert_eq!(
325 RoomVersionId::try_from("2").expect("Failed to create RoomVersionId.").as_str(),
326 "2"
327 );
328 }
329
330 #[test]
331 fn valid_version_3_room_version_id() {
332 assert_eq!(
333 RoomVersionId::try_from("3").expect("Failed to create RoomVersionId.").as_str(),
334 "3"
335 );
336 }
337
338 #[test]
339 fn valid_version_4_room_version_id() {
340 assert_eq!(
341 RoomVersionId::try_from("4").expect("Failed to create RoomVersionId.").as_str(),
342 "4"
343 );
344 }
345
346 #[test]
347 fn valid_version_5_room_version_id() {
348 assert_eq!(
349 RoomVersionId::try_from("5").expect("Failed to create RoomVersionId.").as_str(),
350 "5"
351 );
352 }
353
354 #[test]
355 fn valid_version_6_room_version_id() {
356 assert_eq!(
357 RoomVersionId::try_from("6").expect("Failed to create RoomVersionId.").as_str(),
358 "6"
359 );
360 }
361
362 #[test]
363 fn valid_custom_room_version_id() {
364 assert_eq!(
365 RoomVersionId::try_from("io.ruma.1").expect("Failed to create RoomVersionId.").as_str(),
366 "io.ruma.1"
367 );
368 }
369
370 #[test]
371 fn empty_room_version_id() {
372 assert_eq!(RoomVersionId::try_from(""), Err(IdParseError::Empty));
373 }
374
375 #[test]
376 fn over_max_code_point_room_version_id() {
377 assert_eq!(
378 RoomVersionId::try_from("0123456789012345678901234567890123456789"),
379 Err(IdParseError::MaximumLengthExceeded)
380 );
381 }
382
383 #[test]
384 fn serialize_official_room_id() {
385 assert_eq!(
386 serde_json::to_string(
387 &RoomVersionId::try_from("1").expect("Failed to create RoomVersionId.")
388 )
389 .expect("Failed to convert RoomVersionId to JSON."),
390 r#""1""#
391 );
392 }
393
394 #[test]
395 fn deserialize_official_room_id() {
396 let deserialized = serde_json::from_str::<RoomVersionId>(r#""1""#)
397 .expect("Failed to convert RoomVersionId to JSON.");
398
399 assert_eq!(deserialized, RoomVersionId::V1);
400
401 assert_eq!(
402 deserialized,
403 RoomVersionId::try_from("1").expect("Failed to create RoomVersionId.")
404 );
405 }
406
407 #[test]
408 fn serialize_custom_room_id() {
409 assert_eq!(
410 serde_json::to_string(
411 &RoomVersionId::try_from("io.ruma.1").expect("Failed to create RoomVersionId.")
412 )
413 .expect("Failed to convert RoomVersionId to JSON."),
414 r#""io.ruma.1""#
415 );
416 }
417
418 #[test]
419 fn deserialize_custom_room_id() {
420 let deserialized = serde_json::from_str::<RoomVersionId>(r#""io.ruma.1""#)
421 .expect("Failed to convert RoomVersionId to JSON.");
422
423 assert_eq!(
424 deserialized,
425 RoomVersionId::try_from("io.ruma.1").expect("Failed to create RoomVersionId.")
426 );
427 }
428
429 #[test]
430 fn custom_room_id_invalid_character() {
431 assert!(serde_json::from_str::<RoomVersionId>(r#""io_ruma_1""#).is_err());
432 assert!(serde_json::from_str::<RoomVersionId>(r#""=""#).is_err());
433 assert!(serde_json::from_str::<RoomVersionId>(r#""/""#).is_err());
434 assert!(serde_json::from_str::<RoomVersionId>(r#"".""#).is_ok());
435 assert!(serde_json::from_str::<RoomVersionId>(r#""-""#).is_ok());
436 assert_eq!(
437 RoomVersionId::try_from("io_ruma_1").unwrap_err(),
438 IdParseError::InvalidCharacters
439 );
440 }
441}