Skip to main content

tuwunel_core/matrix/pdu/
unsigned.rs

1use std::{
2	borrow::Cow,
3	collections::{BTreeMap, btree_map::Entry},
4	fmt::{Formatter, Result as FmtResult},
5	iter::from_fn,
6};
7
8use ruma::{
9	MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedUserId, UserId,
10	events::{AnySyncMessageLikeEvent, room::member::MembershipState},
11	serde::Raw,
12};
13use serde::{
14	Deserialize, Deserializer, Serialize, Serializer,
15	de::{DeserializeSeed, Error as DeError, IgnoredAny, MapAccess, Visitor},
16	ser::SerializeMap,
17};
18use serde_json::value::{RawValue as RawJsonValue, Value as JsonValue, to_raw_value};
19
20use super::{Pdu, Unsigned};
21use crate::{Result, err, implement, utils::BoolExt};
22
23type BorrowedObject<'a> = BTreeMap<Cow<'a, str>, &'a RawJsonValue>;
24type JsonEntry<'a> = (JsonString<'a>, &'a RawJsonValue);
25
26struct RelationBundle<'a> {
27	unsigned: BorrowedObject<'a>,
28	relations: BorrowedObject<'a>,
29}
30
31struct ThreadBundleFields<'a> {
32	unsigned: BorrowedObject<'a>,
33	relations: BorrowedObject<'a>,
34	thread: BorrowedObject<'a>,
35	latest_event: BorrowedObject<'a>,
36	latest_event_unsigned: BorrowedObject<'a>,
37}
38
39struct RawObjectPatch<'a, 'raw, T> {
40	object: &'a BorrowedObject<'raw>,
41	field: &'static str,
42	value: T,
43}
44
45struct RawObjectRemove<'a, 'raw> {
46	object: &'a BorrowedObject<'raw>,
47	field: &'a str,
48}
49
50struct BorrowedField<'a>(&'a str);
51
52struct UniqueObject;
53
54struct JsonString<'a>(Cow<'a, str>);
55
56struct JsonStringVisitor;
57
58#[derive(Serialize)]
59struct ReferenceBundle<'a> {
60	chunk: ReferenceChunk<'a>,
61}
62
63struct ReferenceChunk<'a>(&'a [OwnedEventId]);
64
65#[derive(Serialize)]
66struct ReferenceEvent<'a> {
67	event_id: &'a OwnedEventId,
68}
69
70#[derive(Deserialize)]
71struct Identity {
72	event_id: OwnedEventId,
73	sender: OwnedUserId,
74}
75
76/// Removes the transaction ID unless the event is served to its own sender.
77///
78/// The token is the sending client's own idempotency value, which it matches
79/// its echo against, so the sender keeps it and nobody else sees it. A `None`
80/// requester is nobody in particular and is treated as somebody else.
81#[implement(Pdu)]
82pub fn remove_transaction_id_unless_sender(&mut self, user_id: Option<&UserId>) -> Result {
83	user_id
84		.is_none_or(|user_id| self.sender != *user_id)
85		.then(|| self.remove_transaction_id())
86		.unwrap_or(Ok(()))
87}
88
89/// Removes the local transaction ID from unsigned event metadata.
90///
91/// Other unsigned properties are retained and the object is re-encoded. An
92/// event without unsigned data, or without the key, is left unchanged.
93#[implement(Pdu)]
94pub fn remove_transaction_id(&mut self) -> Result {
95	use BTreeMap as Map;
96
97	let Some(unsigned) = &self.unsigned else {
98		return Ok(());
99	};
100
101	let raw = unsigned.json().get();
102	if !raw.contains("\"transaction_id\"") {
103		return Ok(());
104	}
105
106	let mut unsigned: Map<&str, Raw<JsonValue>> = serde_json::from_str(raw)
107		.map_err(|e| err!(Database("Invalid unsigned in pdu event: {e}")))?;
108
109	unsigned.remove("transaction_id");
110	self.unsigned = to_raw_value(&unsigned)
111		.map(Into::into)
112		.map(Some)
113		.expect("unsigned is valid");
114
115	Ok(())
116}
117
118/// State-section serving strips the stored `prev_content`/`prev_sender`
119/// pair, dropping `unsigned` entirely when emptied; timeline serving keeps
120/// the trio.
121#[implement(Pdu)]
122pub fn remove_prev_state(&mut self) -> Result {
123	use BTreeMap as Map;
124
125	let Some(unsigned) = &self.unsigned else {
126		return Ok(());
127	};
128
129	let raw = unsigned.json().get();
130	let prev_keys = raw.contains("\"prev_content\"") || raw.contains("\"prev_sender\"");
131	if !prev_keys && raw != "{}" {
132		return Ok(());
133	}
134
135	let mut unsigned: Map<&str, Raw<JsonValue>> = serde_json::from_str(raw)
136		.map_err(|e| err!(Database("Invalid unsigned in pdu event: {e}")))?;
137
138	unsigned.remove("prev_content");
139	unsigned.remove("prev_sender");
140	self.unsigned = unsigned
141		.is_empty()
142		.is_false()
143		.then(|| to_raw_value(&unsigned))
144		.transpose()?
145		.map(Into::into);
146
147	Ok(())
148}
149
150/// Adds the event's current age to unsigned metadata.
151///
152/// Age is the saturating millisecond difference between the current time and
153/// `origin_server_ts`. Future timestamps can therefore produce a negative
154/// value.
155#[implement(Pdu)]
156pub fn add_age(&mut self) -> Result {
157	use BTreeMap as Map;
158
159	let mut unsigned: Map<&str, Raw<JsonValue>> = self
160		.unsigned
161		.as_ref()
162		.map(Unsigned::json)
163		.map(RawJsonValue::get)
164		.map_or_else(|| Ok(Map::new()), serde_json::from_str)
165		.map_err(|e| err!(Database("Invalid unsigned in pdu event: {e}")))?;
166
167	// deliberately allowing for the possibility of negative age
168	let now: i128 = MilliSecondsSinceUnixEpoch::now().get().into();
169	let then: i128 = self.origin_server_ts.into();
170	let this_age = now.saturating_sub(then);
171
172	unsigned.insert("age", raw_of(&this_age)?);
173	self.unsigned = Some(to_raw_value(&unsigned)?.into());
174
175	Ok(())
176}
177
178/// MSC4115: annotate the served event with the requesting user's room
179/// membership at the time of the event.
180#[implement(Pdu)]
181pub fn add_membership(&mut self, membership: &MembershipState) -> Result {
182	use BTreeMap as Map;
183
184	let mut unsigned: Map<&str, Raw<JsonValue>> = self
185		.unsigned
186		.as_ref()
187		.map(Unsigned::json)
188		.map(RawJsonValue::get)
189		.map_or_else(|| Ok(Map::new()), serde_json::from_str)
190		.map_err(|e| err!(Database("Invalid unsigned in pdu event: {e}")))?;
191
192	unsigned.insert("membership", raw_of(membership)?);
193	self.unsigned = Some(to_raw_value(&unsigned)?.into());
194
195	Ok(())
196}
197
198/// Adds or replaces a named bundled relation in unsigned metadata.
199///
200/// The related PDU is serialized under `unsigned.m.relations`; `None` stores an
201/// empty object for the named relation. Existing unsigned properties are
202/// retained.
203#[implement(Pdu)]
204pub fn add_relation(&mut self, name: &str, pdu: Option<&Pdu>) -> Result {
205	use serde_json::Map;
206
207	let mut unsigned: Map<String, JsonValue> = self
208		.unsigned
209		.as_ref()
210		.map(Unsigned::json)
211		.map(RawJsonValue::get)
212		.map_or_else(|| Ok(Map::new()), serde_json::from_str)
213		.map_err(|e| err!(Database("Invalid unsigned in pdu event: {e}")))?;
214
215	let pdu = pdu
216		.map(serde_json::to_value)
217		.transpose()?
218		.unwrap_or_else(|| JsonValue::Object(Map::new()));
219
220	unsigned
221		.entry("m.relations")
222		.or_insert(JsonValue::Object(Map::new()))
223		.as_object_mut()
224		.map(|object| object.insert(name.to_owned(), pdu));
225
226	self.unsigned = Some(to_raw_value(&unsigned)?.into());
227
228	Ok(())
229}
230
231/// MSC3816: overwrite `unsigned.m.relations.m.thread.current_user_participated`
232/// with a per-requester value. No-op when the event carries no thread bundle.
233#[implement(Pdu)]
234pub fn set_thread_participated(&mut self, participated: bool) -> Result {
235	use serde_json::Map;
236
237	let Some(unsigned) = self.unsigned.as_ref() else {
238		return Ok(());
239	};
240
241	let mut unsigned: Map<String, JsonValue> = serde_json::from_str(unsigned.json().get())
242		.map_err(|e| err!(Database("Invalid unsigned in pdu event: {e}")))?;
243
244	let updated = unsigned
245		.get_mut("m.relations")
246		.and_then(JsonValue::as_object_mut)
247		.and_then(|relations| relations.get_mut("m.thread"))
248		.and_then(JsonValue::as_object_mut)
249		.map(|thread| {
250			thread.insert("current_user_participated".to_owned(), participated.into());
251		})
252		.is_some();
253
254	if updated {
255		self.unsigned = Some(to_raw_value(&unsigned)?.into());
256	}
257
258	Ok(())
259}
260
261/// Identifies the sender and event ID of a bundled thread preview.
262///
263/// MSC4025 uses the sender for the erasure check and the event ID to load the
264/// event. The thread writer derives both fields from one validated event.
265#[implement(Pdu)]
266#[inline]
267pub fn thread_latest_event(&self) -> Result<Option<(OwnedEventId, OwnedUserId)>> {
268	self.unsigned
269		.as_ref()
270		.map(Unsigned::json)
271		.map(thread_latest)
272		.transpose()?
273		.flatten()
274		.map(thread_identity)
275		.transpose()
276}
277
278/// Whether `unsigned.m.relations` contains an `m.thread` bundle.
279///
280/// This decodes object keys, so escaped spellings cannot bypass read-time
281/// privacy handling.
282#[implement(Pdu)]
283pub fn has_thread_bundle(&self) -> Result<bool> {
284	self.unsigned
285		.as_ref()
286		.map(Unsigned::json)
287		.map(thread_bundle)
288		.transpose()
289		.map(|thread| thread.flatten().is_some())
290}
291
292/// MSC4025: overwrite `unsigned.m.relations.m.thread.latest_event`, serving
293/// the pruned form of an erased sender's thread activity. No-op when the
294/// event carries no thread bundle.
295#[implement(Pdu)]
296pub fn set_thread_latest_event(&mut self, latest: &Raw<AnySyncMessageLikeEvent>) -> Result {
297	use serde_json::Map;
298
299	let Some(unsigned) = self.unsigned.as_ref() else {
300		return Ok(());
301	};
302
303	let latest = serde_json::to_value(latest)?;
304
305	let mut unsigned: Map<String, JsonValue> = serde_json::from_str(unsigned.json().get())
306		.map_err(|e| err!(Database("Invalid unsigned in pdu event: {e}")))?;
307
308	let updated = unsigned
309		.get_mut("m.relations")
310		.and_then(JsonValue::as_object_mut)
311		.and_then(|relations| relations.get_mut("m.thread"))
312		.and_then(JsonValue::as_object_mut)
313		.map(|thread| {
314			thread.insert("latest_event".to_owned(), latest);
315		})
316		.is_some();
317
318	if updated {
319		self.unsigned = Some(to_raw_value(&unsigned)?.into());
320	}
321
322	Ok(())
323}
324
325/// Removes a thread preview transaction ID unless served to its own sender.
326///
327/// Stored thread bundles predate write-side sanitization, so this rewrites the
328/// nested event at serve time while retaining its other unsigned properties.
329/// A missing or invalid sender returns an error so callers can fail closed.
330#[implement(Pdu)]
331pub fn remove_thread_latest_transaction_id_unless_sender(&mut self, user_id: &UserId) -> Result {
332	if let Some(unsigned) = self
333		.unsigned
334		.as_ref()
335		.map(Unsigned::json)
336		.map(|raw| thread_without_transaction_id(raw, user_id))
337		.transpose()?
338		.flatten()
339	{
340		self.unsigned = Some(unsigned);
341	}
342
343	Ok(())
344}
345
346fn thread_without_transaction_id(
347	raw: &RawJsonValue,
348	user_id: &UserId,
349) -> Result<Option<Unsigned>> {
350	thread_transaction_sender(raw)?
351		.map(|sender| serde_json::from_str(sender.get()))
352		.transpose()
353		.map_err(|error| err!(Database("Invalid sender in thread latest event: {error}")))?
354		.filter(|JsonString(sender)| sender != user_id.as_str())
355		.map(|JsonString(sender)| {
356			UserId::parse(sender.as_ref())
357				.map_err(|error| err!(Database("Invalid sender in thread latest event: {error}")))
358				.and_then(|_| ThreadBundleFields::parse(raw)?.without_transaction_id())
359		})
360		.transpose()
361}
362
363fn thread_transaction_sender(raw: &RawJsonValue) -> Result<Option<&RawJsonValue>> {
364	thread_latest(raw)?
365		.map(transaction_sender)
366		.transpose()
367		.map(Option::flatten)
368}
369
370fn transaction_sender(latest_event: &RawJsonValue) -> Result<Option<&RawJsonValue>> {
371	let sender = || {
372		raw_field(latest_event, "sender", "thread latest event")?.ok_or_else(|| {
373			err!(Database("Thread latest event with transaction ID has no sender"))
374		})
375	};
376
377	raw_field(latest_event, "unsigned", "thread latest event")?
378		.map(|unsigned| raw_field(unsigned, "transaction_id", "thread latest event unsigned"))
379		.transpose()?
380		.flatten()
381		.map(|_| sender())
382		.transpose()
383}
384
385fn thread_latest(raw: &RawJsonValue) -> Result<Option<&RawJsonValue>> {
386	thread_bundle(raw)?
387		.map(|thread| raw_field(thread, "latest_event", "thread bundle"))
388		.transpose()
389		.map(Option::flatten)
390}
391
392fn thread_bundle(raw: &RawJsonValue) -> Result<Option<&RawJsonValue>> {
393	raw_field(raw, "m.relations", "unsigned")?
394		.map(|relations| raw_field(relations, "m.thread", "bundled relations"))
395		.transpose()
396		.map(Option::flatten)
397}
398
399fn thread_identity(raw: &RawJsonValue) -> Result<(OwnedEventId, OwnedUserId)> {
400	serde_json::from_str(raw.get())
401		.map(|Identity { event_id, sender }| (event_id, sender))
402		.map_err(|error| err!(Database("Invalid thread latest event in PDU event: {error}")))
403}
404
405/// Excise `m.thread` from `unsigned.m.relations`, retaining unrelated data.
406///
407/// Empty relation and unsigned objects are removed with the bundle.
408#[implement(Pdu)]
409pub fn remove_thread_bundle(&mut self) -> Result {
410	remove_relation_bundle(self, "m.thread")
411		.or_else(|_| remove_relation_bundle_canonical(self, "m.thread"))
412}
413
414/// Canonicalize malformed or ambiguous relation objects while removing only
415/// the requested relation. This allocation is reserved for the error path of
416/// the borrowed surgical rewrite.
417fn remove_relation_bundle_canonical(pdu: &mut Pdu, relation_type: &str) -> Result {
418	let Some(raw) = pdu.unsigned.as_ref() else {
419		return Ok(());
420	};
421
422	let mut unsigned: JsonValue = serde_json::from_str(raw.json().get())?;
423	let unsigned = unsigned
424		.as_object_mut()
425		.ok_or_else(|| err!(Database("Invalid unsigned object in PDU event")))?;
426
427	let remove_relations = match unsigned.get_mut("m.relations") {
428		| None => return Ok(()),
429		| Some(JsonValue::Object(relations)) => {
430			relations.remove(relation_type);
431			relations.is_empty()
432		},
433		| Some(_) => true,
434	};
435
436	if remove_relations {
437		unsigned.remove("m.relations");
438	}
439
440	pdu.unsigned = if unsigned.is_empty() {
441		None
442	} else {
443		Some(raw_as(unsigned)?)
444	};
445
446	Ok(())
447}
448
449fn remove_relation_bundle(pdu: &mut Pdu, relation_type: &str) -> Result {
450	if let Some(unsigned) = pdu
451		.unsigned
452		.as_ref()
453		.map(Unsigned::json)
454		.map(|raw| RelationBundle::parse(raw, relation_type))
455		.transpose()?
456		.flatten()
457		.map(|bundle| bundle.without(relation_type))
458		.transpose()?
459	{
460		pdu.unsigned = unsigned;
461	}
462
463	Ok(())
464}
465
466#[implement(RelationBundle, generics = "<'a>", params = "<'a>")]
467fn parse(raw: &'a RawJsonValue, relation_type: &str) -> Result<Option<Self>> {
468	let unsigned = raw_object(raw, "unsigned")?;
469
470	unsigned
471		.get("m.relations")
472		.copied()
473		.map(|relations| raw_object(relations, "bundled relations"))
474		.transpose()
475		.map(|relations| {
476			relations
477				.filter(|relations| relations.contains_key(relation_type))
478				.map(|relations| Self { unsigned, relations })
479		})
480}
481
482#[implement(RelationBundle, generics = "<'a>", params = "<'a>")]
483fn without(&self, relation_type: &str) -> Result<Option<Unsigned>> {
484	match self.relations.len() {
485		| 1 => self
486			.unsigned
487			.len()
488			.ne(&1)
489			.then(|| raw_as(&RawObjectRemove::new(&self.unsigned, "m.relations")))
490			.transpose(),
491		| _ => {
492			let relations = RawObjectRemove::new(&self.relations, relation_type);
493			let unsigned = RawObjectPatch::new(&self.unsigned, "m.relations", relations);
494
495			raw_as(&unsigned).map(Some)
496		},
497	}
498}
499
500/// MSC3856: overwrite `unsigned.m.relations.m.thread.count` with a
501/// per-requester value excluding ignored senders' replies. No-op when the
502/// event carries no thread bundle.
503#[implement(Pdu)]
504pub fn set_thread_count(&mut self, count: usize) -> Result {
505	use serde_json::Map;
506
507	let Some(unsigned) = self.unsigned.as_ref() else {
508		return Ok(());
509	};
510
511	let mut unsigned: Map<String, JsonValue> = serde_json::from_str(unsigned.json().get())
512		.map_err(|e| err!(Database("Invalid unsigned in pdu event: {e}")))?;
513
514	let updated = unsigned
515		.get_mut("m.relations")
516		.and_then(JsonValue::as_object_mut)
517		.and_then(|relations| relations.get_mut("m.thread"))
518		.and_then(JsonValue::as_object_mut)
519		.map(|thread| {
520			thread.insert("count".to_owned(), count.into());
521		})
522		.is_some();
523
524	if updated {
525		self.unsigned = Some(to_raw_value(&unsigned)?.into());
526	}
527
528	Ok(())
529}
530
531/// MSC3925: fold the newest `m.replace` edit into
532/// `unsigned.m.relations.m.replace` as the full replacement event, preserving
533/// an existing bundle such as `m.thread` and creating `unsigned` when absent.
534#[implement(Pdu)]
535pub fn set_replacement_bundle(&mut self, replacement: &Raw<AnySyncMessageLikeEvent>) -> Result {
536	use BTreeMap as Map;
537
538	type Object = Map<String, Raw<JsonValue>>;
539
540	let parse = |raw: &RawJsonValue| -> Result<Object> {
541		serde_json::from_str(raw.get())
542			.map_err(|e| err!(Database("Invalid object in pdu unsigned: {e}")))
543	};
544
545	let mut unsigned: Object = self
546		.unsigned
547		.as_ref()
548		.map(|unsigned| parse(unsigned.json()))
549		.transpose()?
550		.unwrap_or_default();
551
552	let mut relations: Object = unsigned
553		.get("m.relations")
554		.map(|relations| parse(relations.json()))
555		.transpose()?
556		.unwrap_or_default();
557
558	relations.insert("m.replace".to_owned(), replacement.cast_ref().clone());
559	unsigned.insert("m.relations".to_owned(), to_raw_value(&relations)?.into());
560	self.unsigned = Some(to_raw_value(&unsigned)?.into());
561
562	Ok(())
563}
564
565/// Inverse of `set_replacement_bundle`: excise `m.replace` from
566/// `unsigned.m.relations`, dropping `m.relations` when the excision empties
567/// it and `unsigned` when that leaves nothing.
568#[implement(Pdu)]
569pub fn remove_replacement_bundle(&mut self) -> Result {
570	remove_relation_bundle(self, "m.replace")
571}
572
573/// MSC2675/MSC3267: fold reference relations into
574/// `unsigned.m.relations.m.reference` as `{ chunk: [{ event_id }, ...] }`,
575/// preserving an existing bundle such as `m.thread` or `m.replace` and creating
576/// `unsigned` when absent.
577#[implement(Pdu)]
578pub fn set_reference_bundle(&mut self, event_ids: &[OwnedEventId]) -> Result {
579	let unsigned = self
580		.unsigned
581		.as_ref()
582		.map(|unsigned| raw_object(unsigned.json(), "unsigned"))
583		.transpose()?
584		.unwrap_or_default();
585
586	let relations = unsigned
587		.get("m.relations")
588		.copied()
589		.map(|relations| raw_object(relations, "bundled relations"))
590		.transpose()?
591		.unwrap_or_default();
592
593	let reference = ReferenceBundle { chunk: ReferenceChunk(event_ids) };
594	let relations = RawObjectPatch::new(&relations, "m.reference", reference);
595	let unsigned = RawObjectPatch::new(&unsigned, "m.relations", relations);
596	self.unsigned = Some(raw_as(&unsigned)?);
597
598	Ok(())
599}
600
601impl<'de> Deserialize<'de> for JsonString<'de> {
602	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
603	where
604		D: Deserializer<'de>,
605	{
606		deserializer.deserialize_str(JsonStringVisitor)
607	}
608}
609
610impl<'de> Visitor<'de> for JsonStringVisitor {
611	type Value = JsonString<'de>;
612
613	fn expecting(&self, formatter: &mut Formatter<'_>) -> FmtResult {
614		formatter.write_str("a JSON string")
615	}
616
617	fn visit_borrowed_str<E>(self, value: &'de str) -> Result<Self::Value, E> {
618		Ok(JsonString(Cow::Borrowed(value)))
619	}
620
621	fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
622	where
623		E: DeError,
624	{
625		Ok(JsonString(Cow::Owned(value.to_owned())))
626	}
627
628	fn visit_string<E>(self, value: String) -> Result<Self::Value, E> {
629		Ok(JsonString(Cow::Owned(value)))
630	}
631}
632
633impl<'de> DeserializeSeed<'de> for BorrowedField<'_> {
634	type Value = Option<&'de RawJsonValue>;
635
636	fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
637	where
638		D: Deserializer<'de>,
639	{
640		deserializer.deserialize_map(self)
641	}
642}
643
644impl<'de> Visitor<'de> for BorrowedField<'_> {
645	type Value = Option<&'de RawJsonValue>;
646
647	fn expecting(&self, formatter: &mut Formatter<'_>) -> FmtResult {
648		write!(formatter, "an object containing {}", self.0)
649	}
650
651	fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
652	where
653		A: MapAccess<'de>,
654	{
655		let mut value = None; // MapAccess key/value reads share the cursor.
656
657		while let Some(JsonString(field)) = map.next_key()? {
658			value = match field.as_ref() {
659				| field if field != self.0 => map.next_value().map(|_: IgnoredAny| value),
660				| _ if value.is_some() =>
661					Err(A::Error::custom(format_args!("duplicate field `{field}`"))),
662				| _ => map.next_value().map(Some),
663			}?;
664		}
665
666		Ok(value)
667	}
668}
669
670impl<'de> DeserializeSeed<'de> for UniqueObject {
671	type Value = BorrowedObject<'de>;
672
673	fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
674	where
675		D: Deserializer<'de>,
676	{
677		deserializer.deserialize_map(self)
678	}
679}
680
681impl<'de> Visitor<'de> for UniqueObject {
682	type Value = BorrowedObject<'de>;
683
684	fn expecting(&self, formatter: &mut Formatter<'_>) -> FmtResult {
685		formatter.write_str("an object without duplicate fields")
686	}
687
688	fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
689	where
690		A: MapAccess<'de>,
691	{
692		from_fn(|| map.next_entry().transpose()).try_fold(
693			BorrowedObject::new(),
694			|mut object, entry: Result<JsonEntry<'de>, A::Error>| {
695				let (JsonString(field), value) = entry?;
696
697				match object.entry(field) {
698					| Entry::Occupied(entry) =>
699						Err(A::Error::custom(format_args!("duplicate field `{}`", entry.key()))),
700					| Entry::Vacant(entry) => {
701						entry.insert(value);
702
703						Ok(object)
704					},
705				}
706			},
707		)
708	}
709}
710
711#[implement(ThreadBundleFields, generics = "<'a>", params = "<'a>")]
712fn parse(raw: &'a RawJsonValue) -> Result<Self> {
713	let child = |object: &BorrowedObject<'a>, field, name| {
714		object
715			.get(field)
716			.ok_or_else(|| {
717				err!(Database("Thread transaction probe disagreed with bundle parser"))
718			})
719			.and_then(|raw| raw_object(raw, name))
720	};
721
722	let unsigned = raw_object(raw, "unsigned")?;
723	let relations = child(&unsigned, "m.relations", "bundled relations")?;
724	let thread = child(&relations, "m.thread", "thread bundle")?;
725	let latest_event = child(&thread, "latest_event", "thread latest event")?;
726	let latest_event_unsigned = child(&latest_event, "unsigned", "thread latest event unsigned")?;
727
728	Ok(Self {
729		unsigned,
730		relations,
731		thread,
732		latest_event,
733		latest_event_unsigned,
734	})
735}
736
737#[implement(ThreadBundleFields, generics = "<'a>", params = "<'a>")]
738fn without_transaction_id<U, const N: usize>(&self) -> Result<Raw<U, N>> {
739	let latest_event_unsigned =
740		RawObjectRemove::new(&self.latest_event_unsigned, "transaction_id");
741
742	let latest_event = RawObjectPatch::new(&self.latest_event, "unsigned", latest_event_unsigned);
743	let thread = RawObjectPatch::new(&self.thread, "latest_event", latest_event);
744	let relations = RawObjectPatch::new(&self.relations, "m.thread", thread);
745	let unsigned = RawObjectPatch::new(&self.unsigned, "m.relations", relations);
746
747	raw_as(&unsigned)
748}
749
750impl<'a, 'raw, T> RawObjectPatch<'a, 'raw, T> {
751	fn new(object: &'a BorrowedObject<'raw>, field: &'static str, value: T) -> Self {
752		Self { object, field, value }
753	}
754}
755
756impl<T: Serialize> Serialize for RawObjectPatch<'_, '_, T> {
757	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
758	where
759		S: Serializer,
760	{
761		let present = self.object.contains_key(self.field);
762		let len = self
763			.object
764			.len()
765			.saturating_add(usize::from(!present));
766
767		let map = self.object.iter().try_fold(
768			serializer.serialize_map(Some(len))?,
769			|mut map, (field, value)| {
770				match field.as_ref() == self.field {
771					| true => map.serialize_entry(field, &self.value),
772					| false => map.serialize_entry(field, value),
773				}?;
774
775				Ok(map)
776			},
777		)?;
778
779		present
780			.is_false()
781			.then_some(self.field)
782			.into_iter()
783			.try_fold(map, |mut map, field| {
784				map.serialize_entry(field, &self.value)?;
785
786				Ok(map)
787			})
788			.and_then(SerializeMap::end)
789	}
790}
791
792impl<'a, 'raw> RawObjectRemove<'a, 'raw> {
793	fn new(object: &'a BorrowedObject<'raw>, field: &'a str) -> Self { Self { object, field } }
794}
795
796impl Serialize for RawObjectRemove<'_, '_> {
797	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
798	where
799		S: Serializer,
800	{
801		self.object
802			.iter()
803			.filter(|(field, _)| field.as_ref() != self.field)
804			.try_fold(
805				serializer.serialize_map(Some(self.object.len().saturating_sub(1)))?,
806				|mut map, (field, value)| {
807					map.serialize_entry(field, value)?;
808
809					Ok(map)
810				},
811			)
812			.and_then(SerializeMap::end)
813	}
814}
815
816impl Serialize for ReferenceChunk<'_> {
817	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
818	where
819		S: Serializer,
820	{
821		serializer.collect_seq(
822			self.0
823				.iter()
824				.map(|event_id| ReferenceEvent { event_id }),
825		)
826	}
827}
828
829fn raw_object<'a>(raw: &'a RawJsonValue, name: &str) -> Result<BorrowedObject<'a>> {
830	deserialize_raw(raw, UniqueObject, name)
831}
832
833fn raw_field<'a>(
834	raw: &'a RawJsonValue,
835	field: &str,
836	name: &str,
837) -> Result<Option<&'a RawJsonValue>> {
838	deserialize_raw(raw, BorrowedField(field), name)
839}
840
841fn deserialize_raw<'a, D>(raw: &'a RawJsonValue, seed: D, name: &str) -> Result<D::Value>
842where
843	D: DeserializeSeed<'a>,
844{
845	let mut deserializer = serde_json::Deserializer::from_str(raw.get());
846
847	seed.deserialize(&mut deserializer)
848		.and_then(|value| deserializer.end().map(|()| value))
849		.map_err(|error| err!(Database("Invalid {name} object in PDU event: {error}")))
850}
851
852/// Serializes `value` into raw JSON labeled as `U`.
853///
854/// `Raw<T, N>` has identical layout for every phantom `T` at fixed `N`.
855/// Callers must ensure that the serialized JSON is valid for `U`.
856#[inline]
857fn raw_as<T, U, const N: usize>(value: &T) -> Result<Raw<U, N>>
858where
859	T: Serialize,
860{
861	Raw::<T, N>::new(value)
862		.map(|raw| raw.cast_ref_unchecked::<U>().clone())
863		.map_err(Into::into)
864}
865
866#[inline]
867fn raw_of<T: Serialize>(value: &T) -> Result<Raw<JsonValue>> { raw_as(value) }