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