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};
 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}