Skip to main content

matrix_sdk_common/linked_chunk/
identifiers.rs

1// Copyright 2026 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::fmt;
16
17use ruma::{EventId, OwnedEventId, OwnedRoomId, RoomId};
18use serde::{Deserialize, Serialize};
19
20/// An identifier for a linked chunk; borrowed variant.
21#[derive(Debug, Clone, Copy, PartialEq)]
22pub enum LinkedChunkId<'a> {
23    /// A room's unthreaded timeline.
24    Room(&'a RoomId),
25    /// A room's thread.
26    Thread(&'a RoomId, &'a EventId),
27    /// A room's list of pinned events.
28    PinnedEvents(&'a RoomId),
29    /// An event-focused timeline (e.g., for permalinks).
30    EventFocused(&'a RoomId, &'a EventId),
31}
32
33impl fmt::Display for LinkedChunkId<'_> {
34    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35        match self {
36            Self::Room(room_id) => write!(f, "{room_id}"),
37            Self::Thread(room_id, thread_root) => {
38                write!(f, "{room_id}:thread:{thread_root}")
39            }
40            Self::PinnedEvents(room_id) => {
41                write!(f, "{room_id}:pinned")
42            }
43            Self::EventFocused(room_id, event_id) => {
44                write!(f, "{room_id}:event_focused:{event_id}")
45            }
46        }
47    }
48}
49
50impl LinkedChunkId<'_> {
51    pub fn storage_key(&self) -> impl '_ + AsRef<[u8]> {
52        match self {
53            LinkedChunkId::Room(room_id) => room_id.to_string(),
54            LinkedChunkId::Thread(room_id, event_id) => format!("t:{room_id}:{event_id}"),
55            LinkedChunkId::PinnedEvents(room_id) => format!("pinned:{room_id}"),
56            LinkedChunkId::EventFocused(room_id, event_id) => {
57                format!("event_focused:{room_id}:{event_id}")
58            }
59        }
60    }
61
62    pub fn to_owned(&self) -> OwnedLinkedChunkId {
63        match self {
64            LinkedChunkId::Room(room_id) => OwnedLinkedChunkId::Room((*room_id).to_owned()),
65            LinkedChunkId::Thread(room_id, event_id) => {
66                OwnedLinkedChunkId::Thread((*room_id).to_owned(), (*event_id).to_owned())
67            }
68            LinkedChunkId::PinnedEvents(room_id) => {
69                OwnedLinkedChunkId::PinnedEvents((*room_id).to_owned())
70            }
71            LinkedChunkId::EventFocused(room_id, event_id) => {
72                OwnedLinkedChunkId::EventFocused((*room_id).to_owned(), (*event_id).to_owned())
73            }
74        }
75    }
76}
77
78impl<'a> From<&'a OwnedLinkedChunkId> for LinkedChunkId<'a> {
79    fn from(value: &'a OwnedLinkedChunkId) -> Self {
80        value.as_ref()
81    }
82}
83
84impl PartialEq<&OwnedLinkedChunkId> for LinkedChunkId<'_> {
85    fn eq(&self, other: &&OwnedLinkedChunkId) -> bool {
86        match (self, other) {
87            (LinkedChunkId::Room(a), OwnedLinkedChunkId::Room(b)) => *a == b,
88            (LinkedChunkId::PinnedEvents(a), OwnedLinkedChunkId::PinnedEvents(b)) => *a == b,
89            (LinkedChunkId::Thread(r, ev), OwnedLinkedChunkId::Thread(r2, ev2)) => {
90                r == r2 && ev == ev2
91            }
92            (LinkedChunkId::EventFocused(r, ev), OwnedLinkedChunkId::EventFocused(r2, ev2)) => {
93                r == r2 && ev == ev2
94            }
95            _ => false,
96        }
97    }
98}
99
100impl PartialEq<LinkedChunkId<'_>> for OwnedLinkedChunkId {
101    fn eq(&self, other: &LinkedChunkId<'_>) -> bool {
102        other.eq(&self)
103    }
104}
105
106/// An identifier for a linked chunk; owned variant.
107#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
108pub enum OwnedLinkedChunkId {
109    Room(OwnedRoomId),
110    Thread(OwnedRoomId, OwnedEventId),
111    PinnedEvents(OwnedRoomId),
112    EventFocused(OwnedRoomId, OwnedEventId),
113}
114
115impl fmt::Display for OwnedLinkedChunkId {
116    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117        self.as_ref().fmt(f)
118    }
119}
120
121impl OwnedLinkedChunkId {
122    pub fn as_ref(&self) -> LinkedChunkId<'_> {
123        match self {
124            OwnedLinkedChunkId::Room(room_id) => LinkedChunkId::Room(room_id.as_ref()),
125            OwnedLinkedChunkId::Thread(room_id, event_id) => {
126                LinkedChunkId::Thread(room_id.as_ref(), event_id.as_ref())
127            }
128            OwnedLinkedChunkId::PinnedEvents(room_id) => {
129                LinkedChunkId::PinnedEvents(room_id.as_ref())
130            }
131            OwnedLinkedChunkId::EventFocused(room_id, event_id) => {
132                LinkedChunkId::EventFocused(room_id.as_ref(), event_id.as_ref())
133            }
134        }
135    }
136
137    pub fn room_id(&self) -> &RoomId {
138        match self {
139            OwnedLinkedChunkId::Room(room_id)
140            | OwnedLinkedChunkId::Thread(room_id, ..)
141            | OwnedLinkedChunkId::PinnedEvents(room_id, ..)
142            | OwnedLinkedChunkId::EventFocused(room_id, ..) => room_id,
143        }
144    }
145}
146
147impl From<LinkedChunkId<'_>> for OwnedLinkedChunkId {
148    fn from(value: LinkedChunkId<'_>) -> Self {
149        value.to_owned()
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use ruma::{event_id, room_id};
156
157    use super::LinkedChunkId;
158
159    #[test]
160    fn test_display() {
161        assert_eq!(LinkedChunkId::Room(room_id!("!r")).to_string(), "!r");
162        assert_eq!(
163            LinkedChunkId::Thread(room_id!("!r"), event_id!("$e")).to_string(),
164            "!r:thread:$e"
165        );
166        assert_eq!(LinkedChunkId::PinnedEvents(room_id!("!r")).to_string(), "!r:pinned");
167        assert_eq!(
168            LinkedChunkId::EventFocused(room_id!("!r"), event_id!("$e")).to_string(),
169            "!r:event_focused:$e"
170        );
171    }
172
173    #[test]
174    fn test_storage_key() {
175        assert_eq!(LinkedChunkId::Room(room_id!("!r")).storage_key().as_ref(), "!r".as_bytes());
176        assert_eq!(
177            LinkedChunkId::Thread(room_id!("!r"), event_id!("$e")).storage_key().as_ref(),
178            "t:!r:$e".as_bytes()
179        );
180        assert_eq!(
181            LinkedChunkId::PinnedEvents(room_id!("!r")).storage_key().as_ref(),
182            "pinned:!r".as_bytes()
183        );
184        assert_eq!(
185            LinkedChunkId::EventFocused(room_id!("!r"), event_id!("$e")).storage_key().as_ref(),
186            "event_focused:!r:$e".as_bytes()
187        );
188    }
189
190    #[test]
191    fn test_partial_eq() {
192        // Room.
193        assert!(LinkedChunkId::Room(room_id!("!r0")) == LinkedChunkId::Room(room_id!("!r0")));
194        assert!(LinkedChunkId::Room(room_id!("!r0")) != LinkedChunkId::Room(room_id!("!r1")));
195
196        // Thread.
197        assert!(
198            LinkedChunkId::Thread(room_id!("!r0"), event_id!("$e0"))
199                == LinkedChunkId::Thread(room_id!("!r0"), event_id!("$e0"))
200        );
201        assert!(
202            LinkedChunkId::Thread(room_id!("!r0"), event_id!("$e0"))
203                != LinkedChunkId::Thread(room_id!("!r1"), event_id!("$e0"))
204        );
205        assert!(
206            LinkedChunkId::Thread(room_id!("!r0"), event_id!("$e0"))
207                != LinkedChunkId::Thread(room_id!("!r0"), event_id!("$e1"))
208        );
209
210        // PinnedEvents.
211        assert!(
212            LinkedChunkId::PinnedEvents(room_id!("!r0"))
213                == LinkedChunkId::PinnedEvents(room_id!("!r0"))
214        );
215        assert!(
216            LinkedChunkId::PinnedEvents(room_id!("!r0"))
217                != LinkedChunkId::PinnedEvents(room_id!("!r1"))
218        );
219
220        // EventFocused.
221        assert!(
222            LinkedChunkId::EventFocused(room_id!("!r0"), event_id!("$e0"))
223                == LinkedChunkId::EventFocused(room_id!("!r0"), event_id!("$e0"))
224        );
225        assert!(
226            LinkedChunkId::EventFocused(room_id!("!r0"), event_id!("$e0"))
227                != LinkedChunkId::EventFocused(room_id!("!r1"), event_id!("$e0"))
228        );
229        assert!(
230            LinkedChunkId::EventFocused(room_id!("!r0"), event_id!("$e0"))
231                != LinkedChunkId::EventFocused(room_id!("!r0"), event_id!("$e1"))
232        );
233    }
234}