ruma_identifiers/
room_version_id.rs

1//! Matrix room version identifiers.
2
3use std::{cmp::Ordering, convert::TryFrom, str::FromStr};
4
5use ruma_serde_macros::DisplayAsRefStr;
6#[cfg(feature = "serde")]
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8
9/// A Matrix [room version] ID.
10///
11/// A `RoomVersionId` can be or converted or deserialized from a string slice, and can be converted
12/// or serialized back into a string as needed.
13///
14/// ```
15/// # use std::convert::TryFrom;
16/// # use ruma_identifiers::RoomVersionId;
17/// assert_eq!(RoomVersionId::try_from("1").unwrap().as_ref(), "1");
18/// ```
19///
20/// Any string consisting of at minimum 1, at maximum 32 unicode codepoints is a room version ID.
21/// Custom room versions or ones that were introduced into the specification after this code was
22/// written are represented by a hidden enum variant. You can still construct them the same, and
23/// check for them using one of `RoomVersionId`s `PartialEq` implementations or through `.as_str()`.
24///
25/// [room version]: https://spec.matrix.org/v1.2/rooms/
26#[derive(Clone, Debug, PartialEq, Eq, Hash, DisplayAsRefStr)]
27#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
28pub enum RoomVersionId {
29    /// A version 1 room.
30    V1,
31
32    /// A version 2 room.
33    V2,
34
35    /// A version 3 room.
36    V3,
37
38    /// A version 4 room.
39    V4,
40
41    /// A version 5 room.
42    V5,
43
44    /// A version 6 room.
45    V6,
46
47    /// A version 7 room.
48    V7,
49
50    /// A version 8 room.
51    V8,
52
53    /// A version 9 room.
54    V9,
55
56    #[doc(hidden)]
57    _Custom(CustomRoomVersion),
58}
59
60impl RoomVersionId {
61    /// Creates a string slice from this `RoomVersionId`.
62    pub fn as_str(&self) -> &str {
63        // FIXME: Add support for non-`str`-deref'ing types for fallback to AsRefStr derive and
64        //        implement this function in terms of `AsRef<str>`
65        match &self {
66            Self::V1 => "1",
67            Self::V2 => "2",
68            Self::V3 => "3",
69            Self::V4 => "4",
70            Self::V5 => "5",
71            Self::V6 => "6",
72            Self::V7 => "7",
73            Self::V8 => "8",
74            Self::V9 => "9",
75            Self::_Custom(version) => version.as_str(),
76        }
77    }
78
79    /// Creates a byte slice from this `RoomVersionId`.
80    pub fn as_bytes(&self) -> &[u8] {
81        self.as_str().as_bytes()
82    }
83}
84
85impl From<RoomVersionId> for String {
86    fn from(id: RoomVersionId) -> Self {
87        match id {
88            RoomVersionId::V1 => "1".to_owned(),
89            RoomVersionId::V2 => "2".to_owned(),
90            RoomVersionId::V3 => "3".to_owned(),
91            RoomVersionId::V4 => "4".to_owned(),
92            RoomVersionId::V5 => "5".to_owned(),
93            RoomVersionId::V6 => "6".to_owned(),
94            RoomVersionId::V7 => "7".to_owned(),
95            RoomVersionId::V8 => "8".to_owned(),
96            RoomVersionId::V9 => "9".to_owned(),
97            RoomVersionId::_Custom(version) => version.into(),
98        }
99    }
100}
101
102impl AsRef<str> for RoomVersionId {
103    fn as_ref(&self) -> &str {
104        self.as_str()
105    }
106}
107
108impl PartialOrd for RoomVersionId {
109    /// Compare the two given room version IDs by comparing their string representations.
110    ///
111    /// Please be aware that room version IDs don't have a defined ordering in the Matrix
112    /// specification. This implementation only exists to be able to use `RoomVersionId`s or
113    /// types containing `RoomVersionId`s as `BTreeMap` keys.
114    fn partial_cmp(&self, other: &RoomVersionId) -> Option<Ordering> {
115        self.as_ref().partial_cmp(other.as_ref())
116    }
117}
118
119impl Ord for RoomVersionId {
120    /// Compare the two given room version IDs by comparing their string representations.
121    ///
122    /// Please be aware that room version IDs don't have a defined ordering in the Matrix
123    /// specification. This implementation only exists to be able to use `RoomVersionId`s or
124    /// types containing `RoomVersionId`s as `BTreeMap` keys.
125    fn cmp(&self, other: &Self) -> Ordering {
126        self.as_ref().cmp(other.as_ref())
127    }
128}
129
130#[cfg(feature = "serde")]
131impl Serialize for RoomVersionId {
132    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
133    where
134        S: Serializer,
135    {
136        serializer.serialize_str(self.as_ref())
137    }
138}
139
140#[cfg(feature = "serde")]
141impl<'de> Deserialize<'de> for RoomVersionId {
142    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
143    where
144        D: Deserializer<'de>,
145    {
146        crate::deserialize_id(deserializer, "a Matrix room version ID as a string")
147    }
148}
149
150/// Attempts to create a new Matrix room version ID from a string representation.
151fn try_from<S>(room_version_id: S) -> Result<RoomVersionId, crate::Error>
152where
153    S: AsRef<str> + Into<Box<str>>,
154{
155    let version = match room_version_id.as_ref() {
156        "1" => RoomVersionId::V1,
157        "2" => RoomVersionId::V2,
158        "3" => RoomVersionId::V3,
159        "4" => RoomVersionId::V4,
160        "5" => RoomVersionId::V5,
161        "6" => RoomVersionId::V6,
162        "7" => RoomVersionId::V7,
163        "8" => RoomVersionId::V8,
164        "9" => RoomVersionId::V9,
165        custom => {
166            ruma_identifiers_validation::room_version_id::validate(custom)?;
167            RoomVersionId::_Custom(CustomRoomVersion(room_version_id.into()))
168        }
169    };
170
171    Ok(version)
172}
173
174impl FromStr for RoomVersionId {
175    type Err = crate::Error;
176
177    fn from_str(s: &str) -> Result<Self, crate::Error> {
178        try_from(s)
179    }
180}
181
182impl TryFrom<&str> for RoomVersionId {
183    type Error = crate::Error;
184
185    fn try_from(s: &str) -> Result<Self, crate::Error> {
186        try_from(s)
187    }
188}
189
190impl TryFrom<String> for RoomVersionId {
191    type Error = crate::Error;
192
193    fn try_from(s: String) -> Result<Self, crate::Error> {
194        try_from(s)
195    }
196}
197
198impl PartialEq<&str> for RoomVersionId {
199    fn eq(&self, other: &&str) -> bool {
200        self.as_str() == *other
201    }
202}
203
204impl PartialEq<RoomVersionId> for &str {
205    fn eq(&self, other: &RoomVersionId) -> bool {
206        *self == other.as_str()
207    }
208}
209
210impl PartialEq<String> for RoomVersionId {
211    fn eq(&self, other: &String) -> bool {
212        self.as_str() == other
213    }
214}
215
216impl PartialEq<RoomVersionId> for String {
217    fn eq(&self, other: &RoomVersionId) -> bool {
218        self == other.as_str()
219    }
220}
221
222#[derive(Clone, Debug, PartialEq, Eq, Hash)]
223#[doc(hidden)]
224pub struct CustomRoomVersion(Box<str>);
225
226#[doc(hidden)]
227impl CustomRoomVersion {
228    /// Creates a string slice from this `CustomRoomVersion`
229    pub fn as_str(&self) -> &str {
230        &self.0
231    }
232}
233
234#[doc(hidden)]
235impl From<CustomRoomVersion> for String {
236    fn from(v: CustomRoomVersion) -> Self {
237        v.0.into()
238    }
239}
240
241#[doc(hidden)]
242impl AsRef<str> for CustomRoomVersion {
243    fn as_ref(&self) -> &str {
244        self.as_str()
245    }
246}
247
248#[cfg(test)]
249mod tests {
250    use std::convert::TryFrom;
251
252    use super::RoomVersionId;
253    use crate::Error;
254
255    #[test]
256    fn valid_version_1_room_version_id() {
257        assert_eq!(
258            RoomVersionId::try_from("1").expect("Failed to create RoomVersionId.").as_ref(),
259            "1"
260        );
261    }
262
263    #[test]
264    fn valid_version_2_room_version_id() {
265        assert_eq!(
266            RoomVersionId::try_from("2").expect("Failed to create RoomVersionId.").as_ref(),
267            "2"
268        );
269    }
270
271    #[test]
272    fn valid_version_3_room_version_id() {
273        assert_eq!(
274            RoomVersionId::try_from("3").expect("Failed to create RoomVersionId.").as_ref(),
275            "3"
276        );
277    }
278
279    #[test]
280    fn valid_version_4_room_version_id() {
281        assert_eq!(
282            RoomVersionId::try_from("4").expect("Failed to create RoomVersionId.").as_ref(),
283            "4"
284        );
285    }
286
287    #[test]
288    fn valid_version_5_room_version_id() {
289        assert_eq!(
290            RoomVersionId::try_from("5").expect("Failed to create RoomVersionId.").as_ref(),
291            "5"
292        );
293    }
294
295    #[test]
296    fn valid_version_6_room_version_id() {
297        assert_eq!(
298            RoomVersionId::try_from("6").expect("Failed to create RoomVersionId.").as_ref(),
299            "6"
300        );
301    }
302
303    #[test]
304    fn valid_custom_room_version_id() {
305        assert_eq!(
306            RoomVersionId::try_from("io.ruma.1").expect("Failed to create RoomVersionId.").as_ref(),
307            "io.ruma.1"
308        );
309    }
310
311    #[test]
312    fn empty_room_version_id() {
313        assert_eq!(RoomVersionId::try_from(""), Err(Error::EmptyRoomVersionId));
314    }
315
316    #[test]
317    fn over_max_code_point_room_version_id() {
318        assert_eq!(
319            RoomVersionId::try_from("0123456789012345678901234567890123456789"),
320            Err(Error::MaximumLengthExceeded)
321        );
322    }
323
324    #[cfg(feature = "serde")]
325    #[test]
326    fn serialize_official_room_id() {
327        assert_eq!(
328            serde_json::to_string(
329                &RoomVersionId::try_from("1").expect("Failed to create RoomVersionId.")
330            )
331            .expect("Failed to convert RoomVersionId to JSON."),
332            r#""1""#
333        );
334    }
335
336    #[cfg(feature = "serde")]
337    #[test]
338    fn deserialize_official_room_id() {
339        let deserialized = serde_json::from_str::<RoomVersionId>(r#""1""#)
340            .expect("Failed to convert RoomVersionId to JSON.");
341
342        assert_eq!(deserialized, RoomVersionId::V1);
343
344        assert_eq!(
345            deserialized,
346            RoomVersionId::try_from("1").expect("Failed to create RoomVersionId.")
347        );
348    }
349
350    #[cfg(feature = "serde")]
351    #[test]
352    fn serialize_custom_room_id() {
353        assert_eq!(
354            serde_json::to_string(
355                &RoomVersionId::try_from("io.ruma.1").expect("Failed to create RoomVersionId.")
356            )
357            .expect("Failed to convert RoomVersionId to JSON."),
358            r#""io.ruma.1""#
359        );
360    }
361
362    #[cfg(feature = "serde")]
363    #[test]
364    fn deserialize_custom_room_id() {
365        let deserialized = serde_json::from_str::<RoomVersionId>(r#""io.ruma.1""#)
366            .expect("Failed to convert RoomVersionId to JSON.");
367
368        assert_eq!(
369            deserialized,
370            RoomVersionId::try_from("io.ruma.1").expect("Failed to create RoomVersionId.")
371        );
372    }
373}