Skip to main content

ruma_client_api/room/
create_room.rs

1//! `POST /_matrix/client/*/createRoom`
2//!
3//! Create a new room.
4
5use std::collections::BTreeMap;
6
7use js_int::Int;
8use ruma_common::{
9    OwnedUserId,
10    power_levels::NotificationPowerLevels,
11    serde::{JsonCastable, JsonObject},
12};
13use ruma_events::{TimelineEventType, room::power_levels::RoomPowerLevelsEventContent};
14use serde::Serialize;
15
16pub mod v3 {
17    //! `/v3/` ([spec])
18    //!
19    //! [spec]: https://spec.matrix.org/v1.18/client-server-api/#post_matrixclientv3createroom
20
21    use assign::assign;
22    use ruma_common::{
23        OwnedRoomId, OwnedUserId, RoomVersionId,
24        api::{auth_scheme::AccessToken, request, response},
25        metadata,
26        room::RoomType,
27        serde::{Raw, StringEnum},
28    };
29    use ruma_events::{
30        AnyInitialStateEvent,
31        room::create::{PreviousRoom, RoomCreateEventContent},
32    };
33    use serde::{Deserialize, Serialize};
34
35    use super::RoomPowerLevelsContentOverride;
36    use crate::{PrivOwnedStr, membership::Invite3pid, room::Visibility};
37
38    metadata! {
39        method: POST,
40        rate_limited: false,
41        authentication: AccessToken,
42        history: {
43            1.0 => "/_matrix/client/r0/createRoom",
44            1.1 => "/_matrix/client/v3/createRoom",
45        }
46    }
47
48    /// Request type for the `create_room` endpoint.
49    #[request]
50    #[derive(Default)]
51    pub struct Request {
52        /// Extra keys to be added to the content of the `m.room.create`.
53        #[serde(default, skip_serializing_if = "Option::is_none")]
54        pub creation_content: Option<Raw<CreationContent>>,
55
56        /// List of state events to send to the new room.
57        ///
58        /// Takes precedence over events set by preset, but gets overridden by name and topic keys.
59        #[serde(default, skip_serializing_if = "<[_]>::is_empty")]
60        pub initial_state: Vec<Raw<AnyInitialStateEvent>>,
61
62        /// A list of user IDs to invite to the room.
63        ///
64        /// This will tell the server to invite everyone in the list to the newly created room.
65        #[serde(default, skip_serializing_if = "<[_]>::is_empty")]
66        pub invite: Vec<OwnedUserId>,
67
68        /// List of third party IDs of users to invite.
69        #[serde(default, skip_serializing_if = "<[_]>::is_empty")]
70        pub invite_3pid: Vec<Invite3pid>,
71
72        /// If set, this sets the `is_direct` flag on room invites.
73        #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
74        pub is_direct: bool,
75
76        /// If this is included, an `m.room.name` event will be sent into the room to indicate the
77        /// name of the room.
78        #[serde(skip_serializing_if = "Option::is_none")]
79        pub name: Option<String>,
80
81        /// Power level content to override in the default power level event.
82        #[serde(skip_serializing_if = "Option::is_none")]
83        pub power_level_content_override: Option<Raw<RoomPowerLevelsContentOverride>>,
84
85        /// Convenience parameter for setting various default state events based on a preset.
86        #[serde(skip_serializing_if = "Option::is_none")]
87        pub preset: Option<RoomPreset>,
88
89        /// The desired room alias local part.
90        #[serde(skip_serializing_if = "Option::is_none")]
91        pub room_alias_name: Option<String>,
92
93        /// Room version to set for the room.
94        ///
95        /// Defaults to homeserver's default if not specified.
96        #[serde(skip_serializing_if = "Option::is_none")]
97        pub room_version: Option<RoomVersionId>,
98
99        /// If this is included, an `m.room.topic` event will be sent into the room to indicate
100        /// the topic for the room.
101        #[serde(skip_serializing_if = "Option::is_none")]
102        pub topic: Option<String>,
103
104        /// A public visibility indicates that the room will be shown in the published room list.
105        ///
106        /// A private visibility will hide the room from the published room list. Defaults to
107        /// `Private`.
108        #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
109        pub visibility: Visibility,
110    }
111
112    /// Response type for the `create_room` endpoint.
113    #[response]
114    pub struct Response {
115        /// The created room's ID.
116        pub room_id: OwnedRoomId,
117    }
118
119    impl Request {
120        /// Creates a new `Request` will all-default parameters.
121        pub fn new() -> Self {
122            Default::default()
123        }
124    }
125
126    impl Response {
127        /// Creates a new `Response` with the given room id.
128        pub fn new(room_id: OwnedRoomId) -> Self {
129            Self { room_id }
130        }
131    }
132
133    /// Extra options to be added to the `m.room.create` event.
134    ///
135    /// This is the same as the event content struct for `m.room.create`, but without some fields
136    /// that servers are supposed to ignore.
137    #[derive(Clone, Debug, Deserialize, Serialize)]
138    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
139    pub struct CreationContent {
140        /// A list of user IDs to consider as additional creators, and hence grant an "infinite"
141        /// immutable power level, from room version 12 onwards.
142        #[serde(default, skip_serializing_if = "<[_]>::is_empty")]
143        pub additional_creators: Vec<OwnedUserId>,
144
145        /// Whether users on other servers can join this room.
146        ///
147        /// Defaults to `true` if key does not exist.
148        #[serde(
149            rename = "m.federate",
150            default = "ruma_common::serde::default_true",
151            skip_serializing_if = "ruma_common::serde::is_true"
152        )]
153        pub federate: bool,
154
155        /// A reference to the room this room replaces, if the previous room was upgraded.
156        #[serde(skip_serializing_if = "Option::is_none")]
157        pub predecessor: Option<PreviousRoom>,
158
159        /// The room type.
160        #[serde(skip_serializing_if = "Option::is_none", rename = "type")]
161        pub room_type: Option<RoomType>,
162    }
163
164    impl CreationContent {
165        /// Creates a new `CreationContent` with all fields defaulted.
166        pub fn new() -> Self {
167            Self {
168                additional_creators: Vec::new(),
169                federate: true,
170                predecessor: None,
171                room_type: None,
172            }
173        }
174
175        /// Given a `CreationContent` and the other fields that a homeserver has to fill, construct
176        /// a `RoomCreateEventContent`.
177        pub fn into_event_content(
178            self,
179            creator: OwnedUserId,
180            room_version: RoomVersionId,
181        ) -> RoomCreateEventContent {
182            assign!(RoomCreateEventContent::new_v1(creator), {
183                federate: self.federate,
184                room_version: room_version,
185                predecessor: self.predecessor,
186                room_type: self.room_type
187            })
188        }
189
190        /// Returns whether all fields have their default value.
191        pub fn is_empty(&self) -> bool {
192            self.federate && self.predecessor.is_none() && self.room_type.is_none()
193        }
194    }
195
196    impl Default for CreationContent {
197        fn default() -> Self {
198            Self::new()
199        }
200    }
201
202    /// A convenience parameter for setting a few default state events.
203    #[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
204    #[derive(Clone, StringEnum)]
205    #[ruma_enum(rename_all = "snake_case")]
206    #[non_exhaustive]
207    pub enum RoomPreset {
208        /// `join_rules` is set to `invite` and `history_visibility` is set to `shared`.
209        PrivateChat,
210
211        /// `join_rules` is set to `public` and `history_visibility` is set to `shared`.
212        PublicChat,
213
214        /// Same as `PrivateChat`, but all initial invitees get the same power level as the
215        /// creator.
216        TrustedPrivateChat,
217
218        #[doc(hidden)]
219        _Custom(PrivOwnedStr),
220    }
221}
222
223/// The power level values that can be overridden when creating a room.
224///
225/// This has the same fields as [`RoomPowerLevelsEventContent`], but most of them are `Option`s.
226/// Contrary to [`RoomPowerLevelsEventContent`] which doesn't serialize fields that are set to their
227/// default value defined in the Matrix specification, this type serializes all fields that are
228/// `Some(_)`, regardless of their value.
229///
230/// This type is used to allow clients to avoid server behavior observed in the wild that sets
231/// custom default values for fields that are not set in the `create_room` request, while a client
232/// wants the server to use the default value defined in the Matrix specification for that field.
233#[derive(Clone, Debug, Serialize, Default)]
234#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
235pub struct RoomPowerLevelsContentOverride {
236    /// The level required to ban a user.
237    #[serde(skip_serializing_if = "Option::is_none")]
238    pub ban: Option<Int>,
239
240    /// The level required to send specific event types.
241    ///
242    /// This is a mapping from event type to power level required.
243    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
244    pub events: BTreeMap<TimelineEventType, Int>,
245
246    /// The default level required to send message events.
247    #[serde(skip_serializing_if = "Option::is_none")]
248    pub events_default: Option<Int>,
249
250    /// The level required to invite a user.
251    #[serde(skip_serializing_if = "Option::is_none")]
252    pub invite: Option<Int>,
253
254    /// The level required to kick a user.
255    #[serde(skip_serializing_if = "Option::is_none")]
256    pub kick: Option<Int>,
257
258    /// The level required to redact an event.
259    #[serde(skip_serializing_if = "Option::is_none")]
260    pub redact: Option<Int>,
261
262    /// The default level required to send state events.
263    #[serde(skip_serializing_if = "Option::is_none")]
264    pub state_default: Option<Int>,
265
266    /// The power levels for specific users.
267    ///
268    /// This is a mapping from `user_id` to power level for that user.
269    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
270    pub users: BTreeMap<OwnedUserId, Int>,
271
272    /// The default power level for every user in the room.
273    #[serde(skip_serializing_if = "Option::is_none")]
274    pub users_default: Option<Int>,
275
276    /// The power level requirements for specific notification types.
277    ///
278    /// This is a mapping from `key` to power level for that notifications key.
279    #[serde(default, skip_serializing_if = "NotificationPowerLevels::is_default")]
280    pub notifications: NotificationPowerLevels,
281}
282
283impl RoomPowerLevelsContentOverride {
284    /// Creates a new, empty [`RoomPowerLevelsContentOverride`] instance.
285    pub fn new() -> Self {
286        Self::default()
287    }
288}
289
290impl From<RoomPowerLevelsEventContent> for RoomPowerLevelsContentOverride {
291    fn from(value: RoomPowerLevelsEventContent) -> Self {
292        let RoomPowerLevelsEventContent {
293            ban,
294            events,
295            events_default,
296            invite,
297            kick,
298            redact,
299            state_default,
300            users,
301            users_default,
302            notifications,
303            ..
304        } = value;
305
306        Self {
307            ban: Some(ban),
308            events,
309            events_default: Some(events_default),
310            invite: Some(invite),
311            kick: Some(kick),
312            redact: Some(redact),
313            state_default: Some(state_default),
314            users,
315            users_default: Some(users_default),
316            notifications,
317        }
318    }
319}
320
321impl JsonCastable<RoomPowerLevelsEventContent> for RoomPowerLevelsContentOverride {}
322
323impl JsonCastable<RoomPowerLevelsContentOverride> for RoomPowerLevelsEventContent {}
324
325impl JsonCastable<JsonObject> for RoomPowerLevelsContentOverride {}
326
327#[cfg(test)]
328mod tests {
329    use std::collections::BTreeMap;
330
331    use assign::assign;
332    use js_int::int;
333    use maplit::btreemap;
334    use ruma_common::{
335        canonical_json::assert_to_canonical_json_eq, owned_user_id,
336        power_levels::NotificationPowerLevels,
337    };
338    use serde_json::json;
339
340    use super::RoomPowerLevelsContentOverride;
341
342    #[test]
343    fn serialization_of_power_levels_overridden_values_with_optional_fields_as_none() {
344        let power_levels = RoomPowerLevelsContentOverride {
345            ban: None,
346            events: BTreeMap::new(),
347            events_default: None,
348            invite: None,
349            kick: None,
350            redact: None,
351            state_default: None,
352            users: BTreeMap::new(),
353            users_default: None,
354            notifications: NotificationPowerLevels::default(),
355        };
356
357        assert_to_canonical_json_eq!(power_levels, json!({}));
358    }
359
360    #[test]
361    fn serialization_of_power_levels_overridden_values_with_all_fields() {
362        let user = owned_user_id!("@carl:example.com");
363        let power_levels_event = RoomPowerLevelsContentOverride {
364            ban: Some(int!(23)),
365            events: btreemap! {
366                "m.dummy".into() => int!(23)
367            },
368            events_default: Some(int!(23)),
369            invite: Some(int!(23)),
370            kick: Some(int!(23)),
371            redact: Some(int!(23)),
372            state_default: Some(int!(23)),
373            users: btreemap! {
374                user => int!(23)
375            },
376            users_default: Some(int!(23)),
377            notifications: assign!(NotificationPowerLevels::new(), { room: int!(23) }),
378        };
379
380        assert_to_canonical_json_eq!(
381            power_levels_event,
382            json!({
383                "ban": 23,
384                "events": {
385                    "m.dummy": 23
386                },
387                "events_default": 23,
388                "invite": 23,
389                "kick": 23,
390                "redact": 23,
391                "state_default": 23,
392                "users": {
393                    "@carl:example.com": 23
394                },
395                "users_default": 23,
396                "notifications": {
397                    "room": 23
398                },
399            })
400        );
401    }
402}