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 room_id(&self) -> &RoomId {
63        match self {
64            Self::Room(room_id)
65            | Self::Thread(room_id, ..)
66            | Self::PinnedEvents(room_id, ..)
67            | Self::EventFocused(room_id, ..) => room_id,
68        }
69    }
70
71    pub fn to_owned(&self) -> OwnedLinkedChunkId {
72        match self {
73            LinkedChunkId::Room(room_id) => OwnedLinkedChunkId::Room((*room_id).to_owned()),
74            LinkedChunkId::Thread(room_id, event_id) => {
75                OwnedLinkedChunkId::Thread((*room_id).to_owned(), (*event_id).to_owned())
76            }
77            LinkedChunkId::PinnedEvents(room_id) => {
78                OwnedLinkedChunkId::PinnedEvents((*room_id).to_owned())
79            }
80            LinkedChunkId::EventFocused(room_id, event_id) => {
81                OwnedLinkedChunkId::EventFocused((*room_id).to_owned(), (*event_id).to_owned())
82            }
83        }
84    }
85}
86
87impl<'a> From<&'a OwnedLinkedChunkId> for LinkedChunkId<'a> {
88    fn from(value: &'a OwnedLinkedChunkId) -> Self {
89        value.as_ref()
90    }
91}
92
93impl PartialEq<&OwnedLinkedChunkId> for LinkedChunkId<'_> {
94    fn eq(&self, other: &&OwnedLinkedChunkId) -> bool {
95        match (self, other) {
96            (LinkedChunkId::Room(a), OwnedLinkedChunkId::Room(b)) => *a == b,
97            (LinkedChunkId::PinnedEvents(a), OwnedLinkedChunkId::PinnedEvents(b)) => *a == b,
98            (LinkedChunkId::Thread(r, ev), OwnedLinkedChunkId::Thread(r2, ev2)) => {
99                r == r2 && ev == ev2
100            }
101            (LinkedChunkId::EventFocused(r, ev), OwnedLinkedChunkId::EventFocused(r2, ev2)) => {
102                r == r2 && ev == ev2
103            }
104            _ => false,
105        }
106    }
107}
108
109impl PartialEq<LinkedChunkId<'_>> for OwnedLinkedChunkId {
110    fn eq(&self, other: &LinkedChunkId<'_>) -> bool {
111        other.eq(&self)
112    }
113}
114
115/// An identifier for a linked chunk; owned variant.
116#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
117pub enum OwnedLinkedChunkId {
118    Room(OwnedRoomId),
119    Thread(OwnedRoomId, OwnedEventId),
120    PinnedEvents(OwnedRoomId),
121    EventFocused(OwnedRoomId, OwnedEventId),
122}
123
124impl fmt::Display for OwnedLinkedChunkId {
125    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126        self.as_ref().fmt(f)
127    }
128}
129
130impl OwnedLinkedChunkId {
131    pub fn as_ref(&self) -> LinkedChunkId<'_> {
132        match self {
133            OwnedLinkedChunkId::Room(room_id) => LinkedChunkId::Room(room_id.as_ref()),
134            OwnedLinkedChunkId::Thread(room_id, event_id) => {
135                LinkedChunkId::Thread(room_id.as_ref(), event_id.as_ref())
136            }
137            OwnedLinkedChunkId::PinnedEvents(room_id) => {
138                LinkedChunkId::PinnedEvents(room_id.as_ref())
139            }
140            OwnedLinkedChunkId::EventFocused(room_id, event_id) => {
141                LinkedChunkId::EventFocused(room_id.as_ref(), event_id.as_ref())
142            }
143        }
144    }
145
146    pub fn room_id(&self) -> &RoomId {
147        match self {
148            OwnedLinkedChunkId::Room(room_id)
149            | OwnedLinkedChunkId::Thread(room_id, ..)
150            | OwnedLinkedChunkId::PinnedEvents(room_id, ..)
151            | OwnedLinkedChunkId::EventFocused(room_id, ..) => room_id,
152        }
153    }
154}
155
156impl From<LinkedChunkId<'_>> for OwnedLinkedChunkId {
157    fn from(value: LinkedChunkId<'_>) -> Self {
158        value.to_owned()
159    }
160}
161
162#[cfg(test)]
163mod tests {
164    use ruma::{event_id, room_id};
165
166    use super::LinkedChunkId;
167
168    #[test]
169    fn test_display() {
170        assert_eq!(LinkedChunkId::Room(room_id!("!r")).to_string(), "!r");
171        assert_eq!(
172            LinkedChunkId::Thread(room_id!("!r"), event_id!("$e")).to_string(),
173            "!r:thread:$e"
174        );
175        assert_eq!(LinkedChunkId::PinnedEvents(room_id!("!r")).to_string(), "!r:pinned");
176        assert_eq!(
177            LinkedChunkId::EventFocused(room_id!("!r"), event_id!("$e")).to_string(),
178            "!r:event_focused:$e"
179        );
180    }
181
182    #[test]
183    fn test_storage_key() {
184        assert_eq!(LinkedChunkId::Room(room_id!("!r")).storage_key().as_ref(), "!r".as_bytes());
185        assert_eq!(
186            LinkedChunkId::Thread(room_id!("!r"), event_id!("$e")).storage_key().as_ref(),
187            "t:!r:$e".as_bytes()
188        );
189        assert_eq!(
190            LinkedChunkId::PinnedEvents(room_id!("!r")).storage_key().as_ref(),
191            "pinned:!r".as_bytes()
192        );
193        assert_eq!(
194            LinkedChunkId::EventFocused(room_id!("!r"), event_id!("$e")).storage_key().as_ref(),
195            "event_focused:!r:$e".as_bytes()
196        );
197    }
198
199    #[test]
200    fn test_partial_eq() {
201        // Room.
202        assert!(LinkedChunkId::Room(room_id!("!r0")) == LinkedChunkId::Room(room_id!("!r0")));
203        assert!(LinkedChunkId::Room(room_id!("!r0")) != LinkedChunkId::Room(room_id!("!r1")));
204
205        // Thread.
206        assert!(
207            LinkedChunkId::Thread(room_id!("!r0"), event_id!("$e0"))
208                == LinkedChunkId::Thread(room_id!("!r0"), event_id!("$e0"))
209        );
210        assert!(
211            LinkedChunkId::Thread(room_id!("!r0"), event_id!("$e0"))
212                != LinkedChunkId::Thread(room_id!("!r1"), event_id!("$e0"))
213        );
214        assert!(
215            LinkedChunkId::Thread(room_id!("!r0"), event_id!("$e0"))
216                != LinkedChunkId::Thread(room_id!("!r0"), event_id!("$e1"))
217        );
218
219        // PinnedEvents.
220        assert!(
221            LinkedChunkId::PinnedEvents(room_id!("!r0"))
222                == LinkedChunkId::PinnedEvents(room_id!("!r0"))
223        );
224        assert!(
225            LinkedChunkId::PinnedEvents(room_id!("!r0"))
226                != LinkedChunkId::PinnedEvents(room_id!("!r1"))
227        );
228
229        // EventFocused.
230        assert!(
231            LinkedChunkId::EventFocused(room_id!("!r0"), event_id!("$e0"))
232                == LinkedChunkId::EventFocused(room_id!("!r0"), event_id!("$e0"))
233        );
234        assert!(
235            LinkedChunkId::EventFocused(room_id!("!r0"), event_id!("$e0"))
236                != LinkedChunkId::EventFocused(room_id!("!r1"), event_id!("$e0"))
237        );
238        assert!(
239            LinkedChunkId::EventFocused(room_id!("!r0"), event_id!("$e0"))
240                != LinkedChunkId::EventFocused(room_id!("!r0"), event_id!("$e1"))
241        );
242    }
243}