Skip to main content

arminius_lib_component/
message_token.rs

1// SPDX-FileCopyrightText: 2025 BWI GmbH
2//
3// SPDX-License-Identifier: Apache-2.0
4
5//! Types to represent positions for the /messages endpoint
6
7#![allow(
8	clippy::allow_attributes,
9	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."
10)]
11
12mod tests;
13
14use axum::http::StatusCode;
15use ruma::{OwnedEventId, api::error::ErrorKind};
16use serde::{Deserialize, Serialize};
17
18use crate::error::MatrixApiError;
19
20/// Prefix for messages token
21const MESSAGES_TOKEN_PREFIX: &str = "m_";
22
23/// Error that occurs when parsing a messages token
24#[derive(
25	thiserror::Error, Debug, MatrixApiError, PartialEq, Eq, serde::Serialize, serde::Deserialize,
26)]
27#[matrix_api_error(status_code = StatusCode::BAD_REQUEST, error_kind = ErrorKind::InvalidParam, component_crate_root = crate)]
28#[error(transparent)]
29#[allow(
30	clippy::missing_docs_in_private_items,
31	missing_docs,
32	reason = "TODO(#2003): This exception predates us enabling `clippy::allow_attributes_without_reason`, so we don't know why it was enabled"
33)]
34pub enum MessageTokenParseError {
35	#[error("Missing messages token prefix")]
36	MissingMessagesTokenPrefix,
37	#[error("Missing event stream position")]
38	MissingEventStreamPosition,
39	#[error("Could not parse event stream position")]
40	InvalidEventStreamPosition,
41}
42
43/// Token representing position in the `/messages` endpoint
44///
45/// Consists of an event ID. Contains the last message sent in the last request
46/// We expect the next `/messages` response, *not* to include this event ID
47#[derive(Clone, Debug, Serialize, Deserialize)]
48pub struct MessagesToken(pub OwnedEventId);
49
50impl TryFrom<&str> for MessagesToken {
51	type Error = MessageTokenParseError;
52
53	fn try_from(input: &str) -> Result<Self, Self::Error> {
54		if input.get(0..MESSAGES_TOKEN_PREFIX.len()) != Some(MESSAGES_TOKEN_PREFIX) {
55			return Err(MessageTokenParseError::MissingMessagesTokenPrefix);
56		}
57
58		let event_stream_position: OwnedEventId = input
59			.get(MESSAGES_TOKEN_PREFIX.len()..)
60			.ok_or(MessageTokenParseError::MissingEventStreamPosition)?
61			.parse()
62			.map_err(|_| MessageTokenParseError::InvalidEventStreamPosition)?;
63
64		Ok(MessagesToken(event_stream_position))
65	}
66}
67
68impl From<&MessagesToken> for String {
69	fn from(value: &MessagesToken) -> Self {
70		let MessagesToken(event_stream_position) = value;
71		format!("{MESSAGES_TOKEN_PREFIX}{event_stream_position}")
72	}
73}