1use crate::{Envelope, PeerId};
2use anyhow::{Context as _, Result};
3use serde::Serialize;
4use std::{
5 any::{Any, TypeId},
6 cmp,
7 fmt::{self, Debug},
8 path::{Path, PathBuf},
9 sync::Arc,
10};
11use std::{marker::PhantomData, time::Instant};
12
13pub trait EnvelopedMessage: Clone + Debug + Serialize + Sized + Send + Sync + 'static {
14 const NAME: &'static str;
15 const PRIORITY: MessagePriority;
16 fn into_envelope(
17 self,
18 id: u32,
19 responding_to: Option<u32>,
20 original_sender_id: Option<PeerId>,
21 ) -> Envelope;
22 fn from_envelope(envelope: Envelope) -> Option<Self>;
23}
24
25pub trait EntityMessage: EnvelopedMessage {
26 type Entity;
27 fn remote_entity_id(&self) -> u64;
28}
29
30pub trait RequestMessage: EnvelopedMessage {
31 type Response: EnvelopedMessage;
32}
33
34pub trait AnyTypedEnvelope: Any + Send + Sync {
35 fn payload_type_id(&self) -> TypeId;
36 fn payload_type_name(&self) -> &'static str;
37 fn into_any(self: Box<Self>) -> Box<dyn Any + Send + Sync>;
38 fn is_background(&self) -> bool;
39 fn original_sender_id(&self) -> Option<PeerId>;
40 fn sender_id(&self) -> PeerId;
41 fn message_id(&self) -> u32;
42}
43
44pub enum MessagePriority {
45 Foreground,
46 Background,
47}
48
49impl<T: EnvelopedMessage> AnyTypedEnvelope for TypedEnvelope<T> {
50 fn payload_type_id(&self) -> TypeId {
51 TypeId::of::<T>()
52 }
53
54 fn payload_type_name(&self) -> &'static str {
55 T::NAME
56 }
57
58 fn into_any(self: Box<Self>) -> Box<dyn Any + Send + Sync> {
59 self
60 }
61
62 fn is_background(&self) -> bool {
63 matches!(T::PRIORITY, MessagePriority::Background)
64 }
65
66 fn original_sender_id(&self) -> Option<PeerId> {
67 self.original_sender_id
68 }
69
70 fn sender_id(&self) -> PeerId {
71 self.sender_id
72 }
73
74 fn message_id(&self) -> u32 {
75 self.message_id
76 }
77}
78
79impl PeerId {
80 pub fn from_u64(peer_id: u64) -> Self {
81 let owner_id = (peer_id >> 32) as u32;
82 let id = peer_id as u32;
83 Self { owner_id, id }
84 }
85
86 pub fn as_u64(self) -> u64 {
87 ((self.owner_id as u64) << 32) | (self.id as u64)
88 }
89}
90
91impl Copy for PeerId {}
92
93impl Eq for PeerId {}
94
95impl Ord for PeerId {
96 fn cmp(&self, other: &Self) -> cmp::Ordering {
97 self.owner_id
98 .cmp(&other.owner_id)
99 .then_with(|| self.id.cmp(&other.id))
100 }
101}
102
103impl PartialOrd for PeerId {
104 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
105 Some(self.cmp(other))
106 }
107}
108
109impl std::hash::Hash for PeerId {
110 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
111 self.owner_id.hash(state);
112 self.id.hash(state);
113 }
114}
115
116impl fmt::Display for PeerId {
117 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118 write!(f, "{}/{}", self.owner_id, self.id)
119 }
120}
121
122pub trait FromProto {
123 fn from_proto(proto: String) -> Self;
124}
125
126pub trait ToProto {
127 fn to_proto(self) -> String;
128}
129
130#[inline]
131fn from_proto_path(proto: String) -> PathBuf {
132 #[cfg(target_os = "windows")]
133 let proto = proto.replace('/', "\\");
134
135 PathBuf::from(proto)
136}
137
138#[inline]
139fn to_proto_path(path: &Path) -> String {
140 #[cfg(target_os = "windows")]
141 let proto = path.to_string_lossy().replace('\\', "/");
142
143 #[cfg(not(target_os = "windows"))]
144 let proto = path.to_string_lossy().to_string();
145
146 proto
147}
148
149impl FromProto for PathBuf {
150 fn from_proto(proto: String) -> Self {
151 from_proto_path(proto)
152 }
153}
154
155impl FromProto for Arc<Path> {
156 fn from_proto(proto: String) -> Self {
157 from_proto_path(proto).into()
158 }
159}
160
161impl ToProto for PathBuf {
162 fn to_proto(self) -> String {
163 to_proto_path(&self)
164 }
165}
166
167impl ToProto for &Path {
168 fn to_proto(self) -> String {
169 to_proto_path(self)
170 }
171}
172
173pub struct Receipt<T> {
174 pub sender_id: PeerId,
175 pub message_id: u32,
176 payload_type: PhantomData<T>,
177}
178
179impl<T> Clone for Receipt<T> {
180 fn clone(&self) -> Self {
181 *self
182 }
183}
184
185impl<T> Copy for Receipt<T> {}
186
187#[derive(Clone, Debug)]
188pub struct TypedEnvelope<T> {
189 pub sender_id: PeerId,
190 pub original_sender_id: Option<PeerId>,
191 pub message_id: u32,
192 pub payload: T,
193 pub received_at: Instant,
194}
195
196impl<T> TypedEnvelope<T> {
197 pub fn original_sender_id(&self) -> Result<PeerId> {
198 self.original_sender_id
199 .context("missing original_sender_id")
200 }
201}
202
203impl<T: RequestMessage> TypedEnvelope<T> {
204 pub fn receipt(&self) -> Receipt<T> {
205 Receipt {
206 sender_id: self.sender_id,
207 message_id: self.message_id,
208 payload_type: PhantomData,
209 }
210 }
211}
212
213#[cfg(test)]
214mod tests {
215 use typed_path::{UnixPath, UnixPathBuf, WindowsPath, WindowsPathBuf};
216
217 fn windows_path_from_proto(proto: String) -> WindowsPathBuf {
218 let proto = proto.replace('/', "\\");
219 WindowsPathBuf::from(proto)
220 }
221
222 fn unix_path_from_proto(proto: String) -> UnixPathBuf {
223 UnixPathBuf::from(proto)
224 }
225
226 fn windows_path_to_proto(path: &WindowsPath) -> String {
227 path.to_string_lossy().replace('\\', "/")
228 }
229
230 fn unix_path_to_proto(path: &UnixPath) -> String {
231 path.to_string_lossy().to_string()
232 }
233
234 #[test]
235 fn test_path_proto_interop() {
236 const WINDOWS_PATHS: &[&str] = &[
237 "C:\\Users\\User\\Documents\\file.txt",
238 "C:/Program Files/App/app.exe",
239 "projects\\zed\\crates\\proto\\src\\typed_envelope.rs",
240 "projects/my project/src/main.rs",
241 ];
242 const UNIX_PATHS: &[&str] = &[
243 "/home/user/documents/file.txt",
244 "/usr/local/bin/my app/app",
245 "projects/zed/crates/proto/src/typed_envelope.rs",
246 "projects/my project/src/main.rs",
247 ];
248
249 // Windows path to proto and back
250 for &windows_path_str in WINDOWS_PATHS {
251 let windows_path = WindowsPathBuf::from(windows_path_str);
252 let proto = windows_path_to_proto(&windows_path);
253 let recovered_path = windows_path_from_proto(proto);
254 assert_eq!(windows_path, recovered_path);
255 assert_eq!(
256 recovered_path.to_string_lossy(),
257 windows_path_str.replace('/', "\\")
258 );
259 }
260 // Unix path to proto and back
261 for &unix_path_str in UNIX_PATHS {
262 let unix_path = UnixPathBuf::from(unix_path_str);
263 let proto = unix_path_to_proto(&unix_path);
264 let recovered_path = unix_path_from_proto(proto);
265 assert_eq!(unix_path, recovered_path);
266 assert_eq!(recovered_path.to_string_lossy(), unix_path_str);
267 }
268 // Windows host, Unix client, host sends Windows path to client
269 for &windows_path_str in WINDOWS_PATHS {
270 let windows_host_path = WindowsPathBuf::from(windows_path_str);
271 let proto = windows_path_to_proto(&windows_host_path);
272 let unix_client_received_path = unix_path_from_proto(proto);
273 let proto = unix_path_to_proto(&unix_client_received_path);
274 let windows_host_recovered_path = windows_path_from_proto(proto);
275 assert_eq!(windows_host_path, windows_host_recovered_path);
276 assert_eq!(
277 windows_host_recovered_path.to_string_lossy(),
278 windows_path_str.replace('/', "\\")
279 );
280 }
281 // Unix host, Windows client, host sends Unix path to client
282 for &unix_path_str in UNIX_PATHS {
283 let unix_host_path = UnixPathBuf::from(unix_path_str);
284 let proto = unix_path_to_proto(&unix_host_path);
285 let windows_client_received_path = windows_path_from_proto(proto);
286 let proto = windows_path_to_proto(&windows_client_received_path);
287 let unix_host_recovered_path = unix_path_from_proto(proto);
288 assert_eq!(unix_host_path, unix_host_recovered_path);
289 assert_eq!(unix_host_recovered_path.to_string_lossy(), unix_path_str);
290 }
291 }
292
293 // todo(zjk)
294 #[test]
295 fn test_unsolved_case() {
296 // Unix host, Windows client
297 // The Windows client receives a Unix path with backslashes in it, then
298 // sends it back to the host.
299 // This currently fails.
300 let unix_path = UnixPathBuf::from("/home/user/projects/my\\project/src/main.rs");
301 let proto = unix_path_to_proto(&unix_path);
302 let windows_client_received_path = windows_path_from_proto(proto);
303 let proto = windows_path_to_proto(&windows_client_received_path);
304 let unix_host_recovered_path = unix_path_from_proto(proto);
305 assert_ne!(unix_path, unix_host_recovered_path);
306 assert_eq!(
307 unix_host_recovered_path.to_string_lossy(),
308 "/home/user/projects/my/project/src/main.rs"
309 );
310 }
311}