Skip to main content

tuwunel_api/client/account_data/
mod.rs

1mod delete_global_account_data;
2mod delete_room_account_data;
3mod get_global_account_data;
4mod get_room_account_data;
5mod set_global_account_data;
6mod set_room_account_data;
7
8use ruma::{
9	RoomId, UserId,
10	events::{GlobalAccountDataEventType, RoomAccountDataEventType},
11	serde::Raw,
12};
13use serde::Deserialize;
14use serde_json::{Value as JsonValue, json, value::RawValue as RawJsonValue};
15use tuwunel_core::{Err, Result, err};
16use tuwunel_service::Services;
17
18pub(crate) use self::{
19	delete_global_account_data::delete_global_account_data_route,
20	delete_room_account_data::delete_room_account_data_route,
21	get_global_account_data::get_global_account_data_route,
22	get_room_account_data::get_room_account_data_route,
23	set_global_account_data::set_global_account_data_route,
24	set_room_account_data::set_room_account_data_route,
25};
26
27async fn set_account_data(
28	services: &Services,
29	room_id: Option<&RoomId>,
30	sender_user: &UserId,
31	event_type_s: &str,
32	data: &RawJsonValue,
33) -> Result {
34	if event_type_s == RoomAccountDataEventType::FullyRead.to_cow_str() {
35		return Err!(Request(BadJson(
36			"This endpoint cannot be used for marking a room as fully read (setting \
37			 m.fully_read)"
38		)));
39	}
40
41	if event_type_s == GlobalAccountDataEventType::PushRules.to_cow_str() {
42		return Err!(Request(BadJson(
43			"This endpoint cannot be used for setting/configuring push rules."
44		)));
45	}
46
47	let data: serde_json::Value = serde_json::from_str(data.get())
48		.map_err(|e| err!(Request(BadJson(warn!("Invalid JSON provided: {e}")))))?;
49
50	services
51		.account_data
52		.update(
53			room_id,
54			sender_user,
55			event_type_s.into(),
56			&json!({
57				"type": event_type_s,
58				"content": data,
59			}),
60		)
61		.await
62}
63
64/// MSC3391: tombstoned account data carries `content: {}`. Sync delta
65/// surfaces the empty event so clients can apply the deletion; everywhere
66/// else (GET, initial sync) treats it as not-present.
67fn is_empty_content<T>(content: &Raw<T>) -> bool { is_empty_object_json(content.json()) }
68
69/// Equivalent test against a stored account-data event (`{type, content}`)
70/// rather than the bare `content` payload. Used by sync filters.
71pub(crate) fn is_empty_account_data_event<T>(event: &Raw<T>) -> bool {
72	#[derive(Deserialize)]
73	struct ContentOnly<'a> {
74		#[serde(borrow)]
75		content: &'a RawJsonValue,
76	}
77
78	serde_json::from_str::<ContentOnly<'_>>(event.json().get())
79		.is_ok_and(|c| is_empty_object_json(c.content))
80}
81
82fn is_empty_object_json(s: &RawJsonValue) -> bool {
83	serde_json::from_str::<JsonValue>(s.get())
84		.ok()
85		.and_then(|v| v.as_object().map(serde_json::Map::is_empty))
86		.unwrap_or(false)
87}