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