1use ruma_common::{
6 OwnedEventId, OwnedRoomId, OwnedUserId, RoomVersionId, room::RoomType,
7 room_version_rules::RedactionRules,
8};
9use ruma_macros::EventContent;
10use serde::{Deserialize, Serialize};
11
12use crate::{EmptyStateKey, RedactContent, RedactedStateEventContent, StateEventType};
13
14#[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 #[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 #[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 #[serde(default = "default_room_version_id")]
45 pub room_version: RoomVersionId,
46
47 #[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 #[serde(skip_serializing_if = "Option::is_none", rename = "type")]
62 pub room_type: Option<RoomType>,
63
64 #[serde(skip_serializing_if = "Vec::is_empty", default)]
67 pub additional_creators: Vec<OwnedUserId>,
68}
69
70impl RoomCreateEventContent {
71 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 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#[derive(Clone, Debug, Deserialize, Serialize)]
121#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
122pub struct PreviousRoom {
123 pub room_id: OwnedRoomId,
125
126 #[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 pub fn new(room_id: OwnedRoomId) -> Self {
137 #[allow(deprecated)]
138 Self { room_id, event_id: None }
139 }
140}
141
142fn default_room_version_id() -> RoomVersionId {
144 RoomVersionId::V1
145}
146
147pub 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::{RoomVersionId, canonical_json::assert_to_canonical_json_eq, owned_user_id};
169 use serde_json::{from_value as from_json_value, json};
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 assert_to_canonical_json_eq!(
186 content,
187 json!({
188 "creator": "@carl:example.com",
189 "m.federate": false,
190 "room_version": "4",
191 }),
192 );
193 }
194
195 #[test]
196 fn space_serialization() {
197 #[allow(deprecated)]
198 let content = RoomCreateEventContent {
199 creator: Some(owned_user_id!("@carl:example.com")),
200 federate: false,
201 room_version: RoomVersionId::V4,
202 predecessor: None,
203 room_type: Some(RoomType::Space),
204 additional_creators: Vec::new(),
205 };
206
207 assert_to_canonical_json_eq!(
208 content,
209 json!({
210 "creator": "@carl:example.com",
211 "m.federate": false,
212 "room_version": "4",
213 "type": "m.space",
214 }),
215 );
216 }
217
218 #[test]
219 #[allow(deprecated)]
220 fn deserialization() {
221 let json = json!({
222 "creator": "@carl:example.com",
223 "m.federate": true,
224 "room_version": "4"
225 });
226
227 let content = from_json_value::<RoomCreateEventContent>(json).unwrap();
228 assert_eq!(content.creator.unwrap(), "@carl:example.com");
229 assert!(content.federate);
230 assert_eq!(content.room_version, RoomVersionId::V4);
231 assert_matches!(content.predecessor, None);
232 assert_eq!(content.room_type, None);
233 }
234
235 #[test]
236 #[allow(deprecated)]
237 fn space_deserialization() {
238 let json = json!({
239 "creator": "@carl:example.com",
240 "m.federate": true,
241 "room_version": "4",
242 "type": "m.space"
243 });
244
245 let content = from_json_value::<RoomCreateEventContent>(json).unwrap();
246 assert_eq!(content.creator.unwrap(), "@carl:example.com");
247 assert!(content.federate);
248 assert_eq!(content.room_version, RoomVersionId::V4);
249 assert_matches!(content.predecessor, None);
250 assert_eq!(content.room_type, Some(RoomType::Space));
251 }
252
253 #[test]
254 fn deserialize_valid_predecessor() {
255 let json = json!({
256 "m.federate": true,
257 "room_version": "11",
258 "predecessor": {
259 "room_id": "!room:localhost",
260 "event_id": "$eokpnkpn",
261 },
262 });
263
264 let content = from_json_value::<RoomCreateEventContent>(json).unwrap();
265 assert!(content.federate);
266 assert_eq!(content.room_version, RoomVersionId::V11);
267 assert_matches!(content.predecessor, Some(_));
268 assert_eq!(content.room_type, None);
269
270 let content = serde_json::from_str::<RoomCreateEventContent>(
271 r#"{"m.federate":true,"room_version":"11","predecessor":{"room_id":"!room:localhost","event_id":"$eokpnkpn"}}"#,
272 )
273 .unwrap();
274 assert!(content.federate);
275 assert_eq!(content.room_version, RoomVersionId::V11);
276 assert_matches!(content.predecessor, Some(_));
277 assert_eq!(content.room_type, None);
278 }
279
280 #[test]
281 #[cfg(feature = "compat-lax-room-create-deser")]
282 fn deserialize_invalid_predecessor() {
283 let json = json!({
284 "m.federate": true,
285 "room_version": "11",
286 "predecessor": "!room:localhost",
287 });
288
289 let content = from_json_value::<RoomCreateEventContent>(json).unwrap();
290 assert!(content.federate);
291 assert_eq!(content.room_version, RoomVersionId::V11);
292 assert_matches!(content.predecessor, None);
293 assert_eq!(content.room_type, None);
294
295 let content = serde_json::from_str::<RoomCreateEventContent>(
296 r#"{"m.federate":true,"room_version":"11","predecessor":"!room:localhost"}"#,
297 )
298 .unwrap();
299 assert!(content.federate);
300 assert_eq!(content.room_version, RoomVersionId::V11);
301 assert_matches!(content.predecessor, None);
302 assert_eq!(content.room_type, None);
303 }
304}