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