1use std::{
4 collections::BTreeMap,
5 ops::Deref,
6 sync::{Arc, Mutex, OnceLock},
7};
8
9use js_int::{int, Int};
10use ruma_common::{
11 room_version_rules::AuthorizationRules,
12 serde::{
13 btreemap_deserialize_v1_powerlevel_values, deserialize_v1_powerlevel, from_raw_json_value,
14 DebugAsRefStr, DisplayAsRefStr, JsonObject, OrdAsRefStr, PartialEqAsRefStr,
15 PartialOrdAsRefStr,
16 },
17 OwnedUserId, UserId,
18};
19use ruma_events::TimelineEventType;
20use serde::de::DeserializeOwned;
21use serde_json::{from_value as from_json_value, Error};
22
23use super::Event;
24
25const DEFAULT_CREATOR_POWER_LEVEL: i32 = 100;
27
28#[derive(Debug, Clone)]
33pub struct RoomPowerLevelsEvent<E: Event> {
34 inner: Arc<RoomPowerLevelsEventInner<E>>,
35}
36
37#[derive(Debug)]
38struct RoomPowerLevelsEventInner<E: Event> {
39 event: E,
41
42 deserialized_content: OnceLock<JsonObject>,
44
45 int_fields: Mutex<BTreeMap<RoomPowerLevelsIntField, Option<Int>>>,
47
48 users: OnceLock<Option<BTreeMap<OwnedUserId, Int>>>,
50}
51
52impl<E: Event> RoomPowerLevelsEvent<E> {
53 pub fn new(event: E) -> Self {
55 Self {
56 inner: RoomPowerLevelsEventInner {
57 event,
58 deserialized_content: OnceLock::new(),
59 int_fields: Mutex::new(BTreeMap::new()),
60 users: OnceLock::new(),
61 }
62 .into(),
63 }
64 }
65
66 fn deserialized_content(&self) -> Result<&JsonObject, String> {
68 if let Some(content) = self.inner.deserialized_content.get() {
70 Ok(content)
71 } else {
72 let content = from_raw_json_value(self.content()).map_err(|error: Error| {
73 format!("malformed `m.room.power_levels` content: {error}")
74 })?;
75 Ok(self.inner.deserialized_content.get_or_init(|| content))
76 }
77 }
78
79 pub fn get_as_int(
83 &self,
84 field: RoomPowerLevelsIntField,
85 rules: &AuthorizationRules,
86 ) -> Result<Option<Int>, String> {
87 let mut int_fields =
88 self.inner.int_fields.lock().expect("we never panic while holding the mutex");
89
90 if let Some(power_level) = int_fields.get(&field) {
91 return Ok(*power_level);
92 }
93
94 let content = self.deserialized_content()?;
95
96 let Some(value) = content.get(field.as_str()) else {
97 int_fields.insert(field, None);
98 return Ok(None);
99 };
100
101 let res = if rules.integer_power_levels {
102 from_json_value(value.clone())
103 } else {
104 deserialize_v1_powerlevel(value)
105 };
106
107 let power_level = res.map(Some).map_err(|error| {
108 format!(
109 "unexpected format of `{field}` field in `content` \
110 of `m.room.power_levels` event: {error}"
111 )
112 })?;
113
114 int_fields.insert(field, power_level);
115 Ok(power_level)
116 }
117
118 pub fn get_as_int_or_default(
121 &self,
122 field: RoomPowerLevelsIntField,
123 rules: &AuthorizationRules,
124 ) -> Result<Int, String> {
125 Ok(self.get_as_int(field, rules)?.unwrap_or_else(|| field.default_value()))
126 }
127
128 fn get_as_int_map<T: Ord + DeserializeOwned>(
130 &self,
131 field: &str,
132 rules: &AuthorizationRules,
133 ) -> Result<Option<BTreeMap<T, Int>>, String> {
134 let content = self.deserialized_content()?;
135
136 let Some(value) = content.get(field) else {
137 return Ok(None);
138 };
139
140 let res = if rules.integer_power_levels {
141 from_json_value(value.clone())
142 } else {
143 btreemap_deserialize_v1_powerlevel_values(value)
144 };
145
146 res.map(Some).map_err(|error| {
147 format!(
148 "unexpected format of `{field}` field in `content` \
149 of `m.room.power_levels` event: {error}"
150 )
151 })
152 }
153
154 pub fn events(
156 &self,
157 rules: &AuthorizationRules,
158 ) -> Result<Option<BTreeMap<TimelineEventType, Int>>, String> {
159 self.get_as_int_map("events", rules)
160 }
161
162 pub fn notifications(
164 &self,
165 rules: &AuthorizationRules,
166 ) -> Result<Option<BTreeMap<String, Int>>, String> {
167 self.get_as_int_map("notifications", rules)
168 }
169
170 pub fn users(
174 &self,
175 rules: &AuthorizationRules,
176 ) -> Result<Option<&BTreeMap<OwnedUserId, Int>>, String> {
177 if let Some(users) = self.inner.users.get() {
179 Ok(users.as_ref())
180 } else {
181 let users = self.get_as_int_map("users", rules)?;
182 Ok(self.inner.users.get_or_init(|| users).as_ref())
183 }
184 }
185
186 pub fn user_power_level(
191 &self,
192 user_id: &UserId,
193 rules: &AuthorizationRules,
194 ) -> Result<Int, String> {
195 if let Some(power_level) = self.users(rules)?.as_ref().and_then(|users| users.get(user_id))
196 {
197 return Ok(*power_level);
198 }
199
200 self.get_as_int_or_default(RoomPowerLevelsIntField::UsersDefault, rules)
201 }
202
203 pub fn event_power_level(
205 &self,
206 event_type: &TimelineEventType,
207 state_key: Option<&str>,
208 rules: &AuthorizationRules,
209 ) -> Result<Int, String> {
210 let events = self.events(rules)?;
211
212 if let Some(power_level) = events.as_ref().and_then(|events| events.get(event_type)) {
213 return Ok(*power_level);
214 }
215
216 let default_field = if state_key.is_some() {
217 RoomPowerLevelsIntField::StateDefault
218 } else {
219 RoomPowerLevelsIntField::EventsDefault
220 };
221
222 self.get_as_int_or_default(default_field, rules)
223 }
224
225 pub(crate) fn int_fields_map(
228 &self,
229 rules: &AuthorizationRules,
230 ) -> Result<BTreeMap<RoomPowerLevelsIntField, Int>, String> {
231 RoomPowerLevelsIntField::ALL
232 .iter()
233 .copied()
234 .filter_map(|field| match self.get_as_int(field, rules) {
235 Ok(value) => value.map(|value| Ok((field, value))),
236 Err(error) => Some(Err(error)),
237 })
238 .collect()
239 }
240}
241
242impl<E: Event> Deref for RoomPowerLevelsEvent<E> {
243 type Target = E;
244
245 fn deref(&self) -> &Self::Target {
246 &self.inner.event
247 }
248}
249
250pub(crate) trait RoomPowerLevelsEventOptionExt {
252 fn user_power_level(
254 &self,
255 user_id: &UserId,
256 creator: &UserId,
257 rules: &AuthorizationRules,
258 ) -> Result<Int, String>;
259
260 fn get_as_int_or_default(
263 &self,
264 field: RoomPowerLevelsIntField,
265 rules: &AuthorizationRules,
266 ) -> Result<Int, String>;
267
268 fn event_power_level(
270 &self,
271 event_type: &TimelineEventType,
272 state_key: Option<&str>,
273 rules: &AuthorizationRules,
274 ) -> Result<Int, String>;
275}
276
277impl<E: Event> RoomPowerLevelsEventOptionExt for Option<RoomPowerLevelsEvent<E>> {
278 fn user_power_level(
279 &self,
280 user_id: &UserId,
281 creator: &UserId,
282 rules: &AuthorizationRules,
283 ) -> Result<Int, String> {
284 if let Some(room_power_levels_event) = self {
285 room_power_levels_event.user_power_level(user_id, rules)
286 } else {
287 let power_level = if user_id == creator {
288 DEFAULT_CREATOR_POWER_LEVEL.into()
289 } else {
290 RoomPowerLevelsIntField::UsersDefault.default_value()
291 };
292 Ok(power_level)
293 }
294 }
295
296 fn get_as_int_or_default(
297 &self,
298 field: RoomPowerLevelsIntField,
299 rules: &AuthorizationRules,
300 ) -> Result<Int, String> {
301 if let Some(room_power_levels_event) = self {
302 room_power_levels_event.get_as_int_or_default(field, rules)
303 } else {
304 Ok(field.default_value())
305 }
306 }
307
308 fn event_power_level(
309 &self,
310 event_type: &TimelineEventType,
311 state_key: Option<&str>,
312 rules: &AuthorizationRules,
313 ) -> Result<Int, String> {
314 if let Some(room_power_levels_event) = self {
315 room_power_levels_event.event_power_level(event_type, state_key, rules)
316 } else {
317 let default_field = if state_key.is_some() {
318 RoomPowerLevelsIntField::StateDefault
319 } else {
320 RoomPowerLevelsIntField::EventsDefault
321 };
322
323 Ok(default_field.default_value())
324 }
325 }
326}
327
328#[derive(
330 DebugAsRefStr,
331 Clone,
332 Copy,
333 DisplayAsRefStr,
334 PartialEqAsRefStr,
335 Eq,
336 PartialOrdAsRefStr,
337 OrdAsRefStr,
338)]
339#[non_exhaustive]
340pub enum RoomPowerLevelsIntField {
341 UsersDefault,
343
344 EventsDefault,
346
347 StateDefault,
349
350 Ban,
352
353 Redact,
355
356 Kick,
358
359 Invite,
361}
362
363impl RoomPowerLevelsIntField {
364 pub(crate) const ALL: &[RoomPowerLevelsIntField] = &[
366 Self::UsersDefault,
367 Self::EventsDefault,
368 Self::StateDefault,
369 Self::Ban,
370 Self::Redact,
371 Self::Kick,
372 Self::Invite,
373 ];
374
375 pub fn as_str(&self) -> &str {
377 self.as_ref()
378 }
379
380 pub fn default_value(&self) -> Int {
382 match self {
383 Self::UsersDefault | Self::EventsDefault | Self::Invite => int!(0),
384 Self::StateDefault | Self::Kick | Self::Ban | Self::Redact => int!(50),
385 }
386 }
387}
388
389impl AsRef<str> for RoomPowerLevelsIntField {
390 fn as_ref(&self) -> &str {
391 match self {
392 Self::UsersDefault => "users_default",
393 Self::EventsDefault => "events_default",
394 Self::StateDefault => "state_default",
395 Self::Ban => "ban",
396 Self::Redact => "redact",
397 Self::Kick => "kick",
398 Self::Invite => "invite",
399 }
400 }
401}