Skip to main content

xmtp_proto/types/
topic_cursor.rs

1use std::{
2    collections::{HashMap, hash_map::Entry},
3    ops::{Deref, DerefMut},
4};
5
6use crate::types::{GlobalCursor, InstallationId, Topic};
7
8/// A cursor that keeps a [`super::GlobalCursor`] for each topic it has seen.
9#[derive(Default, Debug, PartialEq, Clone)]
10pub struct TopicCursor {
11    inner: HashMap<Topic, GlobalCursor>,
12}
13
14pub type TopicEntry<'a> = Entry<'a, Topic, GlobalCursor>;
15
16impl TopicCursor {
17    /// get the item at [`Topic`] or insert the default
18    pub fn get_or_default(&mut self, topic: &Topic) -> &GlobalCursor {
19        self.inner.entry(topic.clone()).or_default()
20    }
21
22    /// get the [`GlobalCursor`] corresponding to the [`super::TopicKind::GroupMessagesV1`]
23    /// by [`super::GroupId`]
24    pub fn get_group(&self, group_id: impl AsRef<[u8]>) -> GlobalCursor {
25        self.inner
26            .get(&Topic::new_group_message(group_id))
27            .cloned()
28            .unwrap_or_default()
29    }
30
31    /// check if this topic cursor contains [`super::GroupId`]
32    pub fn contains_group(&self, group_id: impl AsRef<[u8]>) -> bool {
33        self.inner.contains_key(&Topic::new_group_message(group_id))
34    }
35
36    /// consume this topic cursor into a list of its topics
37    pub fn into_topics(self) -> Vec<Topic> {
38        self.inner.into_keys().collect()
39    }
40
41    /// get a [`Vec`] of all [`Topic`] in this cursor
42    /// by cloning.
43    pub fn topics(&self) -> Vec<Topic> {
44        self.inner.keys().cloned().collect()
45    }
46
47    /// entry api for only [super::TopicKind::GroupMessagesV1]
48    pub fn group_entry(&mut self, group_id: impl AsRef<[u8]>) -> TopicEntry<'_> {
49        self.inner.entry(Topic::new_group_message(group_id))
50    }
51
52    /// entry api for only [super::TopicKind::IdentityUpdatesV1]
53    pub fn identity_entry(&mut self, inbox_id: impl AsRef<[u8]>) -> TopicEntry<'_> {
54        self.inner.entry(Topic::new_identity_update(inbox_id))
55    }
56
57    /// entry api for only [super::TopicKind::WelcomeMessagesV1]
58    pub fn welcome_entry(&mut self, installation_id: InstallationId) -> TopicEntry<'_> {
59        self.inner
60            .entry(Topic::new_welcome_message(installation_id))
61    }
62
63    /// entry api for only [super::TopicKind::KeyPackagesV1]
64    pub fn key_package_entry(&mut self, installation_id: InstallationId) -> TopicEntry<'_> {
65        self.inner.entry(Topic::new_key_package(installation_id))
66    }
67
68    /// iterate over all [`super::TopicKind::GroupMessagesV1`]
69    /// topics as a pair of (&[`super::GroupId`], &[`GlobalCursor`])
70    pub fn groups(&self) -> impl Iterator<Item = (&[u8], &GlobalCursor)> {
71        self.inner
72            .iter()
73            .filter_map(|(t, g)| Some((t.group_message_v1()?.identifier(), g)))
74    }
75}
76
77impl From<HashMap<Topic, GlobalCursor>> for TopicCursor {
78    fn from(inner: HashMap<Topic, GlobalCursor>) -> Self {
79        TopicCursor { inner }
80    }
81}
82
83impl FromIterator<(Topic, GlobalCursor)> for TopicCursor {
84    fn from_iter<T: IntoIterator<Item = (Topic, GlobalCursor)>>(iter: T) -> Self {
85        TopicCursor {
86            inner: HashMap::from_iter(iter),
87        }
88    }
89}
90
91impl IntoIterator for TopicCursor {
92    type Item = (Topic, GlobalCursor);
93    type IntoIter = <HashMap<Topic, GlobalCursor> as IntoIterator>::IntoIter;
94
95    fn into_iter(self) -> Self::IntoIter {
96        self.inner.into_iter()
97    }
98}
99
100impl<'a> IntoIterator for &'a TopicCursor {
101    type Item = (&'a Topic, &'a GlobalCursor);
102    type IntoIter = std::collections::hash_map::Iter<'a, Topic, GlobalCursor>;
103    fn into_iter(self) -> Self::IntoIter {
104        self.inner.iter()
105    }
106}
107
108impl<'a> IntoIterator for &'a mut TopicCursor {
109    type Item = (&'a Topic, &'a mut GlobalCursor);
110    type IntoIter = std::collections::hash_map::IterMut<'a, Topic, GlobalCursor>;
111
112    fn into_iter(self) -> Self::IntoIter {
113        self.inner.iter_mut()
114    }
115}
116
117impl Deref for TopicCursor {
118    type Target = HashMap<Topic, GlobalCursor>;
119    fn deref(&self) -> &Self::Target {
120        &self.inner
121    }
122}
123
124impl DerefMut for TopicCursor {
125    fn deref_mut(&mut self) -> &mut Self::Target {
126        &mut self.inner
127    }
128}
129
130impl TopicCursor {
131    pub fn add(&mut self, topic: Topic, cursor: GlobalCursor) {
132        self.inner.insert(topic, cursor);
133    }
134}
135
136impl std::fmt::Display for TopicCursor {
137    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138        for (topic, has_seen) in self.inner.iter() {
139            writeln!(f, "{} -> {}", topic, has_seen)?;
140        }
141        Ok(())
142    }
143}