1use 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#[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)] #[case(Cursor::new(2, 1u32), Cursor::new(1, 1u32), false)]
159 #[case(Cursor::new(1, 1u32), Cursor::new(1, 1u32), false)] #[case(Cursor::new(1, 1u32), Cursor::new(1, 2u32), true)] #[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}