Skip to main content

arminius_lib_room_service_messages/
event.rs

1// SPDX-FileCopyrightText: 2025 BWI GmbH
2//
3// SPDX-License-Identifier: Apache-2.0
4
5//! Facts the room service publishes about what happened in a room.
6//!
7//! These state what occurred; they say nothing about who should be told. A
8//! consumer decides that for itself — the federation output service works out
9//! which homeservers to deliver to, and the sync service will read the same
10//! facts without the room service changing again.
11
12use arminius_lib_message_bus::Event;
13use ruma::{
14	MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, OwnedServerName, OwnedUserId,
15	events::receipt::{ReceiptThread, ReceiptType},
16};
17use serde::{Deserialize, Serialize};
18use serde_json::value::RawValue;
19
20use crate::{DedupKey, Deduplicate, KEY_SEPARATOR};
21
22/// Where a room event came from.
23#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
24pub enum EventProvenance {
25	/// Created on this homeserver.
26	Local,
27	/// Received in a transaction from `origin`.
28	Federated {
29		/// The homeserver that sent it to us.
30		origin: OwnedServerName,
31	},
32	/// Submitted to us via `/send_join` and friends. The sender's homeserver
33	/// does not yet know enough about the room to distribute the event, so we
34	/// are the resident server and delivery falls to us.
35	ResidentServerFor {
36		/// The homeserver on whose behalf we are acting.
37		origin: OwnedServerName,
38	},
39}
40
41/// An event was added to a room's timeline.
42#[derive(Debug, Clone, Serialize, Deserialize, Event)]
43#[subject(name = "room-service.events.room_event_added", version = "1")]
44pub struct RoomEventAdded {
45	/// The room the event belongs to.
46	pub room_id: OwnedRoomId,
47	/// The event's ID.
48	pub event_id: OwnedEventId,
49	/// The event itself, as canonical JSON.
50	pub pdu: Box<RawValue>,
51	/// Where the event came from.
52	pub provenance: EventProvenance,
53	/// The homeservers with a member in the room at this event, including our
54	/// own.
55	pub member_servers: Vec<OwnedServerName>,
56}
57
58impl Deduplicate for RoomEventAdded {
59	/// A room event is uniquely identified by the event id.
60	fn dedup_key(&self) -> DedupKey {
61		DedupKey(self.event_id.to_string())
62	}
63}
64
65/// A user's read receipt was recorded in a room.
66#[derive(Debug, Clone, Serialize, Deserialize, Event)]
67#[subject(name = "room-service.events.read_receipt_added", version = "1")]
68pub struct ReadReceiptAdded {
69	/// The room the receipt was recorded in.
70	pub room_id: OwnedRoomId,
71	/// The user the receipt belongs to.
72	pub user_id: OwnedUserId,
73	/// The event acknowledged "up to".
74	pub event_id: OwnedEventId,
75	/// Whether the receipt is public or private.
76	/// Only public receipts are federated.
77	pub receipt_type: ReceiptType,
78	/// The thread the receipt applies to.
79	///
80	/// [`ReceiptThread::Unthreaded`] has no string form and serialises as
81	/// `null`, which its own `Deserialize` rejects. The unthreaded case
82	/// therefore has to travel as the field's absence, which is the same shape
83	/// ruma gives `Receipt::thread`.
84	#[serde(default, skip_serializing_if = "ruma::serde::is_default")]
85	pub thread: ReceiptThread,
86	/// When the receipt was made.
87	pub ts: MilliSecondsSinceUnixEpoch,
88	/// Where the receipt came from.
89	pub provenance: EventProvenance,
90	/// The homeservers with a member in the room, including our own.
91	pub member_servers: Vec<OwnedServerName>,
92}
93
94impl Deduplicate for ReadReceiptAdded {
95	/// A receipt has no unique ID of its own. Two receipts agreeing on all of
96	/// these describe the same receipt state.
97	fn dedup_key(&self) -> DedupKey {
98		DedupKey(
99			[
100				self.room_id.as_str(),
101				self.user_id.as_str(),
102				self.receipt_type.as_str(),
103				// `Unthreaded` has no string form. Empty is unambiguous here:
104				// every other variant is either `main` or an event ID.
105				self.thread.as_str().unwrap_or_default(),
106				self.event_id.as_str(),
107			]
108			.join(&KEY_SEPARATOR.to_string()),
109		)
110	}
111}