arminius_lib_sync/
sync_token.rs1#![allow(
12 clippy::allow_attributes,
13 reason = "This source file contains enums which allow `clippy::missing_docs_in_private_items`. But an exception to this lint will mistakenly report that it's unfulfilled when used with expect, even though it is not."
14)]
15
16mod tests;
17
18use std::num::ParseIntError;
19
20use arminius_lib_component::error::MatrixApiError;
21use arminius_lib_db::DatabaseError;
22use arminius_lib_db_sync::syncv4::{DbMaxStreamIds, TransactedSyncV4Queries};
23use arminius_lib_model::types::StreamId;
24use itertools::Itertools as _;
25use reqwest::StatusCode;
26use ruma::api::error::ErrorKind;
27
28#[allow(
30 missing_docs,
31 clippy::missing_docs_in_private_items,
32 reason = "TODO(#2003): This exception predates us enabling `clippy::allow_attributes_without_reason`, so we don't know why it was enabled"
33)]
34#[derive(thiserror::Error, Debug, MatrixApiError, PartialEq, Eq)]
35#[matrix_api_error(status_code = StatusCode::BAD_REQUEST, error_kind = ErrorKind::InvalidParam)]
36#[error(transparent)]
37pub enum SyncTokenParseError {
38 #[error("Missing sync token prefix")]
39 MissingSyncTokenPrefix,
40
41 #[error("Missing event stream position")]
42 MissingEventStreamPosition,
43 #[error("Could not parse event stream position: {0}")]
44 InvalidEventStreamPosition(ParseIntError),
45
46 #[error("Missing to-device stream position")]
47 MissingToDeviceStreamPosition,
48 #[error("Could not parse to-device stream position: {0}")]
49 InvalidToDeviceStreamPosition(ParseIntError),
50
51 #[error("Missing account data stream position")]
52 MissingAccountDataStreamPosition,
53 #[error("Could not parse account data stream position: {0}")]
54 InvalidAccountDataStreamPosition(ParseIntError),
55
56 #[error("Missing user keys stream position")]
57 MissingUserKeysStreamPosition,
58 #[error("Could not parse user keys stream position: {0}")]
59 InvalidUserKeysStreamPosition(ParseIntError),
60
61 #[error("Missing read receipts stream position")]
62 MissingReadReceiptsStreamPosition,
63 #[error("Could not parse read receipts stream position: {0}")]
64 InvalidReadReceiptsStreamPosition(ParseIntError),
65
66 #[error("Missing federated invites stream position")]
67 MissingFederatedInvitesStreamPosition,
68 #[error("Could not parse federated invites stream position: {0}")]
69 InvalidFederatedInvitesStreamPosition(ParseIntError),
70
71 #[error("Missing sync v4 position")]
72 MissingSyncV4Position,
73 #[error("Could not parse sync v4 position: {0}")]
74 InvalidSyncV4Position(ParseIntError),
75
76 #[error("Missing federated invites stream position")]
77 MissingFederatedKnocksStreamPosition,
78 #[error("Could not parse federated invites stream position: {0}")]
79 InvalidFederatedKnocksStreamPosition(ParseIntError),
80}
81
82const SYNC_TOKEN_PREFIX: &str = "s";
84
85#[derive(Clone, Debug, PartialEq, Eq, Default)]
90pub struct SyncToken {
91 pub event_stream_position: StreamId,
93 pub todevice_stream_position: StreamId,
95 pub account_data_stream_position: StreamId,
97 pub user_keys_stream_position: StreamId,
99 pub read_receipts_stream_position: StreamId,
101 pub federated_invites_stream_position: StreamId,
103 pub syncv4_position: StreamId,
105 pub federated_knocks_stream_position: StreamId,
107}
108
109impl SyncToken {
110 #[must_use]
116 pub fn is_after(&self, other: &Self) -> bool {
117 self.event_stream_position > other.event_stream_position
118 || self.todevice_stream_position > other.todevice_stream_position
119 || self.account_data_stream_position > other.account_data_stream_position
120 || self.user_keys_stream_position > other.user_keys_stream_position
121 || self.read_receipts_stream_position > other.read_receipts_stream_position
122 || self.federated_invites_stream_position > other.federated_invites_stream_position
123 }
124}
125
126impl TryFrom<&str> for SyncToken {
127 type Error = SyncTokenParseError;
128
129 fn try_from(input: &str) -> Result<Self, Self::Error> {
130 let mut parted = input.split_terminator("_");
131
132 if parted.next().is_none_or(|prefix| prefix != SYNC_TOKEN_PREFIX) {
133 return Err(SyncTokenParseError::MissingSyncTokenPrefix);
134 }
135
136 let event_stream_position: i64 = parted
137 .next()
138 .ok_or(SyncTokenParseError::MissingEventStreamPosition)?
139 .parse()
140 .map_err(SyncTokenParseError::InvalidEventStreamPosition)?;
141
142 let todevice_stream_position = parted
143 .next()
144 .ok_or(SyncTokenParseError::MissingToDeviceStreamPosition)?
145 .parse()
146 .map_err(SyncTokenParseError::InvalidToDeviceStreamPosition)?;
147
148 let account_data_stream_position = parted
149 .next()
150 .ok_or(SyncTokenParseError::MissingAccountDataStreamPosition)?
151 .parse()
152 .map_err(SyncTokenParseError::InvalidAccountDataStreamPosition)?;
153
154 let user_keys_stream_position = parted
155 .next()
156 .ok_or(SyncTokenParseError::MissingUserKeysStreamPosition)?
157 .parse()
158 .map_err(SyncTokenParseError::InvalidUserKeysStreamPosition)?;
159
160 let read_receipts_stream_position = parted
161 .next()
162 .ok_or(SyncTokenParseError::MissingReadReceiptsStreamPosition)?
163 .parse()
164 .map_err(SyncTokenParseError::InvalidReadReceiptsStreamPosition)?;
165
166 let federated_invites_stream_position = parted
167 .next()
168 .ok_or(SyncTokenParseError::MissingFederatedInvitesStreamPosition)?
169 .parse()
170 .map_err(SyncTokenParseError::InvalidFederatedInvitesStreamPosition)?;
171
172 let syncv4_position = parted
173 .next()
174 .ok_or(SyncTokenParseError::MissingSyncV4Position)?
175 .parse()
176 .map_err(SyncTokenParseError::InvalidSyncV4Position)?;
177
178 let federated_knocks_stream_position = parted
179 .next()
180 .ok_or(SyncTokenParseError::MissingFederatedKnocksStreamPosition)?
181 .parse()
182 .map_err(SyncTokenParseError::InvalidFederatedKnocksStreamPosition)?;
183
184 Ok(SyncToken {
185 event_stream_position,
186 todevice_stream_position,
187 account_data_stream_position,
188 user_keys_stream_position,
189 read_receipts_stream_position,
190 federated_invites_stream_position,
191 syncv4_position,
192 federated_knocks_stream_position,
193 })
194 }
195}
196
197impl From<&SyncToken> for String {
198 fn from(value: &SyncToken) -> Self {
199 let SyncToken {
200 event_stream_position,
201 todevice_stream_position,
202 account_data_stream_position,
203 user_keys_stream_position,
204 read_receipts_stream_position,
205 federated_invites_stream_position,
206 syncv4_position,
207 federated_knocks_stream_position,
208 } = value;
209
210 [
211 SYNC_TOKEN_PREFIX.to_owned(),
212 event_stream_position.to_string(),
213 todevice_stream_position.to_string(),
214 account_data_stream_position.to_string(),
215 user_keys_stream_position.to_string(),
216 read_receipts_stream_position.to_string(),
217 federated_invites_stream_position.to_string(),
218 syncv4_position.to_string(),
219 federated_knocks_stream_position.to_string(),
220 ]
221 .iter()
222 .join("_")
223 }
224}
225
226pub async fn get_current_token<T: TransactedSyncV4Queries>(
232 transaction: &mut T,
233 prev_position: Option<i64>,
234) -> Result<SyncToken, DatabaseError> {
235 let DbMaxStreamIds {
236 max_event_stream_id,
237 max_todevice_stream_id,
238 max_account_data_stream_id,
239 max_user_keys_stream_id,
240 max_read_receipts_stream_id,
241 max_federated_invites_stream_id,
242 max_federated_knocks_stream_id,
243 } = transaction.fetch_max_stream_ids().await?;
244
245 Ok(SyncToken {
246 event_stream_position: max_event_stream_id,
247 todevice_stream_position: max_todevice_stream_id,
248 account_data_stream_position: max_account_data_stream_id,
249 user_keys_stream_position: max_user_keys_stream_id,
250 read_receipts_stream_position: max_read_receipts_stream_id,
251 federated_invites_stream_position: max_federated_invites_stream_id,
252 federated_knocks_stream_position: max_federated_knocks_stream_id,
253 syncv4_position: prev_position.unwrap_or(0),
255 })
256}