bsp_server/
request.rs

1mod id;
2
3use crate::Message;
4use std::fmt;
5
6use bsp_types::*;
7pub use id::*;
8
9use serde::{
10    de::{Error as DeError, MapAccess, Visitor},
11    ser::SerializeStruct,
12    Deserialize, Deserializer, Serialize,
13};
14
15use serde_json::Value;
16
17#[derive(Clone)]
18pub enum Request {
19    /// Client->Server: Initialize Server
20    InitializeBuild(RequestId, InitializeBuild),
21    /// Client->Server: Shutdown server
22    Shutdown(RequestId),
23    /// Client->Server: Get a list of all available build targets in the workspace.
24    WorkspaceBuildTargets(RequestId),
25    /// Client->Server: Reload the build configuration.
26    WorkspaceReload(RequestId),
27    /// Client->Server: Get libraries of build target dependencies that are external to the
28    /// workspace including meta information about library and their sources. It's an extended
29    /// version of buildTarget/sources
30    BuildTargetDependencyModules(RequestId, BuildTargetDependencyModules),
31    /// Client->Server: Debug build target(s)
32    DebugSessionStart(RequestId, DebugSessionStart),
33    /// Client->Server: Get text documents and directories that belong to a build target.
34    BuildTargetSources(RequestId, BuildTargetSources),
35    /// Client->Server: Get build targets containing a text document.
36    TextDocumentInverseSources(RequestId, TextDocumentInverseSources),
37    /// Client->Server: Get sources of build target dependencies that are external to the
38    /// workspace.
39    BuildTargetDependencySources(RequestId, BuildTargetDependencySources),
40    /// Client->Server: Get list of resources of a given list of build targets.
41    BuildTargetResources(RequestId, BuildTargetResources),
42    /// Client->Server: Run a build target
43    BuildTargetRun(RequestId, BuildTargetRun),
44    /// Client->Server: Run a compile target
45    BuildTargetCompile(RequestId, BuildTargetCompile),
46    /// Client->Server: Run a test target
47    BuildTargetTest(RequestId, BuildTargetTest),
48    /// Client->Server: reset any state associated with a given build target
49    BuildTargetCleanCache(RequestId, BuildTargetCleanCache),
50    /// Any custom message not yet supported in the crate or custom
51    Custom(RequestId, &'static str, Value),
52}
53
54impl Request {
55    /// Get request method
56    pub fn method(&self) -> &'static str {
57        use Request::*;
58        match self {
59            InitializeBuild(_, _) => "build/initialize",
60            Shutdown(_) => "build/shutdown",
61            WorkspaceBuildTargets(_) => "workspace/buildTargets",
62            WorkspaceReload(_) => "workspace/reload",
63            BuildTargetDependencyModules(_, _) => "buildTarget/dependencyModules",
64            DebugSessionStart(_, _) => "debugSession/start",
65            BuildTargetSources(_, _) => "buildTarget/sources",
66            TextDocumentInverseSources(_, _) => "textDocument/inverseSources",
67            BuildTargetDependencySources(_, _) => "buildTarget/dependencySources",
68            BuildTargetResources(_, _) => "buildTarget/resources",
69            BuildTargetRun(_, _) => "buildTarget/run",
70            BuildTargetCompile(_, _) => "buildTarget/compile",
71            BuildTargetTest(_, _) => "buildTarget/test",
72            BuildTargetCleanCache(_, _) => "buildTarget/cleanCache",
73            Custom(_, m, _) => m,
74        }
75    }
76
77    /// Get request id
78    pub fn id(&self) -> &RequestId {
79        use Request::*;
80        match self {
81            InitializeBuild(id, _)
82            | Shutdown(id)
83            | WorkspaceBuildTargets(id)
84            | WorkspaceReload(id)
85            | BuildTargetDependencyModules(id, _)
86            | DebugSessionStart(id, _)
87            | BuildTargetSources(id, _)
88            | TextDocumentInverseSources(id, _)
89            | BuildTargetDependencySources(id, _)
90            | BuildTargetResources(id, _)
91            | BuildTargetRun(id, _)
92            | BuildTargetCompile(id, _)
93            | BuildTargetTest(id, _)
94            | BuildTargetCleanCache(id, _)
95            | Custom(id, _, _) => id,
96        }
97    }
98
99    /// Get request params
100    pub fn params(&self) -> anyhow::Result<Value> {
101        use Request::*;
102        let value = match self {
103            Shutdown(_) | WorkspaceBuildTargets(_) | WorkspaceReload(_) => return Ok(Value::Null),
104            InitializeBuild(_, ref params) => serde_json::to_value(params),
105            BuildTargetDependencyModules(_, ref params) => serde_json::to_value(params),
106            DebugSessionStart(_, ref params) => serde_json::to_value(params),
107            BuildTargetSources(_, ref params) => serde_json::to_value(params),
108            TextDocumentInverseSources(_, ref params) => serde_json::to_value(params),
109            BuildTargetDependencySources(_, ref params) => serde_json::to_value(params),
110            BuildTargetResources(_, ref params) => serde_json::to_value(params),
111            BuildTargetRun(_, ref params) => serde_json::to_value(params),
112            BuildTargetCompile(_, ref params) => serde_json::to_value(params),
113            BuildTargetTest(_, ref params) => serde_json::to_value(params),
114            BuildTargetCleanCache(_, ref params) => serde_json::to_value(params),
115            Custom(_, ref params, _) => serde_json::to_value(params),
116        };
117
118        Ok(value?)
119    }
120}
121
122impl From<Request> for Message {
123    fn from(request: Request) -> Message {
124        Message::Request(request)
125    }
126}
127
128impl From<(RequestId, &'static str, Value)> for Request {
129    fn from(v: (RequestId, &'static str, Value)) -> Self {
130        Self::Custom(v.0.into(), v.1, v.2)
131    }
132}
133
134impl From<(RequestId, &'static str, Value)> for Message {
135    fn from(v: (RequestId, &'static str, Value)) -> Self {
136        Self::Request((v.0.into(), v.1, v.2).into())
137    }
138}
139
140macro_rules! convertible {
141    ($p:ident) => {
142        impl From<(RequestId, $p)> for Request {
143            fn from(v: (RequestId, $p)) -> Self {
144                Self::$p(v.0, v.1)
145            }
146        }
147
148        impl From<(RequestId, $p)> for Message {
149            fn from(v: (RequestId, $p)) -> Self {
150                Self::Request(crate::Request::$p(v.0, v.1))
151            }
152        }
153    };
154}
155
156convertible!(BuildTargetCleanCache);
157convertible!(BuildTargetCompile);
158convertible!(BuildTargetDependencyModules);
159convertible!(BuildTargetDependencySources);
160convertible!(BuildTargetResources);
161convertible!(BuildTargetRun);
162convertible!(BuildTargetSources);
163convertible!(BuildTargetTest);
164convertible!(DebugSessionStart);
165convertible!(InitializeBuild);
166convertible!(TextDocumentInverseSources);
167
168impl fmt::Debug for Request {
169    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170        fn format(
171            f: &mut fmt::Formatter<'_>,
172            id: &RequestId,
173            value: impl fmt::Debug,
174        ) -> fmt::Result {
175            fmt::Display::fmt(&id, f)?;
176            f.write_str(", ")?;
177            value.fmt(f)
178        }
179        match self {
180            Request::InitializeBuild(id, value) => format(f, id, value),
181            Request::Shutdown(id) => format(f, id, "Shutdown"),
182            Request::WorkspaceBuildTargets(id) => format(f, id, "WorkspaceBuildTargets"),
183            Request::WorkspaceReload(id) => format(f, id, "WorkspaceReload"),
184            Request::BuildTargetDependencyModules(id, value) => format(f, id, value),
185            Request::DebugSessionStart(id, value) => format(f, id, value),
186            Request::BuildTargetSources(id, value) => format(f, id, value),
187            Request::TextDocumentInverseSources(id, value) => format(f, id, value),
188            Request::BuildTargetDependencySources(id, value) => format(f, id, value),
189            Request::BuildTargetResources(id, value) => format(f, id, value),
190            Request::BuildTargetRun(id, value) => format(f, id, value),
191            Request::BuildTargetCompile(id, value) => format(f, id, value),
192            Request::BuildTargetTest(id, value) => format(f, id, value),
193            Request::BuildTargetCleanCache(id, value) => format(f, id, value),
194            Request::Custom(id, method, value) => {
195                fmt::Display::fmt(&id, f)?;
196                f.write_str(", ")?;
197                method.fmt(f)?;
198                f.write_str(",\n")?;
199                value.fmt(f)
200            }
201        }
202    }
203}
204
205impl Serialize for Request {
206    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
207    where
208        S: serde::Serializer,
209    {
210        let method = self.method();
211        let mut obj = s.serialize_struct("Request", 2)?;
212
213        use Request::*;
214        match self {
215            InitializeBuild(id, value) => {
216                obj.serialize_field("id", id)?;
217                obj.serialize_field("method", method)?;
218                obj.serialize_field("params", value)?;
219            }
220            // TODO: Should it set value to None?
221            Shutdown(id) | WorkspaceBuildTargets(id) | WorkspaceReload(id) => {
222                obj.serialize_field("id", id)?;
223                obj.serialize_field("method", method)?;
224            }
225            BuildTargetDependencyModules(id, value) => {
226                obj.serialize_field("id", id)?;
227                obj.serialize_field("method", method)?;
228                obj.serialize_field("params", value)?;
229            }
230            DebugSessionStart(id, value) => {
231                obj.serialize_field("id", id)?;
232                obj.serialize_field("method", method)?;
233                obj.serialize_field("params", value)?;
234            }
235            BuildTargetSources(id, value) => {
236                obj.serialize_field("id", id)?;
237                obj.serialize_field("method", method)?;
238                obj.serialize_field("params", value)?;
239            }
240            TextDocumentInverseSources(id, value) => {
241                obj.serialize_field("id", id)?;
242                obj.serialize_field("method", method)?;
243                obj.serialize_field("params", value)?;
244            }
245            BuildTargetDependencySources(id, value) => {
246                obj.serialize_field("id", id)?;
247                obj.serialize_field("method", method)?;
248                obj.serialize_field("params", value)?;
249            }
250            BuildTargetResources(id, value) => {
251                obj.serialize_field("id", id)?;
252                obj.serialize_field("method", method)?;
253                obj.serialize_field("params", value)?;
254            }
255            BuildTargetRun(id, value) => {
256                obj.serialize_field("id", id)?;
257                obj.serialize_field("method", method)?;
258                obj.serialize_field("params", value)?;
259            }
260            BuildTargetCompile(id, value) => {
261                obj.serialize_field("id", id)?;
262                obj.serialize_field("method", method)?;
263                obj.serialize_field("params", value)?;
264            }
265            BuildTargetTest(id, value) => {
266                obj.serialize_field("id", id)?;
267                obj.serialize_field("method", method)?;
268                obj.serialize_field("params", value)?;
269            }
270            BuildTargetCleanCache(id, value) => {
271                obj.serialize_field("id", id)?;
272                obj.serialize_field("method", method)?;
273                obj.serialize_field("params", value)?;
274            }
275            Custom(id, _, value) => {
276                obj.serialize_field("id", id)?;
277                obj.serialize_field("method", method)?;
278                if !value.is_null() {
279                    obj.serialize_field("params", value)?;
280                }
281            }
282        };
283        obj.end()
284    }
285}
286
287#[cfg(test)]
288mod se {
289    use serde_json::to_string;
290
291    use super::*;
292    #[test]
293    fn initialize() {
294        let mut params = InitializeBuild::new(
295            "MyName",
296            "1",
297            "2",
298            Url::from_file_path("/tmp/lua_27s2fl").unwrap(),
299            Default::default(),
300            Default::default(),
301        );
302        params.set_display_name("MyName".into());
303
304        let value = &Request::InitializeBuild(3.into(), params);
305        let result = to_string(value).unwrap();
306        assert_eq!(
307            result,
308           "{\"id\":3,\"method\":\"build/initialize\",\"params\":{\"displayName\":\"MyName\",\"version\":\"1\",\"bspVersion\":\"2\",\"rootUri\":\"file:///tmp/lua_27s2fl\",\"capabilities\":{\"languageIds\":[]},\"data\":null}}" 
309        );
310    }
311
312    #[test]
313    fn shutdown() {
314        let value = &Request::Shutdown(3.into());
315        let result = to_string(value).unwrap();
316        assert_eq!(result, "{\"id\":3,\"method\":\"build/shutdown\"}");
317    }
318
319    #[test]
320    fn debug_session_start() {
321        let mut params = DebugSessionStart::default();
322        params.set_data_kind("Some".into());
323
324        let value = &Request::DebugSessionStart(3.into(), params);
325        let result = to_string(value).unwrap();
326        assert_eq!(result, "{\"id\":3,\"method\":\"debugSession/start\",\"params\":{\"targets\":[],\"dataKind\":\"Some\"}}");
327    }
328
329    #[test]
330    fn custom() {
331        let value = &Request::Custom(3.into(), "some/method", Value::Null);
332        let result = to_string(value).unwrap();
333        assert_eq!(result, "{\"id\":3,\"method\":\"some/method\"}");
334    }
335}
336
337impl<'de> Deserialize<'de> for Request {
338    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
339    where
340        D: Deserializer<'de>,
341    {
342        const FIELDS: &'static [&'static str] = &["id", "method", "params"];
343        enum Field {
344            ID,
345            Method,
346            Params,
347            Other,
348        }
349
350        impl<'de> Deserialize<'de> for Field {
351            fn deserialize<D>(deserializer: D) -> Result<Field, D::Error>
352            where
353                D: Deserializer<'de>,
354            {
355                struct FieldVisitor;
356
357                impl<'de> Visitor<'de> for FieldVisitor {
358                    type Value = Field;
359
360                    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
361                        formatter.write_str("method and params")
362                    }
363
364                    fn visit_str<E>(self, value: &str) -> Result<Field, E>
365                    where
366                        E: DeError,
367                    {
368                        match value {
369                            "id" => Ok(Field::ID),
370                            "method" => Ok(Field::Method),
371                            "params" => Ok(Field::Params),
372                            _ => Ok(Field::Other),
373                        }
374                    }
375                }
376
377                deserializer.deserialize_identifier(FieldVisitor)
378            }
379        }
380
381        struct RequestVisitor;
382
383        impl<'de> Visitor<'de> for RequestVisitor {
384            type Value = Request;
385
386            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
387                formatter.write_str("struct Request")
388            }
389
390            fn visit_map<V>(self, mut map: V) -> Result<Request, V::Error>
391            where
392                V: MapAccess<'de>,
393            {
394                let mut id: Option<Value> = None; //  not sure maybe string maybe i32
395                let mut method: Option<String> = None; // required for json! to work
396                let mut params: Option<Value> = None; // this is just lazy
397
398                while let Some(key) = map.next_key()? {
399                    match key {
400                        Field::ID => {
401                            if id.is_some() {
402                                return Err(DeError::duplicate_field("id"));
403                            }
404                            id = Some(map.next_value()?);
405                        }
406                        Field::Params => {
407                            if params.is_some() {
408                                return Err(DeError::duplicate_field("params"));
409                            }
410                            params = Some(map.next_value()?);
411                        }
412                        Field::Method => {
413                            if method.is_some() {
414                                return Err(DeError::duplicate_field("method"));
415                            }
416                            method = Some(map.next_value()?);
417                        }
418                        _ => (),
419                    }
420                }
421
422                fn de<'a, T: Deserialize<'a>, E: DeError>(p: serde_json::Value) -> Result<T, E> {
423                    T::deserialize(p).map_err(DeError::custom)
424                }
425
426                let method = method.ok_or_else(|| DeError::missing_field("method"))?;
427                let id = de::<RequestId, _>(id.ok_or_else(|| DeError::missing_field("id"))?)?;
428                let params = match params {
429                    Some(v) => v,
430                    None => {
431                        if &method != "build/shutdown"
432                            || &method != "workspace/buildTargets"
433                            || &method != "workspace/reload"
434                        {
435                            return Err(DeError::missing_field("params"));
436                        }
437                        serde_json::Value::Null
438                    }
439                };
440
441                Ok(match method.as_str() {
442                    "build/initialize" => Request::InitializeBuild(id, de(params)?),
443                    "build/shutdown" => Request::Shutdown(id),
444                    "workspace/buildTargets" => Request::WorkspaceBuildTargets(id),
445                    "workspace/reload" => Request::WorkspaceReload(id),
446                    "buildTarget/dependencyModules" => {
447                        Request::BuildTargetDependencyModules(id, de(params)?)
448                    }
449                    "debugSession/start" => Request::DebugSessionStart(id, de(params)?),
450                    "buildTarget/sources" => Request::BuildTargetSources(id, de(params)?),
451                    "textDocument/inverseSources" => {
452                        Request::TextDocumentInverseSources(id, de(params)?)
453                    }
454                    "buildTarget/dependencySources" => {
455                        Request::BuildTargetDependencySources(id, de(params)?)
456                    }
457                    "buildTarget/resources" => Request::BuildTargetResources(id, de(params)?),
458                    "buildTarget/run" => Request::BuildTargetRun(id, de(params)?),
459                    "buildTarget/compile" => Request::BuildTargetCompile(id, de(params)?),
460                    "buildTarget/test" => Request::BuildTargetTest(id, de(params)?),
461                    "buildTarget/cleanCache" => Request::BuildTargetCleanCache(id, de(params)?),
462                    _ => Request::Custom(id, Box::leak(method.into_boxed_str()), params),
463                })
464            }
465        }
466
467        deserializer.deserialize_struct("Request", FIELDS, RequestVisitor)
468    }
469}
470
471#[cfg(test)]
472mod de {
473    use super::*;
474    #[test]
475    fn initialize() {
476        let value = "{\"id\":3,\"method\":\"build/initialize\",\"params\":{\"displayName\":\"MyName\",\"version\":\"1\",\"bspVersion\":\"2\",\"rootUri\":\"file:///tmp/lua_27s2fl\",\"capabilities\":{\"languageIds\":[]},\"data\":null}}";
477        let msg = serde_json::from_str(value).unwrap();
478        assert!(matches!(
479            msg,
480            Request::InitializeBuild(_, InitializeBuild { .. })
481        ));
482    }
483}