ruma_state_res/events/
power_levels.rs

1//! Types to deserialize `m.room.power_levels` events.
2
3use std::{
4    collections::{BTreeMap, HashSet},
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, EqAsRefStr, JsonObject, OrdAsRefStr,
15    },
16    OwnedUserId, UserId,
17};
18use ruma_events::{room::power_levels::UserPowerLevel, TimelineEventType};
19use serde::de::DeserializeOwned;
20use serde_json::{from_value as from_json_value, Error};
21
22use super::Event;
23
24/// The default value of the creator's power level.
25const DEFAULT_CREATOR_POWER_LEVEL: i32 = 100;
26
27/// A helper type for an [`Event`] of type `m.room.power_levels`.
28///
29/// This is a type that deserializes each field lazily, when requested. Some deserialization results
30/// are cached in memory, if they are used often.
31#[derive(Debug, Clone)]
32pub struct RoomPowerLevelsEvent<E: Event> {
33    inner: Arc<RoomPowerLevelsEventInner<E>>,
34}
35
36#[derive(Debug)]
37struct RoomPowerLevelsEventInner<E: Event> {
38    /// The inner `Event`.
39    event: E,
40
41    /// The deserialized content of the event.
42    deserialized_content: OnceLock<JsonObject>,
43
44    /// The values of fields that should contain an integer.
45    int_fields: Mutex<BTreeMap<RoomPowerLevelsIntField, Option<Int>>>,
46
47    /// The power levels of the users, if any.
48    users: OnceLock<Option<BTreeMap<OwnedUserId, Int>>>,
49}
50
51impl<E: Event> RoomPowerLevelsEvent<E> {
52    /// Construct a new `RoomPowerLevelsEvent` around the given event.
53    pub fn new(event: E) -> Self {
54        Self {
55            inner: RoomPowerLevelsEventInner {
56                event,
57                deserialized_content: OnceLock::new(),
58                int_fields: Mutex::new(BTreeMap::new()),
59                users: OnceLock::new(),
60            }
61            .into(),
62        }
63    }
64
65    /// The deserialized content of the event.
66    fn deserialized_content(&self) -> Result<&JsonObject, String> {
67        // TODO: Use OnceLock::get_or_try_init when it is stabilized.
68        if let Some(content) = self.inner.deserialized_content.get() {
69            Ok(content)
70        } else {
71            let content = from_raw_json_value(self.content()).map_err(|error: Error| {
72                format!("malformed `m.room.power_levels` content: {error}")
73            })?;
74            Ok(self.inner.deserialized_content.get_or_init(|| content))
75        }
76    }
77
78    /// Get the value of a field that should contain an integer, if any.
79    ///
80    /// The deserialization of this field is cached in memory.
81    pub fn get_as_int(
82        &self,
83        field: RoomPowerLevelsIntField,
84        rules: &AuthorizationRules,
85    ) -> Result<Option<Int>, String> {
86        let mut int_fields =
87            self.inner.int_fields.lock().expect("we never panic while holding the mutex");
88
89        if let Some(power_level) = int_fields.get(&field) {
90            return Ok(*power_level);
91        }
92
93        let content = self.deserialized_content()?;
94
95        let Some(value) = content.get(field.as_str()) else {
96            int_fields.insert(field, None);
97            return Ok(None);
98        };
99
100        let res = if rules.integer_power_levels {
101            from_json_value(value.clone())
102        } else {
103            deserialize_v1_powerlevel(value)
104        };
105
106        let power_level = res.map(Some).map_err(|error| {
107            format!(
108                "unexpected format of `{field}` field in `content` \
109                 of `m.room.power_levels` event: {error}"
110            )
111        })?;
112
113        int_fields.insert(field, power_level);
114        Ok(power_level)
115    }
116
117    /// Get the value of a field that should contain an integer, or its default value if it is
118    /// absent.
119    pub fn get_as_int_or_default(
120        &self,
121        field: RoomPowerLevelsIntField,
122        rules: &AuthorizationRules,
123    ) -> Result<Int, String> {
124        Ok(self.get_as_int(field, rules)?.unwrap_or_else(|| field.default_value()))
125    }
126
127    /// Get the value of a field that should contain a map of any value to integer, if any.
128    fn get_as_int_map<T: Ord + DeserializeOwned>(
129        &self,
130        field: &str,
131        rules: &AuthorizationRules,
132    ) -> Result<Option<BTreeMap<T, Int>>, String> {
133        let content = self.deserialized_content()?;
134
135        let Some(value) = content.get(field) else {
136            return Ok(None);
137        };
138
139        let res = if rules.integer_power_levels {
140            from_json_value(value.clone())
141        } else {
142            btreemap_deserialize_v1_powerlevel_values(value)
143        };
144
145        res.map(Some).map_err(|error| {
146            format!(
147                "unexpected format of `{field}` field in `content` \
148                 of `m.room.power_levels` event: {error}"
149            )
150        })
151    }
152
153    /// Get the power levels required to send events, if any.
154    pub fn events(
155        &self,
156        rules: &AuthorizationRules,
157    ) -> Result<Option<BTreeMap<TimelineEventType, Int>>, String> {
158        self.get_as_int_map("events", rules)
159    }
160
161    /// Get the power levels required to trigger notifications, if any.
162    pub fn notifications(
163        &self,
164        rules: &AuthorizationRules,
165    ) -> Result<Option<BTreeMap<String, Int>>, String> {
166        self.get_as_int_map("notifications", rules)
167    }
168
169    /// Get the power levels of the users, if any.
170    ///
171    /// The deserialization of this field is cached in memory.
172    pub fn users(
173        &self,
174        rules: &AuthorizationRules,
175    ) -> Result<Option<&BTreeMap<OwnedUserId, Int>>, String> {
176        // TODO: Use OnceLock::get_or_try_init when it is stabilized.
177        if let Some(users) = self.inner.users.get() {
178            Ok(users.as_ref())
179        } else {
180            let users = self.get_as_int_map("users", rules)?;
181            Ok(self.inner.users.get_or_init(|| users).as_ref())
182        }
183    }
184
185    /// Get the power level of the user with the given ID.
186    ///
187    /// Calling this method several times should be cheap because the necessary deserialization
188    /// results are cached.
189    pub fn user_power_level(
190        &self,
191        user_id: &UserId,
192        rules: &AuthorizationRules,
193    ) -> Result<Int, String> {
194        if let Some(power_level) = self.users(rules)?.as_ref().and_then(|users| users.get(user_id))
195        {
196            return Ok(*power_level);
197        }
198
199        self.get_as_int_or_default(RoomPowerLevelsIntField::UsersDefault, rules)
200    }
201
202    /// Get the power level required to send an event of the given type.
203    pub fn event_power_level(
204        &self,
205        event_type: &TimelineEventType,
206        state_key: Option<&str>,
207        rules: &AuthorizationRules,
208    ) -> Result<Int, String> {
209        let events = self.events(rules)?;
210
211        if let Some(power_level) = events.as_ref().and_then(|events| events.get(event_type)) {
212            return Ok(*power_level);
213        }
214
215        let default_field = if state_key.is_some() {
216            RoomPowerLevelsIntField::StateDefault
217        } else {
218            RoomPowerLevelsIntField::EventsDefault
219        };
220
221        self.get_as_int_or_default(default_field, rules)
222    }
223
224    /// Get a map of all the fields with an integer value in the `content` of an
225    /// `m.room.power_levels` event.
226    pub(crate) fn int_fields_map(
227        &self,
228        rules: &AuthorizationRules,
229    ) -> Result<BTreeMap<RoomPowerLevelsIntField, Int>, String> {
230        RoomPowerLevelsIntField::ALL
231            .iter()
232            .copied()
233            .filter_map(|field| match self.get_as_int(field, rules) {
234                Ok(value) => value.map(|value| Ok((field, value))),
235                Err(error) => Some(Err(error)),
236            })
237            .collect()
238    }
239}
240
241impl<E: Event> Deref for RoomPowerLevelsEvent<E> {
242    type Target = E;
243
244    fn deref(&self) -> &Self::Target {
245        &self.inner.event
246    }
247}
248
249/// Helper trait for `Option<RoomPowerLevelsEvent<E>>`.
250pub(crate) trait RoomPowerLevelsEventOptionExt {
251    /// Get the power level of the user with the given ID.
252    fn user_power_level(
253        &self,
254        user_id: &UserId,
255        creators: &HashSet<OwnedUserId>,
256        rules: &AuthorizationRules,
257    ) -> Result<UserPowerLevel, String>;
258
259    /// Get the value of a field that should contain an integer, or its default value if it is
260    /// absent.
261    fn get_as_int_or_default(
262        &self,
263        field: RoomPowerLevelsIntField,
264        rules: &AuthorizationRules,
265    ) -> Result<Int, String>;
266
267    /// Get the power level required to send an event of the given type.
268    fn event_power_level(
269        &self,
270        event_type: &TimelineEventType,
271        state_key: Option<&str>,
272        rules: &AuthorizationRules,
273    ) -> Result<Int, String>;
274}
275
276impl<E: Event> RoomPowerLevelsEventOptionExt for Option<RoomPowerLevelsEvent<E>> {
277    fn user_power_level(
278        &self,
279        user_id: &UserId,
280        creators: &HashSet<OwnedUserId>,
281        rules: &AuthorizationRules,
282    ) -> Result<UserPowerLevel, String> {
283        if rules.explicitly_privilege_room_creators && creators.contains(user_id) {
284            Ok(UserPowerLevel::Infinite)
285        } else if let Some(room_power_levels_event) = self {
286            room_power_levels_event.user_power_level(user_id, rules).map(Into::into)
287        } else {
288            let power_level = if creators.contains(user_id) {
289                DEFAULT_CREATOR_POWER_LEVEL.into()
290            } else {
291                RoomPowerLevelsIntField::UsersDefault.default_value()
292            };
293            Ok(power_level.into())
294        }
295    }
296
297    fn get_as_int_or_default(
298        &self,
299        field: RoomPowerLevelsIntField,
300        rules: &AuthorizationRules,
301    ) -> Result<Int, String> {
302        if let Some(room_power_levels_event) = self {
303            room_power_levels_event.get_as_int_or_default(field, rules)
304        } else {
305            Ok(field.default_value())
306        }
307    }
308
309    fn event_power_level(
310        &self,
311        event_type: &TimelineEventType,
312        state_key: Option<&str>,
313        rules: &AuthorizationRules,
314    ) -> Result<Int, String> {
315        if let Some(room_power_levels_event) = self {
316            room_power_levels_event.event_power_level(event_type, state_key, rules)
317        } else {
318            let default_field = if state_key.is_some() {
319                RoomPowerLevelsIntField::StateDefault
320            } else {
321                RoomPowerLevelsIntField::EventsDefault
322            };
323
324            Ok(default_field.default_value())
325        }
326    }
327}
328
329/// Fields in the `content` of an `m.room.power_levels` event with an integer value.
330#[derive(DebugAsRefStr, Clone, Copy, DisplayAsRefStr, EqAsRefStr, OrdAsRefStr)]
331#[non_exhaustive]
332pub enum RoomPowerLevelsIntField {
333    /// `users_default`
334    UsersDefault,
335
336    /// `events_default`
337    EventsDefault,
338
339    /// `state_default`
340    StateDefault,
341
342    /// `ban`
343    Ban,
344
345    /// `redact`
346    Redact,
347
348    /// `kick`
349    Kick,
350
351    /// `invite`
352    Invite,
353}
354
355impl RoomPowerLevelsIntField {
356    /// A slice containing all the variants.
357    pub(crate) const ALL: &[RoomPowerLevelsIntField] = &[
358        Self::UsersDefault,
359        Self::EventsDefault,
360        Self::StateDefault,
361        Self::Ban,
362        Self::Redact,
363        Self::Kick,
364        Self::Invite,
365    ];
366
367    /// The string representation of this field.
368    pub fn as_str(&self) -> &str {
369        self.as_ref()
370    }
371
372    /// The default value for this field if it is absent.
373    pub fn default_value(&self) -> Int {
374        match self {
375            Self::UsersDefault | Self::EventsDefault | Self::Invite => int!(0),
376            Self::StateDefault | Self::Kick | Self::Ban | Self::Redact => int!(50),
377        }
378    }
379}
380
381impl AsRef<str> for RoomPowerLevelsIntField {
382    fn as_ref(&self) -> &str {
383        match self {
384            Self::UsersDefault => "users_default",
385            Self::EventsDefault => "events_default",
386            Self::StateDefault => "state_default",
387            Self::Ban => "ban",
388            Self::Redact => "redact",
389            Self::Kick => "kick",
390            Self::Invite => "invite",
391        }
392    }
393}