Skip to main content

ruma_events/room/
join_rules.rs

1//! Types for the [`m.room.join_rules`] event.
2//!
3//! [`m.room.join_rules`]: https://spec.matrix.org/v1.19/client-server-api/#mroomjoin_rules
4
5pub use ruma_common::room::{AllowRule, JoinRule, Restricted};
6use ruma_common::{
7    room_version_rules::RedactionRules,
8    serde::{JsonCastable, JsonObject},
9};
10use ruma_macros::EventContent;
11use serde::{Deserialize, Serialize, de};
12
13use crate::{
14    EmptyStateKey, RedactContent, RedactedStateEventContent, StateEventContent, StateEventType,
15    StaticEventContent,
16};
17
18/// The content of an `m.room.join_rules` event.
19///
20/// Describes how users are allowed to join the room.
21#[derive(Clone, Debug, Serialize, EventContent)]
22#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
23#[ruma_event(type = "m.room.join_rules", kind = State, state_key_type = EmptyStateKey, custom_redacted)]
24#[serde(transparent)]
25pub struct RoomJoinRulesEventContent {
26    /// The rule used for users wishing to join this room.
27    #[ruma_event(skip_redaction)]
28    pub join_rule: JoinRule,
29}
30
31impl RoomJoinRulesEventContent {
32    /// Creates a new `RoomJoinRulesEventContent` with the given rule.
33    pub fn new(join_rule: JoinRule) -> Self {
34        Self { join_rule }
35    }
36
37    /// Creates a new `RoomJoinRulesEventContent` with the restricted rule and the given set of
38    /// allow rules.
39    pub fn restricted(allow: Vec<AllowRule>) -> Self {
40        Self { join_rule: JoinRule::Restricted(Restricted::new(allow)) }
41    }
42
43    /// Creates a new `RoomJoinRulesEventContent` with the knock restricted rule and the given set
44    /// of allow rules.
45    pub fn knock_restricted(allow: Vec<AllowRule>) -> Self {
46        Self { join_rule: JoinRule::KnockRestricted(Restricted::new(allow)) }
47    }
48}
49
50impl RedactContent for RoomJoinRulesEventContent {
51    type Redacted = RedactedRoomJoinRulesEventContent;
52
53    fn redact(self, _rules: &RedactionRules) -> Self::Redacted {
54        RedactedRoomJoinRulesEventContent { join_rule: self.join_rule }
55    }
56}
57
58impl<'de> Deserialize<'de> for RoomJoinRulesEventContent {
59    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
60    where
61        D: de::Deserializer<'de>,
62    {
63        let join_rule = JoinRule::deserialize(deserializer)?;
64        Ok(RoomJoinRulesEventContent { join_rule })
65    }
66}
67
68impl JsonCastable<RedactedRoomJoinRulesEventContent> for RoomJoinRulesEventContent {}
69
70/// The redacted form of [`RoomJoinRulesEventContent`].
71#[derive(Clone, Debug, Serialize)]
72#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
73pub struct RedactedRoomJoinRulesEventContent {
74    /// The type of rules used for users wishing to join this room.
75    #[serde(flatten)]
76    pub join_rule: JoinRule,
77}
78
79impl StaticEventContent for RedactedRoomJoinRulesEventContent {
80    const TYPE: &'static str = RoomJoinRulesEventContent::TYPE;
81    type IsPrefix = <RoomJoinRulesEventContent as StaticEventContent>::IsPrefix;
82}
83
84impl RedactedStateEventContent for RedactedRoomJoinRulesEventContent {
85    type StateKey = <RoomJoinRulesEventContent as StateEventContent>::StateKey;
86
87    fn event_type(&self) -> StateEventType {
88        StateEventType::RoomJoinRules
89    }
90}
91
92impl<'de> Deserialize<'de> for RedactedRoomJoinRulesEventContent {
93    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
94    where
95        D: de::Deserializer<'de>,
96    {
97        let join_rule = JoinRule::deserialize(deserializer)?;
98        Ok(Self { join_rule })
99    }
100}
101
102impl JsonCastable<JsonObject> for RedactedRoomJoinRulesEventContent {}
103
104impl From<RedactedRoomJoinRulesEventContent> for PossiblyRedactedRoomJoinRulesEventContent {
105    fn from(value: RedactedRoomJoinRulesEventContent) -> Self {
106        let RedactedRoomJoinRulesEventContent { join_rule } = value;
107        Self { join_rule }
108    }
109}
110
111impl RoomJoinRulesEvent {
112    /// Obtain the join rule, regardless of whether this event is redacted.
113    pub fn join_rule(&self) -> &JoinRule {
114        match self {
115            Self::Original(ev) => &ev.content.join_rule,
116            Self::Redacted(ev) => &ev.content.join_rule,
117        }
118    }
119}
120
121impl SyncRoomJoinRulesEvent {
122    /// Obtain the join rule, regardless of whether this event is redacted.
123    pub fn join_rule(&self) -> &JoinRule {
124        match self {
125            Self::Original(ev) => &ev.content.join_rule,
126            Self::Redacted(ev) => &ev.content.join_rule,
127        }
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use assert_matches::assert_matches;
134    use ruma_common::owned_room_id;
135    use serde_json::json;
136    use strass::assert_let;
137
138    use super::{
139        AllowRule, JoinRule, OriginalSyncRoomJoinRulesEvent, RedactedRoomJoinRulesEventContent,
140        RoomJoinRulesEventContent,
141    };
142    use crate::room::join_rules::RedactedSyncRoomJoinRulesEvent;
143
144    #[test]
145    fn deserialize_content() {
146        let json = r#"{"join_rule": "public"}"#;
147
148        let event: RoomJoinRulesEventContent = serde_json::from_str(json).unwrap();
149        assert_matches!(event, RoomJoinRulesEventContent { join_rule: JoinRule::Public });
150
151        let event: RedactedRoomJoinRulesEventContent = serde_json::from_str(json).unwrap();
152        assert_matches!(event, RedactedRoomJoinRulesEventContent { join_rule: JoinRule::Public });
153    }
154
155    #[test]
156    fn deserialize_restricted() {
157        let json = r#"{
158            "join_rule": "restricted",
159            "allow": [
160                {
161                    "type": "m.room_membership",
162                    "room_id": "!mods:example.org"
163                },
164                {
165                    "type": "m.room_membership",
166                    "room_id": "!users:example.org"
167                }
168            ]
169        }"#;
170
171        let event: RoomJoinRulesEventContent = serde_json::from_str(json).unwrap();
172        assert_let!(JoinRule::Restricted(restricted) = event.join_rule);
173        assert_eq!(
174            restricted.allow,
175            &[
176                AllowRule::room_membership(owned_room_id!("!mods:example.org")),
177                AllowRule::room_membership(owned_room_id!("!users:example.org"))
178            ]
179        );
180
181        let event: RedactedRoomJoinRulesEventContent = serde_json::from_str(json).unwrap();
182        assert_let!(JoinRule::Restricted(restricted) = event.join_rule);
183        assert_eq!(
184            restricted.allow,
185            &[
186                AllowRule::room_membership(owned_room_id!("!mods:example.org")),
187                AllowRule::room_membership(owned_room_id!("!users:example.org"))
188            ]
189        );
190    }
191
192    #[test]
193    fn deserialize_restricted_event() {
194        let json = r#"{
195            "type": "m.room.join_rules",
196            "sender": "@admin:community.rs",
197            "content": {
198                "join_rule": "restricted",
199                "allow": [
200                    { "type": "m.room_membership","room_id": "!KqeUnzmXPIhHRaWMTs:mccarty.io" }
201                ]
202            },
203            "state_key": "",
204            "origin_server_ts":1630508835342,
205            "unsigned": {
206                "age":4165521871
207            },
208            "event_id": "$0ACb9KSPlT3al3kikyRYvFhMqXPP9ZcQOBrsdIuh58U"
209        }"#;
210
211        assert_matches!(serde_json::from_str::<OriginalSyncRoomJoinRulesEvent>(json), Ok(_));
212    }
213
214    #[test]
215    fn deserialize_redacted_restricted_event() {
216        let json = r#"{
217            "type": "m.room.join_rules",
218            "sender": "@admin:community.rs",
219            "content": {
220                "join_rule": "restricted",
221                "allow": [
222                    { "type": "m.room_membership","room_id": "!KqeUnzmXPIhHRaWMTs:mccarty.io" }
223                ]
224            },
225            "state_key": "",
226            "origin_server_ts":1630508835342,
227            "unsigned": {
228                "age":4165521871,
229                "redacted_because": {
230                    "type": "m.room.redaction",
231                    "content": {
232                        "redacts": "$0ACb9KSPlT3al3kikyRYvFhMqXPP9ZcQOBrsdIuh58U"
233                    },
234                    "event_id": "$h29iv0s8",
235                    "origin_server_ts": 1,
236                    "sender": "@carl:example.com"
237                }
238            },
239            "event_id": "$0ACb9KSPlT3al3kikyRYvFhMqXPP9ZcQOBrsdIuh58U"
240        }"#;
241
242        assert_matches!(serde_json::from_str::<RedactedSyncRoomJoinRulesEvent>(json), Ok(_));
243    }
244
245    #[test]
246    fn restricted_room_no_allow_field() {
247        let json = r#"{"join_rule":"restricted"}"#;
248        let join_rules: RoomJoinRulesEventContent = serde_json::from_str(json).unwrap();
249        assert_matches!(
250            join_rules,
251            RoomJoinRulesEventContent { join_rule: JoinRule::Restricted(_) }
252        );
253    }
254
255    #[test]
256    fn reserialize_unsupported_join_rule() {
257        let json = json!({"join_rule": "local.matrix.custom", "foo": "bar"});
258
259        let content = serde_json::from_value::<RoomJoinRulesEventContent>(json.clone()).unwrap();
260        assert_eq!(content.join_rule.as_str(), "local.matrix.custom");
261        let data = content.join_rule.data();
262        assert_eq!(data.get("foo").unwrap().as_str(), Some("bar"));
263
264        assert_eq!(serde_json::to_value(&content).unwrap(), json);
265    }
266}