Skip to main content

tuwunel_service/account_data/
push_rules.rs

1//! Room push-rule migration helpers.
2//!
3//! The module carries a room-specific push rule to a successor room while preserving user choices.
4//! Shared account-data limits are enforced before the replacement ruleset is stored.
5
6use ruma::{
7	RoomId, UserId,
8	events::{GlobalAccountDataEventType, push_rules::PushRulesEvent},
9	push::{AnyPushRuleRef, NewPushRule, NewSimplePushRule, RuleKind},
10};
11use tuwunel_core::{Result, debug_warn, err, implement, utils::json::serialized_len};
12
13use super::{MAX_RULE_BYTES, admits_rule};
14
15/// Copies a room's push rule to a successor room.
16///
17/// The source rule's enabled state travels with it, so a rule the user had
18/// switched off does not come back on in the successor. A destination rule that
19/// already exists keeps its own enabled state and takes only the actions.
20///
21/// # Panics
22///
23/// Panics when dispatching the global sequence number for the changed ruleset fails.
24#[implement(super::Service)]
25pub async fn copy_room_push_rule(
26	&self,
27	user_id: &UserId,
28	from_room: &RoomId,
29	to_room: &RoomId,
30) -> Result {
31	let Ok(mut account_data): Result<PushRulesEvent> = self
32		.get_global(user_id, GlobalAccountDataEventType::PushRules)
33		.await
34	else {
35		return Ok(());
36	};
37
38	let ruleset = &mut account_data.content.global;
39
40	let Some(AnyPushRuleRef::Room(rule)) = ruleset.get(RuleKind::Room, from_room) else {
41		return Ok(());
42	};
43
44	let actions = rule.actions.clone();
45	let enabled = rule.enabled;
46
47	if !admits_rule(ruleset, RuleKind::Room, to_room.as_str())
48		|| serialized_len(&actions)? > MAX_RULE_BYTES
49	{
50		debug_warn!(%user_id, %to_room, "Ruleset has no room for the copied push rule");
51
52		return Ok(());
53	}
54
55	let rule = NewPushRule::Room(NewSimplePushRule::new(to_room.to_owned(), actions));
56	let is_new = ruleset.get(RuleKind::Room, to_room).is_none();
57
58	ruleset
59		.insert(rule, None, None)
60		.map_err(|e| err!(Request(InvalidParam("Failed to copy room push rule: {e}"))))?;
61
62	// Insert already inherits a pre-existing destination rule's enabled state.
63	if is_new {
64		ruleset
65			.set_enabled(RuleKind::Room, to_room, enabled)
66			.map_err(|e| err!(Request(InvalidParam("Failed to copy push rule state: {e}"))))?;
67	}
68
69	let ty = GlobalAccountDataEventType::PushRules;
70	let data = serde_json::to_value(account_data)?;
71
72	self.update(None, user_id, ty.to_string().into(), &data)
73		.await
74}