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