ruma_events/room/
create.rs

1//! Types for the [`m.room.create`] event.
2//!
3//! [`m.room.create`]: https://spec.matrix.org/latest/client-server-api/#mroomcreate
4
5use ruma_common::{
6    room::RoomType, room_version_rules::RedactionRules, OwnedEventId, OwnedRoomId, OwnedUserId,
7    RoomVersionId,
8};
9use ruma_macros::EventContent;
10use serde::{Deserialize, Serialize};
11
12use crate::{EmptyStateKey, RedactContent, RedactedStateEventContent, StateEventType};
13
14/// The content of an `m.room.create` event.
15///
16/// This is the first event in a room and cannot be changed.
17///
18/// It acts as the root of all other events.
19#[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
20#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
21#[ruma_event(type = "m.room.create", kind = State, state_key_type = EmptyStateKey, custom_redacted)]
22pub struct RoomCreateEventContent {
23    /// The `user_id` of the room creator.
24    ///
25    /// This is set by the homeserver.
26    ///
27    /// This is required in room versions 1 trough 10, but is removed starting from room version
28    /// 11.
29    #[serde(skip_serializing_if = "Option::is_none")]
30    #[deprecated = "Since Matrix 1.8. This field was removed in Room version 11, clients should use the event's sender instead"]
31    pub creator: Option<OwnedUserId>,
32
33    /// Whether or not this room's data should be transferred to other homeservers.
34    #[serde(
35        rename = "m.federate",
36        default = "ruma_common::serde::default_true",
37        skip_serializing_if = "ruma_common::serde::is_true"
38    )]
39    pub federate: bool,
40
41    /// The version of the room.
42    ///
43    /// Defaults to `RoomVersionId::V1`.
44    #[serde(default = "default_room_version_id")]
45    pub room_version: RoomVersionId,
46
47    /// A reference to the room this room replaces, if the previous room was upgraded.
48    ///
49    /// With the `compat-lax-room-create-deser` cargo feature, this field is ignored if its
50    /// deserialization fails.
51    #[serde(skip_serializing_if = "Option::is_none")]
52    #[cfg_attr(
53        feature = "compat-lax-room-create-deser",
54        serde(default, deserialize_with = "ruma_common::serde::default_on_error")
55    )]
56    pub predecessor: Option<PreviousRoom>,
57
58    /// The room type.
59    ///
60    /// This is currently only used for spaces.
61    #[serde(skip_serializing_if = "Option::is_none", rename = "type")]
62    pub room_type: Option<RoomType>,
63
64    /// Additional room creators, considered to have "infinite" power level, in room version 12
65    /// onwards.
66    #[serde(skip_serializing_if = "Vec::is_empty", default)]
67    pub additional_creators: Vec<OwnedUserId>,
68}
69
70impl RoomCreateEventContent {
71    /// Creates a new `RoomCreateEventContent` with the given creator, as required for room versions
72    /// 1 through 10.
73    pub fn new_v1(creator: OwnedUserId) -> Self {
74        #[allow(deprecated)]
75        Self {
76            creator: Some(creator),
77            federate: true,
78            room_version: default_room_version_id(),
79            predecessor: None,
80            room_type: None,
81            additional_creators: Vec::new(),
82        }
83    }
84
85    /// Creates a new `RoomCreateEventContent` with the default values and no creator, as introduced
86    /// in room version 11.
87    ///
88    /// The room version is set to [`RoomVersionId::V11`].
89    pub fn new_v11() -> Self {
90        #[allow(deprecated)]
91        Self {
92            creator: None,
93            federate: true,
94            room_version: RoomVersionId::V11,
95            predecessor: None,
96            room_type: None,
97            additional_creators: Vec::new(),
98        }
99    }
100}
101
102impl RedactContent for RoomCreateEventContent {
103    type Redacted = RedactedRoomCreateEventContent;
104
105    fn redact(self, rules: &RedactionRules) -> Self::Redacted {
106        #[allow(deprecated)]
107        if rules.keep_room_create_content {
108            self
109        } else {
110            Self {
111                room_version: default_room_version_id(),
112                creator: self.creator,
113                ..Self::new_v11()
114            }
115        }
116    }
117}
118
119/// A reference to an old room replaced during a room version upgrade.
120#[derive(Clone, Debug, Deserialize, Serialize)]
121#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
122pub struct PreviousRoom {
123    /// The ID of the old room.
124    pub room_id: OwnedRoomId,
125
126    /// The event ID of the last known event in the old room.
127    #[deprecated = "\
128        This field should be sent by servers when possible for backwards compatibility \
129        but clients should not rely on it."]
130    #[serde(skip_serializing_if = "Option::is_none")]
131    pub event_id: Option<OwnedEventId>,
132}
133
134impl PreviousRoom {
135    /// Creates a new `PreviousRoom` from the given room ID.
136    pub fn new(room_id: OwnedRoomId) -> Self {
137        #[allow(deprecated)]
138        Self { room_id, event_id: None }
139    }
140}
141
142/// Used to default the `room_version` field to room version 1.
143fn default_room_version_id() -> RoomVersionId {
144    RoomVersionId::V1
145}
146
147/// Redacted form of [`RoomCreateEventContent`].
148///
149/// The redaction rules of this event changed with room version 11:
150///
151/// - In room versions 1 through 10, the `creator` field was preserved during redaction, starting
152///   from room version 11 the field is removed.
153/// - In room versions 1 through 10, all the other fields were redacted, starting from room version
154///   11 all the fields are preserved.
155pub type RedactedRoomCreateEventContent = RoomCreateEventContent;
156
157impl RedactedStateEventContent for RedactedRoomCreateEventContent {
158    type StateKey = EmptyStateKey;
159
160    fn event_type(&self) -> StateEventType {
161        StateEventType::RoomCreate
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use assert_matches2::assert_matches;
168    use ruma_common::{owned_user_id, RoomVersionId};
169    use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
170
171    use super::{RoomCreateEventContent, RoomType};
172
173    #[test]
174    fn serialization() {
175        #[allow(deprecated)]
176        let content = RoomCreateEventContent {
177            creator: Some(owned_user_id!("@carl:example.com")),
178            federate: false,
179            room_version: RoomVersionId::V4,
180            predecessor: None,
181            room_type: None,
182            additional_creators: Vec::new(),
183        };
184
185        let json = json!({
186            "creator": "@carl:example.com",
187            "m.federate": false,
188            "room_version": "4"
189        });
190
191        assert_eq!(to_json_value(&content).unwrap(), json);
192    }
193
194    #[test]
195    fn space_serialization() {
196        #[allow(deprecated)]
197        let content = RoomCreateEventContent {
198            creator: Some(owned_user_id!("@carl:example.com")),
199            federate: false,
200            room_version: RoomVersionId::V4,
201            predecessor: None,
202            room_type: Some(RoomType::Space),
203            additional_creators: Vec::new(),
204        };
205
206        let json = json!({
207            "creator": "@carl:example.com",
208            "m.federate": false,
209            "room_version": "4",
210            "type": "m.space"
211        });
212
213        assert_eq!(to_json_value(&content).unwrap(), json);
214    }
215
216    #[test]
217    #[allow(deprecated)]
218    fn deserialization() {
219        let json = json!({
220            "creator": "@carl:example.com",
221            "m.federate": true,
222            "room_version": "4"
223        });
224
225        let content = from_json_value::<RoomCreateEventContent>(json).unwrap();
226        assert_eq!(content.creator.unwrap(), "@carl:example.com");
227        assert!(content.federate);
228        assert_eq!(content.room_version, RoomVersionId::V4);
229        assert_matches!(content.predecessor, None);
230        assert_eq!(content.room_type, None);
231    }
232
233    #[test]
234    #[allow(deprecated)]
235    fn space_deserialization() {
236        let json = json!({
237            "creator": "@carl:example.com",
238            "m.federate": true,
239            "room_version": "4",
240            "type": "m.space"
241        });
242
243        let content = from_json_value::<RoomCreateEventContent>(json).unwrap();
244        assert_eq!(content.creator.unwrap(), "@carl:example.com");
245        assert!(content.federate);
246        assert_eq!(content.room_version, RoomVersionId::V4);
247        assert_matches!(content.predecessor, None);
248        assert_eq!(content.room_type, Some(RoomType::Space));
249    }
250
251    #[test]
252    fn deserialize_valid_predecessor() {
253        let json = json!({
254            "m.federate": true,
255            "room_version": "11",
256            "predecessor": {
257                "room_id": "!room:localhost",
258                "event_id": "$eokpnkpn",
259            },
260        });
261
262        let content = from_json_value::<RoomCreateEventContent>(json).unwrap();
263        assert!(content.federate);
264        assert_eq!(content.room_version, RoomVersionId::V11);
265        assert_matches!(content.predecessor, Some(_));
266        assert_eq!(content.room_type, None);
267
268        let content = serde_json::from_str::<RoomCreateEventContent>(
269            r#"{"m.federate":true,"room_version":"11","predecessor":{"room_id":"!room:localhost","event_id":"$eokpnkpn"}}"#,
270        )
271        .unwrap();
272        assert!(content.federate);
273        assert_eq!(content.room_version, RoomVersionId::V11);
274        assert_matches!(content.predecessor, Some(_));
275        assert_eq!(content.room_type, None);
276    }
277
278    #[test]
279    #[cfg(feature = "compat-lax-room-create-deser")]
280    fn deserialize_invalid_predecessor() {
281        let json = json!({
282            "m.federate": true,
283            "room_version": "11",
284            "predecessor": "!room:localhost",
285        });
286
287        let content = from_json_value::<RoomCreateEventContent>(json).unwrap();
288        assert!(content.federate);
289        assert_eq!(content.room_version, RoomVersionId::V11);
290        assert_matches!(content.predecessor, None);
291        assert_eq!(content.room_type, None);
292
293        let content = serde_json::from_str::<RoomCreateEventContent>(
294            r#"{"m.federate":true,"room_version":"11","predecessor":"!room:localhost"}"#,
295        )
296        .unwrap();
297        assert!(content.federate);
298        assert_eq!(content.room_version, RoomVersionId::V11);
299        assert_matches!(content.predecessor, None);
300        assert_eq!(content.room_type, None);
301    }
302}