ruma_events/room/
join_rules.rs1pub 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#[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 #[ruma_event(skip_redaction)]
28 pub join_rule: JoinRule,
29}
30
31impl RoomJoinRulesEventContent {
32 pub fn new(join_rule: JoinRule) -> Self {
34 Self { join_rule }
35 }
36
37 pub fn restricted(allow: Vec<AllowRule>) -> Self {
40 Self { join_rule: JoinRule::Restricted(Restricted::new(allow)) }
41 }
42
43 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#[derive(Clone, Debug, Serialize)]
72#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
73pub struct RedactedRoomJoinRulesEventContent {
74 #[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 RoomJoinRulesEvent {
105 pub fn join_rule(&self) -> &JoinRule {
107 match self {
108 Self::Original(ev) => &ev.content.join_rule,
109 Self::Redacted(ev) => &ev.content.join_rule,
110 }
111 }
112}
113
114impl SyncRoomJoinRulesEvent {
115 pub fn join_rule(&self) -> &JoinRule {
117 match self {
118 Self::Original(ev) => &ev.content.join_rule,
119 Self::Redacted(ev) => &ev.content.join_rule,
120 }
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use assert_matches2::assert_matches;
127 use ruma_common::owned_room_id;
128 use serde_json::json;
129
130 use super::{
131 AllowRule, JoinRule, OriginalSyncRoomJoinRulesEvent, RedactedRoomJoinRulesEventContent,
132 RoomJoinRulesEventContent,
133 };
134 use crate::room::join_rules::RedactedSyncRoomJoinRulesEvent;
135
136 #[test]
137 fn deserialize_content() {
138 let json = r#"{"join_rule": "public"}"#;
139
140 let event: RoomJoinRulesEventContent = serde_json::from_str(json).unwrap();
141 assert_matches!(event, RoomJoinRulesEventContent { join_rule: JoinRule::Public });
142
143 let event: RedactedRoomJoinRulesEventContent = serde_json::from_str(json).unwrap();
144 assert_matches!(event, RedactedRoomJoinRulesEventContent { join_rule: JoinRule::Public });
145 }
146
147 #[test]
148 fn deserialize_restricted() {
149 let json = r#"{
150 "join_rule": "restricted",
151 "allow": [
152 {
153 "type": "m.room_membership",
154 "room_id": "!mods:example.org"
155 },
156 {
157 "type": "m.room_membership",
158 "room_id": "!users:example.org"
159 }
160 ]
161 }"#;
162
163 let event: RoomJoinRulesEventContent = serde_json::from_str(json).unwrap();
164 assert_matches!(event.join_rule, JoinRule::Restricted(restricted));
165 assert_eq!(
166 restricted.allow,
167 &[
168 AllowRule::room_membership(owned_room_id!("!mods:example.org")),
169 AllowRule::room_membership(owned_room_id!("!users:example.org"))
170 ]
171 );
172
173 let event: RedactedRoomJoinRulesEventContent = serde_json::from_str(json).unwrap();
174 assert_matches!(event.join_rule, JoinRule::Restricted(restricted));
175 assert_eq!(
176 restricted.allow,
177 &[
178 AllowRule::room_membership(owned_room_id!("!mods:example.org")),
179 AllowRule::room_membership(owned_room_id!("!users:example.org"))
180 ]
181 );
182 }
183
184 #[test]
185 fn deserialize_restricted_event() {
186 let json = r#"{
187 "type": "m.room.join_rules",
188 "sender": "@admin:community.rs",
189 "content": {
190 "join_rule": "restricted",
191 "allow": [
192 { "type": "m.room_membership","room_id": "!KqeUnzmXPIhHRaWMTs:mccarty.io" }
193 ]
194 },
195 "state_key": "",
196 "origin_server_ts":1630508835342,
197 "unsigned": {
198 "age":4165521871
199 },
200 "event_id": "$0ACb9KSPlT3al3kikyRYvFhMqXPP9ZcQOBrsdIuh58U"
201 }"#;
202
203 assert_matches!(serde_json::from_str::<OriginalSyncRoomJoinRulesEvent>(json), Ok(_));
204 }
205
206 #[test]
207 fn deserialize_redacted_restricted_event() {
208 let json = r#"{
209 "type": "m.room.join_rules",
210 "sender": "@admin:community.rs",
211 "content": {
212 "join_rule": "restricted",
213 "allow": [
214 { "type": "m.room_membership","room_id": "!KqeUnzmXPIhHRaWMTs:mccarty.io" }
215 ]
216 },
217 "state_key": "",
218 "origin_server_ts":1630508835342,
219 "unsigned": {
220 "age":4165521871,
221 "redacted_because": {
222 "type": "m.room.redaction",
223 "content": {
224 "redacts": "$0ACb9KSPlT3al3kikyRYvFhMqXPP9ZcQOBrsdIuh58U"
225 },
226 "event_id": "$h29iv0s8",
227 "origin_server_ts": 1,
228 "sender": "@carl:example.com"
229 }
230 },
231 "event_id": "$0ACb9KSPlT3al3kikyRYvFhMqXPP9ZcQOBrsdIuh58U"
232 }"#;
233
234 assert_matches!(serde_json::from_str::<RedactedSyncRoomJoinRulesEvent>(json), Ok(_));
235 }
236
237 #[test]
238 fn restricted_room_no_allow_field() {
239 let json = r#"{"join_rule":"restricted"}"#;
240 let join_rules: RoomJoinRulesEventContent = serde_json::from_str(json).unwrap();
241 assert_matches!(
242 join_rules,
243 RoomJoinRulesEventContent { join_rule: JoinRule::Restricted(_) }
244 );
245 }
246
247 #[test]
248 fn reserialize_unsupported_join_rule() {
249 let json = json!({"join_rule": "local.matrix.custom", "foo": "bar"});
250
251 let content = serde_json::from_value::<RoomJoinRulesEventContent>(json.clone()).unwrap();
252 assert_eq!(content.join_rule.as_str(), "local.matrix.custom");
253 let data = content.join_rule.data();
254 assert_eq!(data.get("foo").unwrap().as_str(), Some("bar"));
255
256 assert_eq!(serde_json::to_value(&content).unwrap(), json);
257 }
258}