typed_envelope.rs

  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}