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
 34/// A trait to bind LSP request and responses for the proto layer.
 35/// Should be used for every LSP request that has to traverse through the proto layer.
 36///
 37/// `lsp_messages` macro in the same crate provides a convenient way to implement this.
 38pub trait LspRequestMessage: EnvelopedMessage {
 39    type Response: EnvelopedMessage;
 40
 41    fn to_proto_query(self) -> crate::lsp_query::Request;
 42
 43    fn response_to_proto_query(response: Self::Response) -> crate::lsp_response::Response;
 44
 45    fn buffer_id(&self) -> u64;
 46
 47    fn buffer_version(&self) -> &[crate::VectorClockEntry];
 48
 49    /// Whether to deduplicate the requests, or keep the previous ones running when another
 50    /// request of the same kind is processed.
 51    fn stop_previous_requests() -> bool;
 52}
 53
 54#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
 55pub struct LspRequestId(pub u64);
 56
 57/// A response from a single language server.
 58/// There could be multiple responses for a single LSP request,
 59/// from different servers.
 60pub struct ProtoLspResponse<R> {
 61    pub server_id: u64,
 62    pub response: R,
 63}
 64
 65impl ProtoLspResponse<Box<dyn AnyTypedEnvelope>> {
 66    pub fn into_response<T: LspRequestMessage>(self) -> Result<ProtoLspResponse<T::Response>> {
 67        let envelope = self
 68            .response
 69            .into_any()
 70            .downcast::<TypedEnvelope<T::Response>>()
 71            .map_err(|_| {
 72                anyhow::anyhow!(
 73                    "cannot downcast LspResponse to {} for message {}",
 74                    T::Response::NAME,
 75                    T::NAME,
 76                )
 77            })?;
 78
 79        Ok(ProtoLspResponse {
 80            server_id: self.server_id,
 81            response: envelope.payload,
 82        })
 83    }
 84}
 85
 86pub trait AnyTypedEnvelope: Any + Send + Sync {
 87    fn payload_type_id(&self) -> TypeId;
 88    fn payload_type_name(&self) -> &'static str;
 89    fn into_any(self: Box<Self>) -> Box<dyn Any + Send + Sync>;
 90    fn is_background(&self) -> bool;
 91    fn original_sender_id(&self) -> Option<PeerId>;
 92    fn sender_id(&self) -> PeerId;
 93    fn message_id(&self) -> u32;
 94}
 95
 96pub enum MessagePriority {
 97    Foreground,
 98    Background,
 99}
