ruma_state_res/events/
power_levels.rs

1//! Types to deserialize `m.room.power_levels` events.
2
3use 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
25/// The default value of the creator's power level.
26const DEFAULT_CREATOR_POWER_LEVEL: i32 = 100;
27
28/// A helper type for an [`Event`] of type `m.room.power_levels`.
29///
30/// This is a type that deserializes each field lazily, when requested. Some deserialization results
31/// are cached in memory, if they are used often.
32#[derive(Debug, Clone)]
33pub struct RoomPowerLevelsEvent<E: Event> {
34    inner: Arc<RoomPowerLevelsEventInner<E>>,
35}
36
37#[derive(Debug)]
38struct RoomPowerLevelsEventInner<E: Event> {
39    /// The inner `Event`.
40    event: E,
41
42    /// The deserialized content of the event.
43    deserialized_content: OnceLock<JsonObject>,
44
45    /// The values of fields that should contain an integer.
46    int_fields: Mutex<BTreeMap<RoomPowerLevelsIntField, Option<Int>>>,
47
48    /// The power levels of the users, if any.
49    users: OnceLock<Option<BTreeMap<OwnedUserId, Int>>>,
50}
51
52impl<E: Event> RoomPowerLevelsEvent<E> {
53    /// Construct a new `RoomPowerLevelsEvent` around the given event.
54    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    /// The deserialized content of the event.
67    fn deserialized_content(&self) -> Result<&JsonObject, String> {
68        // TODO: Use OnceLock::get_or_try_init when it is stabilized.
69        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    /// Get the value of a field that should contain an integer, if any.
80    ///
81    /// The deserialization of this field is cached in memory.
82    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    /// Get the value of a field that should contain an integer, or its default value if it is
119    /// absent.
120    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    /// Get the value of a field that should contain a map of any value to integer, if any.
129    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    /// Get the power levels required to send events, if any.
155    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    /// Get the power levels required to trigger notifications, if any.
163    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    /// Get the power levels of the users, if any.
171    ///
172    /// The deserialization of this field is cached in memory.
173    pub fn users(
174        &self,
175        rules: &AuthorizationRules,
176    ) -> Result<Option<&BTreeMap<OwnedUserId, Int>>, String> {
177        // TODO: Use OnceLock::get_or_try_init when it is stabilized.
178        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    /// Get the power level of the user with the given ID.
187    ///
188    /// Calling this method several times should be cheap because the necessary deserialization
189    /// results are cached.
190    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    /// Get the power level required to send an event of the given type.
204    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    /// Get a map of all the fields with an integer value in the `content` of an
226    /// `m.room.power_levels` event.
227    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
250/// Helper trait for `Option<RoomPowerLevelsEvent<E>>`.
251pub(crate) trait RoomPowerLevelsEventOptionExt {
252    /// Get the power level of the user with the given ID.
253    fn user_power_level(
254        &self,
255        user_id: &UserId,
256        creator: &UserId,
257        rules: &AuthorizationRules,
258    ) -> Result<Int, String>;
259
260    /// Get the value of a field that should contain an integer, or its default value if it is
261    /// absent.
262    fn get_as_int_or_default(
263        &self,
264        field: RoomPowerLevelsIntField,
265        rules: &AuthorizationRules,
266    ) -> Result<Int, String>;
267
268    /// Get the power level required to send an event of the given type.
269    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/// Fields in the `content` of an `m.room.power_levels` event with an integer value.
329#[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    /// `users_default`
342    UsersDefault,
343
344    /// `events_default`
345    EventsDefault,
346
347    /// `state_default`
348    StateDefault,
349
350    /// `ban`
351    Ban,
352
353    /// `redact`
354    Redact,
355
356    /// `kick`
357    Kick,
358
359    /// `invite`
360    Invite,
361}
362
363impl RoomPowerLevelsIntField {
364    /// A slice containing all the variants.
365    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    /// The string representation of this field.
376    pub fn as_str(&self) -> &str {
377        self.as_ref()
378    }
379
380    /// The default value for this field if it is absent.
381    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}