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