Skip to main content

arminius_lib_sync/syncv4/
transitions.rs

1// SPDX-FileCopyrightText: 2025 BWI GmbH
2//
3// SPDX-License-Identifier: Apache-2.0
4
5//! The syncv4 handler is organized around a different series of states.
6//!
7//! Here, we collect all transitions between states, to give a nice overview of the flow of a syncv4
8//! request.
9
10use arminius_lib_db::{Database as _, DefaultDb, TransactedDb};
11use arminius_lib_db_sync::syncv4::TransactedSyncV4Queries;
12use ruma::api::client::sync::sync_events;
13
14use crate::syncv4::{connect, error::SyncV4Error, execute, gather, plan};
15
16/// This is the state machine handling sync v4 requests.
17///
18/// It contains a database transaction to be used throughout the whole request handling, as well as
19/// the per-stage data.
20#[derive(Debug)]
21pub struct SyncV4Handler<T: TransactedSyncV4Queries, S> {
22	/// The database transaction to be used throughout the whole request handling.
23	transaction: T,
24	/// The data associated to the stage the handler is currently in.
25	stage: S,
26}
27
28impl<'a> SyncV4Handler<TransactedDb<'a>, connect::ResolvedConnection> {
29	/// Connects a [SyncV4Handler] to the database (by starting a transaction) and to the user's
30	/// connection.
31	///
32	/// This is the start of handling a sync v4 request.
33	pub async fn connect(
34		database: &'a mut DefaultDb,
35		key: connect::ConnectionKey,
36		pos: Option<String>,
37	) -> Result<SyncV4Handler<TransactedDb<'a>, connect::ResolvedConnection>, SyncV4Error> {
38		let mut transaction = database.begin_transaction().await?;
39
40		let stage = connect::resolve(&mut transaction, key, pos).await?;
41
42		Ok(SyncV4Handler { transaction, stage })
43	}
44	// TODO: Locking function ?
45}
46
47impl<T: TransactedSyncV4Queries> SyncV4Handler<T, connect::ResolvedConnection> {
48	/// Gather everything we'll need to handle the current request.
49	///
50	/// This is the second step of handling a sync v4 request.
51	pub async fn gather(self) -> Result<SyncV4Handler<T, gather::SyncV4Inputs>, SyncV4Error> {
52		let SyncV4Handler { mut transaction, stage: connection } = self;
53
54		let stage = gather::gather(&mut transaction, connection).await?;
55
56		Ok(SyncV4Handler { transaction, stage })
57	}
58}
59
60impl<T: TransactedSyncV4Queries> SyncV4Handler<T, gather::SyncV4Inputs> {
61	/// Plan the current sync v4 request handling.
62	///
63	/// This is the core of sync v4 where we compute which data will be part of the current sync (if
64	/// any) by applying the request to the user's rooms, as well as the per-connection state.
65	///
66	/// This is the third step of handling a sync v4 request, in which all the core logic occurs.
67	pub fn plan(
68		self,
69		request: &sync_events::v5::Request,
70	) -> Result<SyncV4Handler<T, plan::SyncV4Plan>, SyncV4Error> {
71		let SyncV4Handler { transaction, stage: inputs } = self;
72
73		let stage = plan::plan(inputs, request)?;
74
75		Ok(SyncV4Handler { transaction, stage })
76	}
77}
78
79impl<T: TransactedSyncV4Queries> SyncV4Handler<T, plan::SyncV4Plan> {
80	/// Tells whether the computed plan actually contains data to sync or not.
81	pub fn has_data_to_sync(&self) -> bool {
82		self.stage.has_data_to_sync()
83	}
84}
85
86impl SyncV4Handler<TransactedDb<'_>, plan::SyncV4Plan> {
87	/// Execute the computed [plan::SyncV4Plan].
88	///
89	/// This will compute the actual content of the sync response, as well as create a new position
90	/// in the per-connection state which will store what the current request produced.
91	///
92	/// NOTE: This is to be ran when [plan::SyncV4Plan::has_data_to_sync] returns `true`.
93	///
94	/// This is the final step of handling a sync v4 request.
95	pub(crate) async fn execute(self) -> Result<execute::Persisted, SyncV4Error> {
96		let SyncV4Handler { transaction, stage: plan } = self;
97
98		execute::execute(transaction, plan).await
99	}
100
101	/// Execute the computed [plan::SyncV4Plan] to produce an empty response.
102	///
103	/// This will simply update the previous position's state if needs be.
104	///
105	/// NOTE: This is to be ran when [plan::SyncV4Plan::has_data_to_sync] returns `false`.
106	///
107	/// This is the final step of handling a sync v4 request.
108	pub(crate) async fn persist_without_position(self) -> Result<execute::Persisted, SyncV4Error> {
109		let SyncV4Handler { transaction, stage: plan } = self;
110
111		execute::persist_without_position(transaction, plan).await
112	}
113}