100
101impl<T: EnvelopedMessage> AnyTypedEnvelope for TypedEnvelope<T> {
102    fn payload_type_id(&self) -> TypeId {
103        TypeId::of::<T>()
104    }
105
106    fn payload_type_name(&self) -> &'static str {
107        T::NAME
108    }
109
110    fn into_any(self: Box<Self>) -> Box<dyn Any + Send + Sync> {
111        self
112    }
113
114    fn is_background(&self) -> bool {
115        matches!(T::PRIORITY, MessagePriority::Background)
116    }
117
118    fn original_sender_id(&self) -> Option<PeerId> {
119        self.original_sender_id
120    }
121
122    fn sender_id(&self) -> PeerId {
123        self.sender_id
124    }
125
126    fn message_id(&self) -> u32 {
127        self.message_id
128    }
129}
130
131impl PeerId {
132    pub fn from_u64(peer_id: u64) -> Self {
133        let owner_id = (peer_id >> 32) as u32;
134        let id = peer_id as u32;
135        Self { owner_id, id }
136    }
137
138    pub fn as_u64(self) -> u64 {
139        ((self.owner_id as u64) << 32) | (self.id as u64)
140    }
141}
142
143impl Copy for PeerId {}
144
145impl Eq for PeerId {}
146
147impl Ord for PeerId {
148    fn cmp(&self, other: &Self) -> cmp::Ordering {
149        self.owner_id
150            .cmp(&other.owner_id)
151            .then_with(|| self.id.cmp(&other.id))
152    }
153}
154
155impl PartialOrd for PeerId {
156    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
157        Some(self.cmp(other))
158    }
159}
160
161impl std::hash::Hash for PeerId {
162    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
163        self.owner_id.hash(state);
164        self.id.hash(state);
165    }
166}
167
168impl fmt::Display for PeerId {
169    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170        write!(f, "{}/{}", self.owner_id, self.id)
171    }
172}
173
174pub trait FromProto {
175    fn from_proto(proto: String) -> Self;
176}
177
178pub trait ToProto {
179    fn to_proto(self) -> String;
180}
181
182#[inline]
183fn from_proto_path(proto: String) -> PathBuf {
184    #[cfg(target_os = "windows")]
185    let proto = proto.replace('/', "\\");
186
187    PathBuf::from(proto)
188}
189
190#[inline]
191fn to_proto_path(path: &Path) -> String {
192    #[cfg(target_os = "windows")]
193    let proto = path.to_string_lossy().replace('\\', "/");
194
195    #[cfg(not(target_os = "windows"))]
196    let proto = path.to_string_lossy().to_string();
197
198    proto
199}
200
201impl FromProto for PathBuf {
202    fn from_proto(proto: String) -> Self {
203        from_proto_path(proto)
204    }
205}
206
207impl FromProto for Arc<Path> {
208    fn from_proto(proto: String) -> Self {
209        from_proto_path(proto).into()
210    }
211}
212
213impl ToProto for PathBuf {
214    fn to_proto(self) -> String {
215        to_proto_path(&self)
216    }
217}
218
219impl ToProto for &Path {
220    fn to_proto(self) -> String {
221        to_proto_path(self)
222    }
223}
224
225pub struct Receipt<T> {
226    pub sender_id: PeerId,
227    pub message_id: u32,
228    payload_type: PhantomData<T>,
229}
230
231impl<T> Clone for Receipt<T> {
232    fn clone(&self) -> Self {
233        *self
234    }
235}
236
237impl<T> Copy for Receipt<T> {}
238
239#[derive(Clone, Debug)]
240pub struct TypedEnvelope<T> {
241    pub sender_id: PeerId,
242    pub original_sender_id: Option<PeerId>,
243    pub message_id: u32,
244    pub payload: T,
245    pub received_at: Instant,
246}
247
248impl<T> TypedEnvelope<T> {
249    pub fn original_sender_id(&self) -> Result<PeerId> {
250        self.original_sender_id
251            .context("missing original_sender_id")
252    }
253}
254
255impl<T: RequestMessage> TypedEnvelope<T> {
256    pub fn receipt(&self) -> Receipt<T> {
257        Receipt {
258            sender_id: self.sender_id,
259            message_id: self.message_id,
260            payload_type: PhantomData,
261        }
262    }
263}
264
265#[cfg(test)]
266mod tests {
267    use typed_path::{UnixPath, UnixPathBuf, WindowsPath, WindowsPathBuf};
268
269    fn windows_path_from_proto(proto: String) -> WindowsPathBuf {
270        let proto = proto.replace('/', "\\");
271        WindowsPathBuf::from(proto)
272    }
273
274    fn unix_path_from_proto(proto: String) -> UnixPathBuf {
275        UnixPathBuf::from(proto)
276    }
277
278    fn windows_path_to_proto(path: &WindowsPath) -> String {
279        path.to_string_lossy().replace('\\', "/")
280    }
281
282    fn unix_path_to_proto(path: &UnixPath) -> String {
283        path.to_string_lossy().to_string()
284    }
285
286    #[test]
287    fn test_path_proto_interop() {
288        const WINDOWS_PATHS: &[&str] = &[
289            "C:\\Users\\User\\Documents\\file.txt",
290            "C:/Program Files/App/app.exe",
291            "projects\\zed\\crates\\proto\\src\\typed_envelope.rs",
292            "projects/my project/src/main.rs",
293        ];
294        const UNIX_PATHS: &[&str] = &[
295            "/home/user/documents/file.txt",
296            "/usr/local/bin/my app/app",
297            "projects/zed/crates/proto/src/typed_envelope.rs",
298            "projects/my project/src/main.rs",
299        ];
300
301        // Windows path to proto and back
302        for &windows_path_str in WINDOWS_PATHS {
303            let windows_path = WindowsPathBuf::from(windows_path_str);
304            let proto = windows_path_to_proto(&windows_path);
305            let recovered_path = windows_path_from_proto(proto);
306            assert_eq!(windows_path, recovered_path);
307            assert_eq!(
308                recovered_path.to_string_lossy(),
309                windows_path_str.replace('/', "\\")
310            );
311        }
312        // Unix path to proto and back
313        for &unix_path_str in UNIX_PATHS {
314            let unix_path = UnixPathBuf::from(unix_path_str);
315            let proto = unix_path_to_proto(&unix_path);
316            let recovered_path = unix_path_from_proto(proto);
317            assert_eq!(unix_path, recovered_path);
318            assert_eq!(recovered_path.to_string_lossy(), unix_path_str);
319        }
320        // Windows host, Unix client, host sends Windows path to client
321        for &windows_path_str in WINDOWS_PATHS {
322            let windows_host_path = WindowsPathBuf::from(windows_path_str);
323            let proto = windows_path_to_proto(&windows_host_path);
324            let unix_client_received_path = unix_path_from_proto(proto);
325            let proto = unix_path_to_proto(&unix_client_received_path);
326            let windows_host_recovered_path = windows_path_from_proto(proto);
327            assert_eq!(windows_host_path, windows_host_recovered_path);
328            assert_eq!(
329                windows_host_recovered_path.to_string_lossy(),
330                windows_path_str.replace('/', "\\")
331            );
332        }
333        // Unix host, Windows client, host sends Unix path to client
334        for &unix_path_str in UNIX_PATHS {
335            let unix_host_path = UnixPathBuf::from(unix_path_str);
336            let proto = unix_path_to_proto(&unix_host_path);
337            let windows_client_received_path = windows_path_from_proto(proto);
338            let proto = windows_path_to_proto(&windows_client_received_path);
339            let unix_host_recovered_path = unix_path_from_proto(proto);
340            assert_eq!(unix_host_path, unix_host_recovered_path);
341            assert_eq!(unix_host_recovered_path.to_string_lossy(), unix_path_str);
342        }
343    }
344
345    // todo(zjk)
346    #[test]
347    fn test_unsolved_case() {
348        // Unix host, Windows client
349        // The Windows client receives a Unix path with backslashes in it, then
350        // sends it back to the host.
351        // This currently fails.
352        let unix_path = UnixPathBuf::from("/home/user/projects/my\\project/src/main.rs");
353        let proto = unix_path_to_proto(&unix_path);
354        let windows_client_received_path = windows_path_from_proto(proto);
355        let proto = windows_path_to_proto(&windows_client_received_path);
356        let unix_host_recovered_path = unix_path_from_proto(proto);
357        assert_ne!(unix_path, unix_host_recovered_path);
358        assert_eq!(
359            unix_host_recovered_path.to_string_lossy(),
360            "/home/user/projects/my/project/src/main.rs"
361        );
362    }
363}