palpo_core/events/room/power_levels.rs
1//! Types for the [`m.room.power_levels`] event.
2//!
3//! [`m.room.power_levels`]: https://spec.matrix.org/latest/client-server-api/#mroompower_levels
4
5use std::{cmp::max, collections::BTreeMap};
6
7use palpo_macros::EventContent;
8use salvo::oapi::ToSchema;
9use serde::{Deserialize, Serialize};
10
11use crate::events::{
12 EmptyStateKey, EventContent, EventContentFromType, MessageLikeEventType, RedactContent, RedactedStateEventContent,
13 StateEventType, StaticEventContent, TimelineEventType,
14};
15use crate::power_levels::{NotificationPowerLevels, default_power_level};
16use crate::serde::RawJsonValue;
17use crate::{OwnedUserId, RoomVersionId, UserId, push::PushConditionPowerLevelsCtx};
18
19/// The content of an `m.room.power_levels` event.
20///
21/// Defines the power levels (privileges) of users in the room.
22#[derive(ToSchema, Deserialize, Serialize, Clone, Debug, EventContent)]
23#[palpo_event(type = "m.room.power_levels", kind = State, state_key_type = EmptyStateKey, custom_redacted)]
24pub struct RoomPowerLevelsEventContent {
25 /// The level required to ban a user.
26 #[serde(
27 default = "default_power_level",
28 // skip_serializing_if = "is_default_power_level",
29 deserialize_with = "palpo_core::serde::deserialize_v1_powerlevel"
30 )]
31 pub ban: i64,
32
33 /// The level required to send specific event types.
34 ///
35 /// This is a mapping from event type to power level required.
36 #[serde(
37 default,
38 // skip_serializing_if = "BTreeMap::is_empty",
39 deserialize_with = "palpo_core::serde::btreemap_deserialize_v1_powerlevel_values"
40 )]
41 pub events: BTreeMap<TimelineEventType, i64>,
42
43 /// The default level required to send message events.
44 #[serde(
45 default,
46 // skip_serializing_if = "palpo_core::serde::is_default",
47 deserialize_with = "palpo_core::serde::deserialize_v1_powerlevel"
48 )]
49 pub events_default: i64,
50
51 /// The level required to invite a user.
52 #[serde(
53 default,
54 // skip_serializing_if = "palpo_core::serde::is_default",
55 deserialize_with = "palpo_core::serde::deserialize_v1_powerlevel"
56 )]
57 pub invite: i64,
58
59 /// The level required to kick a user.
60 #[serde(
61 default = "default_power_level",
62 // skip_serializing_if = "is_default_power_level",
63 deserialize_with = "palpo_core::serde::deserialize_v1_powerlevel"
64 )]
65 pub kick: i64,
66
67 /// The level required to redact an event.
68 #[serde(
69 default = "default_power_level",
70 // skip_serializing_if = "is_default_power_level",
71 deserialize_with = "palpo_core::serde::deserialize_v1_powerlevel"
72 )]
73 pub redact: i64,
74
75 /// The default level required to send state events.
76 #[serde(
77 default = "default_power_level",
78 // skip_serializing_if = "is_default_power_level",
79 deserialize_with = "palpo_core::serde::deserialize_v1_powerlevel"
80 )]
81 pub state_default: i64,
82
83 /// The power levels for specific users.
84 ///
85 /// This is a mapping from `user_id` to power level for that user.
86 #[serde(
87 default,
88 // skip_serializing_if = "BTreeMap::is_empty",
89 deserialize_with = "palpo_core::serde::btreemap_deserialize_v1_powerlevel_values"
90 )]
91 pub users: BTreeMap<OwnedUserId, i64>,
92
93 /// The default power level for every user in the room.
94 #[serde(
95 default,
96 // skip_serializing_if = "palpo_core::serde::is_default",
97 deserialize_with = "palpo_core::serde::deserialize_v1_powerlevel"
98 )]
99 pub users_default: i64,
100
101 /// The power level requirements for specific notification types.
102 ///
103 /// This is a mapping from `key` to power level for that notifications key.
104 // #[serde(default, skip_serializing_if = "NotificationPowerLevels::is_default")]
105 #[serde(default)]
106 pub notifications: NotificationPowerLevels,
107}
108
109impl RoomPowerLevelsEventContent {
110 /// Creates a new `RoomPowerLevelsEventContent` with all-default values.
111 pub fn new() -> Self {
112 // events_default, users_default and invite having a default of 0 while the others have a
113 // default of 50 is not an oversight, these defaults are from the Matrix specification.
114 Self {
115 ban: default_power_level(),
116 events: BTreeMap::new(),
117 events_default: 0,
118 invite: 0,
119 kick: default_power_level(),
120 redact: default_power_level(),
121 state_default: default_power_level(),
122 users: BTreeMap::new(),
123 users_default: 0,
124 notifications: NotificationPowerLevels::default(),
125 }
126 }
127}
128
129impl Default for RoomPowerLevelsEventContent {
130 fn default() -> Self {
131 Self::new()
132 }
133}
134
135impl RedactContent for RoomPowerLevelsEventContent {
136 type Redacted = RedactedRoomPowerLevelsEventContent;
137
138 fn redact(self, version: &RoomVersionId) -> Self::Redacted {
139 let Self {
140 ban,
141 events,
142 events_default,
143 invite,
144 kick,
145 redact,
146 state_default,
147 users,
148 users_default,
149 ..
150 } = self;
151
152 let invite = match version {
153 RoomVersionId::V1
154 | RoomVersionId::V2
155 | RoomVersionId::V3
156 | RoomVersionId::V4
157 | RoomVersionId::V5
158 | RoomVersionId::V6
159 | RoomVersionId::V7
160 | RoomVersionId::V8
161 | RoomVersionId::V9
162 | RoomVersionId::V10 => 0,
163 _ => invite,
164 };
165
166 RedactedRoomPowerLevelsEventContent {
167 ban,
168 events,
169 events_default,
170 invite,
171 kick,
172 redact,
173 state_default,
174 users,
175 users_default,
176 }
177 }
178}
179
180/// Used with `#[serde(skip_serializing_if)]` to omit default power levels.
181#[allow(clippy::trivially_copy_pass_by_ref)]
182fn is_default_power_level(l: &i64) -> bool {
183 *l == 50
184}
185
186impl RoomPowerLevelsEvent {
187 /// Obtain the effective power levels, regardless of whether this event is redacted.
188 pub fn power_levels(&self) -> RoomPowerLevels {
189 match self {
190 Self::Original(ev) => ev.content.clone().into(),
191 Self::Redacted(ev) => ev.content.clone().into(),
192 }
193 }
194}
195
196impl SyncRoomPowerLevelsEvent {
197 /// Obtain the effective power levels, regardless of whether this event is redacted.
198 pub fn power_levels(&self) -> RoomPowerLevels {
199 match self {
200 Self::Original(ev) => ev.content.clone().into(),
201 Self::Redacted(ev) => ev.content.clone().into(),
202 }
203 }
204}
205
206impl StrippedRoomPowerLevelsEvent {
207 /// Obtain the effective power levels from this event.
208 pub fn power_levels(&self) -> RoomPowerLevels {
209 self.content.clone().into()
210 }
211}
212
213/// Redacted form of [`RoomPowerLevelsEventContent`].
214#[derive(ToSchema, Deserialize, Serialize, Clone, Debug)]
215pub struct RedactedRoomPowerLevelsEventContent {
216 /// The level required to ban a user.
217 #[serde(
218 default = "default_power_level",
219 skip_serializing_if = "is_default_power_level",
220 deserialize_with = "palpo_core::serde::deserialize_v1_powerlevel"
221 )]
222 pub ban: i64,
223
224 /// The level required to send specific event types.
225 ///
226 /// This is a mapping from event type to power level required.
227 #[serde(
228 default,
229 skip_serializing_if = "BTreeMap::is_empty",
230 deserialize_with = "palpo_core::serde::btreemap_deserialize_v1_powerlevel_values"
231 )]
232 pub events: BTreeMap<TimelineEventType, i64>,
233
234 /// The default level required to send message events.
235 #[serde(
236 default,
237 skip_serializing_if = "palpo_core::serde::is_default",
238 deserialize_with = "palpo_core::serde::deserialize_v1_powerlevel"
239 )]
240 pub events_default: i64,
241
242 /// The level required to invite a user.
243 ///
244 /// This field was redacted in room versions 1 through 10. Starting from room version 11 it is
245 /// preserved.
246 #[serde(
247 default,
248 skip_serializing_if = "palpo_core::serde::is_default",
249 deserialize_with = "palpo_core::serde::deserialize_v1_powerlevel"
250 )]
251 pub invite: i64,
252
253 /// The level required to kick a user.
254 #[serde(
255 default = "default_power_level",
256 skip_serializing_if = "is_default_power_level",
257 deserialize_with = "palpo_core::serde::deserialize_v1_powerlevel"
258 )]
259 pub kick: i64,
260
261 /// The level required to redact an event.
262 #[serde(
263 default = "default_power_level",
264 skip_serializing_if = "is_default_power_level",
265 deserialize_with = "palpo_core::serde::deserialize_v1_powerlevel"
266 )]
267 pub redact: i64,
268
269 /// The default level required to send state events.
270 #[serde(
271 default = "default_power_level",
272 skip_serializing_if = "is_default_power_level",
273 deserialize_with = "palpo_core::serde::deserialize_v1_powerlevel"
274 )]
275 pub state_default: i64,
276
277 /// The power levels for specific users.
278 ///
279 /// This is a mapping from `user_id` to power level for that user.
280 #[serde(
281 default,
282 skip_serializing_if = "BTreeMap::is_empty",
283 deserialize_with = "palpo_core::serde::btreemap_deserialize_v1_powerlevel_values"
284 )]
285 pub users: BTreeMap<OwnedUserId, i64>,
286
287 /// The default power level for every user in the room.
288 #[serde(
289 default,
290 skip_serializing_if = "palpo_core::serde::is_default",
291 deserialize_with = "palpo_core::serde::deserialize_v1_powerlevel"
292 )]
293 pub users_default: i64,
294}
295
296impl EventContent for RedactedRoomPowerLevelsEventContent {
297 type EventType = StateEventType;
298
299 fn event_type(&self) -> Self::EventType {
300 StateEventType::RoomPowerLevels
301 }
302}
303
304impl StaticEventContent for RedactedRoomPowerLevelsEventContent {
305 const TYPE: &'static str = "m.room.power_levels";
306}
307
308impl RedactedStateEventContent for RedactedRoomPowerLevelsEventContent {
309 type StateKey = EmptyStateKey;
310}
311
312impl EventContentFromType for RedactedRoomPowerLevelsEventContent {
313 fn from_parts(_ev_type: &str, content: &RawJsonValue) -> serde_json::Result<Self> {
314 serde_json::from_str(content.get())
315 }
316}
317
318/// The effective power levels of a room.
319///
320/// This struct contains the same fields as [`RoomPowerLevelsEventContent`] and be created from that
321/// using a `From` trait implementation, but it is also implements
322/// `From<`[`RedactedRoomPowerLevelsEventContent`]`>`, so can be used when wanting to inspect the
323/// power levels of a room, regardless of whether the most recent power-levels event is redacted or
324/// not.
325#[derive(Clone, Debug)]
326pub struct RoomPowerLevels {
327 /// The level required to ban a user.
328 pub ban: i64,
329
330 /// The level required to send specific event types.
331 ///
332 /// This is a mapping from event type to power level required.
333 pub events: BTreeMap<TimelineEventType, i64>,
334
335 /// The default level required to send message events.
336 pub events_default: i64,
337
338 /// The level required to invite a user.
339 pub invite: i64,
340
341 /// The level required to kick a user.
342 pub kick: i64,
343
344 /// The level required to redact an event.
345 pub redact: i64,
346
347 /// The default level required to send state events.
348 pub state_default: i64,
349
350 /// The power levels for specific users.
351 ///
352 /// This is a mapping from `user_id` to power level for that user.
353 pub users: BTreeMap<OwnedUserId, i64>,
354
355 /// The default power level for every user in the room.
356 pub users_default: i64,
357
358 /// The power level requirements for specific notification types.
359 ///
360 /// This is a mapping from `key` to power level for that notifications key.
361 pub notifications: NotificationPowerLevels,
362}
363
364impl RoomPowerLevels {
365 /// Get the power level of a specific user.
366 pub fn for_user(&self, user_id: &UserId) -> i64 {
367 self.users.get(user_id).map_or(self.users_default, |pl| *pl)
368 }
369
370 /// Whether the given user can ban other users based on the power levels.
371 ///
372 /// Shorthand for `power_levels.user_can_do(user_id, PowerLevelAction::Ban)`.
373 pub fn user_can_ban(&self, user_id: &UserId) -> bool {
374 self.for_user(user_id) >= self.ban
375 }
376
377 /// Whether the acting user can ban the target user based on the power levels.
378 ///
379 /// On top of `power_levels.user_can_ban(acting_user_id)`, this performs an extra check
380 /// to make sure the acting user has at greater power level than the target user.
381 ///
382 /// Shorthand for `power_levels.user_can_do_to_user(acting_user_id, target_user_id,
383 /// PowerLevelUserAction::Ban)`.
384 pub fn user_can_ban_user(&self, acting_user_id: &UserId, target_user_id: &UserId) -> bool {
385 let acting_pl = self.for_user(acting_user_id);
386 let target_pl = self.for_user(target_user_id);
387 acting_pl >= self.ban && target_pl < acting_pl
388 }
389
390 /// Whether the given user can unban other users based on the power levels.
391 ///
392 /// This action requires to be allowed to ban and to kick.
393 ///
394 /// Shorthand for `power_levels.user_can_do(user_id, PowerLevelAction::Unban)`.
395 pub fn user_can_unban(&self, user_id: &UserId) -> bool {
396 let pl = self.for_user(user_id);
397 pl >= self.ban && pl >= self.kick
398 }
399
400 /// Whether the acting user can unban the target user based on the power levels.
401 ///
402 /// This action requires to be allowed to ban and to kick.
403 ///
404 /// On top of `power_levels.user_can_unban(acting_user_id)`, this performs an extra check
405 /// to make sure the acting user has at greater power level than the target user.
406 ///
407 /// Shorthand for `power_levels.user_can_do_to_user(acting_user_id, target_user_id,
408 /// PowerLevelUserAction::Unban)`.
409 pub fn user_can_unban_user(&self, acting_user_id: &UserId, target_user_id: &UserId) -> bool {
410 let acting_pl = self.for_user(acting_user_id);
411 let target_pl = self.for_user(target_user_id);
412 acting_pl >= self.ban && acting_pl >= self.kick && target_pl < acting_pl
413 }
414
415 /// Whether the given user can invite other users based on the power levels.
416 ///
417 /// Shorthand for `power_levels.user_can_do(user_id, PowerLevelAction::Invite)`.
418 pub fn user_can_invite(&self, user_id: &UserId) -> bool {
419 self.for_user(user_id) >= self.invite
420 }
421
422 /// Whether the given user can kick other users based on the power levels.
423 ///
424 /// Shorthand for `power_levels.user_can_do(user_id, PowerLevelAction::Kick)`.
425 pub fn user_can_kick(&self, user_id: &UserId) -> bool {
426 self.for_user(user_id) >= self.kick
427 }
428
429 /// Whether the acting user can kick the target user based on the power levels.
430 ///
431 /// On top of `power_levels.user_can_kick(acting_user_id)`, this performs an extra check
432 /// to make sure the acting user has at least the same power level as the target user.
433 ///
434 /// Shorthand for `power_levels.user_can_do_to_user(acting_user_id, target_user_id,
435 /// PowerLevelUserAction::Kick)`.
436 pub fn user_can_kick_user(&self, acting_user_id: &UserId, target_user_id: &UserId) -> bool {
437 let acting_pl = self.for_user(acting_user_id);
438 let target_pl = self.for_user(target_user_id);
439 acting_pl >= self.kick && target_pl < acting_pl
440 }
441
442 /// Whether the given user can redact events based on the power levels.
443 ///
444 /// Shorthand for `power_levels.user_can_do(user_id, PowerLevelAction::Redact)`.
445 pub fn user_can_redact(&self, user_id: &UserId) -> bool {
446 self.for_user(user_id) >= self.redact
447 }
448
449 /// Whether the given user can send message events based on the power levels.
450 ///
451 /// Shorthand for `power_levels.user_can_do(user_id, PowerLevelAction::SendMessage(msg_type))`.
452 pub fn user_can_send_message(&self, user_id: &UserId, msg_type: MessageLikeEventType) -> bool {
453 self.for_user(user_id)
454 >= self
455 .events
456 .get(&msg_type.into())
457 .map(ToOwned::to_owned)
458 .unwrap_or(self.events_default)
459 }
460
461 /// Whether the given user can send state events based on the power levels.
462 ///
463 /// Shorthand for `power_levels.user_can_do(user_id, PowerLevelAction::SendState(state_type))`.
464 pub fn user_can_send_state(&self, user_id: &UserId, state_type: StateEventType) -> bool {
465 self.for_user(user_id)
466 >= self
467 .events
468 .get(&state_type.into())
469 .map(ToOwned::to_owned)
470 .unwrap_or(self.state_default)
471 }
472
473 /// Whether the given user can notify everybody in the room by writing `@room` in a message.
474 ///
475 /// Shorthand for `power_levels.user_can_do(user_id,
476 /// PowerLevelAction::TriggerNotification(NotificationPowerLevelType::Room))`.
477 pub fn user_can_trigger_room_notification(&self, user_id: &UserId) -> bool {
478 self.for_user(user_id) >= self.notifications.room
479 }
480
481 /// Whether the acting user can change the power level of the target user.
482 ///
483 /// Shorthand for `power_levels.user_can_do_to_user(acting_user_id, target_user_id,
484 /// PowerLevelUserAction::ChangePowerLevel`.
485 pub fn user_can_change_user_power_level(&self, acting_user_id: &UserId, target_user_id: &UserId) -> bool {
486 // Check that the user can change the power levels first.
487 if !self.user_can_send_state(acting_user_id, StateEventType::RoomPowerLevels) {
488 return false;
489 }
490
491 // A user can change their own power level.
492 if acting_user_id == target_user_id {
493 return true;
494 }
495
496 // The permission is different whether the target user is added or changed/removed, so
497 // we need to check that.
498 if let Some(target_pl) = self.users.get(target_user_id).copied() {
499 self.for_user(acting_user_id) > target_pl
500 } else {
501 true
502 }
503 }
504
505 /// Whether the given user can do the given action based on the power levels.
506 pub fn user_can_do(&self, user_id: &UserId, action: PowerLevelAction) -> bool {
507 match action {
508 PowerLevelAction::Ban => self.user_can_ban(user_id),
509 PowerLevelAction::Unban => self.user_can_unban(user_id),
510 PowerLevelAction::Invite => self.user_can_invite(user_id),
511 PowerLevelAction::Kick => self.user_can_kick(user_id),
512 PowerLevelAction::Redact => self.user_can_redact(user_id),
513 PowerLevelAction::SendMessage(message_type) => self.user_can_send_message(user_id, message_type),
514 PowerLevelAction::SendState(state_type) => self.user_can_send_state(user_id, state_type),
515 PowerLevelAction::TriggerNotification(NotificationPowerLevelType::Room) => {
516 self.user_can_trigger_room_notification(user_id)
517 }
518 }
519 }
520
521 /// Whether the acting user can do the given action to the target user based on the power
522 /// levels.
523 pub fn user_can_do_to_user(
524 &self,
525 acting_user_id: &UserId,
526 target_user_id: &UserId,
527 action: PowerLevelUserAction,
528 ) -> bool {
529 match action {
530 PowerLevelUserAction::Ban => self.user_can_ban_user(acting_user_id, target_user_id),
531 PowerLevelUserAction::Unban => self.user_can_unban_user(acting_user_id, target_user_id),
532 PowerLevelUserAction::Invite => self.user_can_invite(acting_user_id),
533 PowerLevelUserAction::Kick => self.user_can_kick_user(acting_user_id, target_user_id),
534 PowerLevelUserAction::ChangePowerLevel => {
535 self.user_can_change_user_power_level(acting_user_id, target_user_id)
536 }
537 }
538 }
539
540 /// Get the maximum power level of any user.
541 pub fn max(&self) -> i64 {
542 self.users
543 .values()
544 .fold(self.users_default, |max_pl, user_pl| max(max_pl, *user_pl))
545 }
546}
547
548impl From<RoomPowerLevelsEventContent> for RoomPowerLevels {
549 fn from(c: RoomPowerLevelsEventContent) -> Self {
550 Self {
551 ban: c.ban,
552 events: c.events,
553 events_default: c.events_default,
554 invite: c.invite,
555 kick: c.kick,
556 redact: c.redact,
557 state_default: c.state_default,
558 users: c.users,
559 users_default: c.users_default,
560 notifications: c.notifications,
561 }
562 }
563}
564
565impl From<RedactedRoomPowerLevelsEventContent> for RoomPowerLevels {
566 fn from(c: RedactedRoomPowerLevelsEventContent) -> Self {
567 Self {
568 ban: c.ban,
569 events: c.events,
570 events_default: c.events_default,
571 invite: c.invite,
572 kick: c.kick,
573 redact: c.redact,
574 state_default: c.state_default,
575 users: c.users,
576 users_default: c.users_default,
577 notifications: NotificationPowerLevels::default(),
578 }
579 }
580}
581
582impl From<RoomPowerLevels> for RoomPowerLevelsEventContent {
583 fn from(c: RoomPowerLevels) -> Self {
584 Self {
585 ban: c.ban,
586 events: c.events,
587 events_default: c.events_default,
588 invite: c.invite,
589 kick: c.kick,
590 redact: c.redact,
591 state_default: c.state_default,
592 users: c.users,
593 users_default: c.users_default,
594 notifications: c.notifications,
595 }
596 }
597}
598
599impl From<RoomPowerLevels> for PushConditionPowerLevelsCtx {
600 fn from(c: RoomPowerLevels) -> Self {
601 Self {
602 users: c.users,
603 users_default: c.users_default,
604 notifications: c.notifications,
605 }
606 }
607}
608
609/// The actions that can be limited by power levels.
610#[derive(Clone, Debug, PartialEq, Eq)]
611#[non_exhaustive]
612pub enum PowerLevelAction {
613 /// Ban a user.
614 Ban,
615
616 /// Unban a user.
617 Unban,
618
619 /// Invite a user.
620 Invite,
621
622 /// Kick a user.
623 Kick,
624
625 /// Redact an event.
626 Redact,
627
628 /// Send a message-like event.
629 SendMessage(MessageLikeEventType),
630
631 /// Send a state event.
632 SendState(StateEventType),
633
634 /// Trigger a notification.
635 TriggerNotification(NotificationPowerLevelType),
636}
637
638/// The notification types that can be limited by power levels.
639#[derive(Clone, Debug, PartialEq, Eq)]
640#[non_exhaustive]
641pub enum NotificationPowerLevelType {
642 /// `@room` notifications.
643 Room,
644}
645
646/// The actions to other users that can be limited by power levels.
647#[derive(Clone, Debug, PartialEq, Eq)]
648#[non_exhaustive]
649pub enum PowerLevelUserAction {
650 /// Ban a user.
651 Ban,
652
653 /// Unban a user.
654 Unban,
655
656 /// Invite a user.
657 Invite,
658
659 /// Kick a user.
660 Kick,
661
662 /// Change a user's power level.
663 ChangePowerLevel,
664}
665
666// #[cfg(test)]
667// mod tests {
668// use std::collections::BTreeMap;
669
670// use crate::user_id;
671// use assign::assign;
672// use maplit::btreemap;
673// use serde_json::{json, to_value as to_json_value};
674
675// use super::{default_power_level, NotificationPowerLevels, RoomPowerLevelsEventContent};
676
677// #[test]
678// fn serialization_with_optional_fields_as_none() {
679// let default = default_power_level();
680
681// let power_levels = RoomPowerLevelsEventContent {
682// ban: default,
683// events: BTreeMap::new(),
684// events_default: 0,
685// invite: 0,
686// kick: default,
687// redact: default,
688// state_default: default,
689// users: BTreeMap::new(),
690// users_default: 0,
691// notifications: NotificationPowerLevels::default(),
692// };
693
694// let actual = to_json_value(&power_levels).unwrap();
695// let expected = json!({});
696
697// assert_eq!(actual, expected);
698// }
699
700// #[test]
701// fn serialization_with_all_fields() {
702// let user = user_id!("@carl:example.com");
703// let power_levels_event = RoomPowerLevelsEventContent {
704// ban: 23,
705// events: btreemap! {
706// "m.dummy".into() => 23
707// },
708// events_default: 23,
709// invite: 23,
710// kick: 23,
711// redact: 23,
712// state_default: 23,
713// users: btreemap! {
714// user.to_owned() => 23
715// },
716// users_default: 23,
717// notifications: assign!(NotificationPowerLevels::new(), { room: 23 }),
718// };
719
720// let actual = to_json_value(&power_levels_event).unwrap();
721// let expected = json!({
722// "ban": 23,
723// "events": {
724// "m.dummy": 23
725// },
726// "events_default": 23,
727// "invite": 23,
728// "kick": 23,
729// "redact": 23,
730// "state_default": 23,
731// "users": {
732// "@carl:example.com": 23
733// },
734// "users_default": 23,
735// "notifications": {
736// "room": 23
737// },
738// });
739
740// assert_eq!(actual, expected);
741// }
742// }