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 InitializeBuild(RequestId, InitializeBuild),
21 Shutdown(RequestId),
23 WorkspaceBuildTargets(RequestId),
25 WorkspaceReload(RequestId),
27 BuildTargetDependencyModules(RequestId, BuildTargetDependencyModules),
31 DebugSessionStart(RequestId, DebugSessionStart),
33 BuildTargetSources(RequestId, BuildTargetSources),
35 TextDocumentInverseSources(RequestId, TextDocumentInverseSources),
37 BuildTargetDependencySources(RequestId, BuildTargetDependencySources),
40 BuildTargetResources(RequestId, BuildTargetResources),
42 BuildTargetRun(RequestId, BuildTargetRun),
44 BuildTargetCompile(RequestId, BuildTargetCompile),
46 BuildTargetTest(RequestId, BuildTargetTest),
48 BuildTargetCleanCache(RequestId, BuildTargetCleanCache),
50 Custom(RequestId, &'static str, Value),
52}
53
54impl Request {
55 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 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 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 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; let mut method: Option<String> = None; let mut params: Option<Value> = None; 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}