xmtp_proto/types/
cursor.rs

1//! xmtp message cursor type and implementations
2use serde::{Deserialize, Serialize};
3use std::iter::Once;
4use std::{collections::HashMap, fmt};
5use xmtp_configuration::Originators;
6
7use crate::types::{OriginatorId, SequenceId};
8use crate::xmtp::xmtpv4;
9
10/// XMTP cursor type
11/// represents a position in an ordered sequence of messages, belonging
12// force use of the `new` constructor w/ non_exhaustive
13// so we retain some control of the internal structure/use of this type
14// and disallow ad-hoc construction
15// while still allowing access to fields with `.field` notation
16#[non_exhaustive]
17#[derive(
18    Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
19)]
20pub struct Cursor {
21    pub sequence_id: super::SequenceId,
22    pub originator_id: super::OriginatorId,
23}
24
25impl Cursor {
26    pub fn new<O: Into<u32>>(sequence_id: u64, originator_id: O) -> Self {
27        Self {
28            sequence_id,
29            originator_id: originator_id.into(),
30        }
31    }
32
33    pub const fn commit_log(sequence_id: u64) -> Self {
34        Self {
35            sequence_id,
36            originator_id: Originators::REMOTE_COMMIT_LOG,
37        }
38    }
39
40    pub const fn v3_welcomes(sequence_id: u64) -> Self {
41        Self {
42            sequence_id,
43            originator_id: Originators::WELCOME_MESSAGES,
44        }
45    }
46
47    pub const fn v3_messages(sequence_id: u64) -> Self {
48        Self {
49            sequence_id,
50            originator_id: Originators::APPLICATION_MESSAGES,
51        }
52    }
53
54    pub const fn installations(sequence_id: u64) -> Self {
55        Self {
56            sequence_id,
57            originator_id: Originators::INSTALLATIONS,
58        }
59    }
60
61    pub const fn mls_commits(sequence_id: u64) -> Self {
62        Self {
63            sequence_id,
64            originator_id: Originators::MLS_COMMITS,
65        }
66    }
67
68    pub const fn inbox_log(sequence_id: u64) -> Self {
69        Self {
70            sequence_id,
71            originator_id: Originators::INBOX_LOG,
72        }
73    }
74}
75
76impl fmt::Display for Cursor {
77    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78        write!(
79            f,
80            "[sid({:6}):oid({:3})]",
81            self.sequence_id, self.originator_id
82        )
83    }
84}
85
86#[cfg(any(test, feature = "test-utils"))]
87impl xmtp_common::Generate for Cursor {
88    fn generate() -> Self {
89        Cursor {
90            sequence_id: xmtp_common::rand_u64(),
91            originator_id: openmls::test_utils::random_u32(),
92        }
93    }
94}
95
96impl From<Cursor> for xmtpv4::envelopes::Cursor {
97    fn from(value: Cursor) -> Self {
98        let mut map = HashMap::new();
99        map.insert(value.originator_id, value.sequence_id);
100        xmtpv4::envelopes::Cursor {
101            node_id_to_sequence_id: map,
102        }
103    }
104}
105
106impl<'a> IntoIterator for &'a Cursor {
107    type Item = (&'a OriginatorId, &'a SequenceId);
108    type IntoIter = Once<(&'a OriginatorId, &'a SequenceId)>;
109
110    fn into_iter(self) -> Self::IntoIter {
111        std::iter::once((&self.originator_id, &self.sequence_id))
112    }
113}
114
115impl<'a> IntoIterator for &'a mut Cursor {
116    type Item = (&'a mut OriginatorId, &'a mut SequenceId);
117    type IntoIter = Once<(&'a mut OriginatorId, &'a mut SequenceId)>;
118
119    fn into_iter(self) -> Self::IntoIter {
120        std::iter::once((&mut self.originator_id, &mut self.sequence_id))
121    }
122}
123
124impl IntoIterator for Cursor {
125    type Item = (OriginatorId, SequenceId);
126    type IntoIter = Once<(OriginatorId, SequenceId)>;
127
128    fn into_iter(self) -> Self::IntoIter {
129        std::iter::once((self.originator_id, self.sequence_id))
130    }
131}
132
133#[cfg(test)]
134mod test {
135    use rstest::rstest;
136
137    use super::*;
138
139    #[rstest]
140    #[case(Cursor::commit_log(100), 100, Originators::REMOTE_COMMIT_LOG)]
141    #[case(Cursor::v3_welcomes(200), 200, Originators::WELCOME_MESSAGES)]
142    #[case(Cursor::v3_messages(300), 300, Originators::APPLICATION_MESSAGES)]
143    #[case(Cursor::installations(400), 400, Originators::INSTALLATIONS)]
144    #[case(Cursor::mls_commits(500), 500, Originators::MLS_COMMITS)]
145    #[case(Cursor::inbox_log(600), 600, Originators::INBOX_LOG)]
146    #[xmtp_common::test]
147    async fn test_originator_constructors(
148        #[case] cursor: Cursor,
149        #[case] expected_seq: u64,
150        #[case] expected_orig: u32,
151    ) {
152        assert_eq!(cursor.sequence_id, expected_seq);
153        assert_eq!(cursor.originator_id, expected_orig);
154    }
155
156    #[rstest]
157    #[case(Cursor::new(1, 1u32), Cursor::new(2, 1u32), true)] // same originator, different seq
158    #[case(Cursor::new(2, 1u32), Cursor::new(1, 1u32), false)]
159    #[case(Cursor::new(1, 1u32), Cursor::new(1, 1u32), false)] // equal
160    #[case(Cursor::new(1, 1u32), Cursor::new(1, 2u32), true)] // different originators
161    #[xmtp_common::test]
162    async fn test_ordering(
163        #[case] cursor1: Cursor,
164        #[case] cursor2: Cursor,
165        #[case] cursor1_less: bool,
166    ) {
167        assert_eq!(cursor1 < cursor2, cursor1_less);
168        assert_eq!(cursor1 == cursor2, !cursor1_less && cursor2 >= cursor1);
169    }
170}