bsp_server/
notification.rs

1use std::fmt;
2
3use bsp_types::{
4    BuildTargetDidChange, LogMessage, PublishDiagnostics, ShowMessage, TaskFinish, TaskProgress,
5    TaskStart,
6};
7use serde::{
8    de::{Error as DeError, MapAccess, Visitor},
9    ser::SerializeStruct,
10    Deserialize, Deserializer, Serialize,
11};
12use serde_json::Value;
13
14use crate::Message;
15
16#[derive(Debug, Clone)]
17pub enum Notification {
18    Exit,
19    Initialized,
20    ShowMessage(ShowMessage),
21    LogMessage(LogMessage),
22    PublishDiagnostics(PublishDiagnostics),
23    TaskStart(TaskStart),
24    TaskFinish(TaskFinish),
25    TaskProgress(TaskProgress),
26    BuildTargetDidChange(BuildTargetDidChange),
27    Custom(&'static str, Value),
28}
29
30impl Notification {
31    pub fn method(&self) -> &'static str {
32        use Notification::*;
33        match self {
34            Exit => "build/exit",
35            Initialized => "build/initialized",
36            ShowMessage(_) => "build/showMessage",
37            LogMessage(_) => "build/logMessage",
38            PublishDiagnostics(_) => "build/publishDiagnostics",
39            TaskStart(_) => "build/taskStart",
40            TaskFinish(_) => "build/taskFinish",
41            TaskProgress(_) => "build/taskProgressing",
42            BuildTargetDidChange(_) => "buildTarget/didChange",
43            Custom(m, _) => m,
44        }
45    }
46}
47
48impl From<(&'static str, Value)> for Notification {
49    fn from(v: (&'static str, Value)) -> Self {
50        Self::Custom(v.0, v.1)
51    }
52}
53
54impl From<&str> for Notification {
55    fn from(msg: &str) -> Self {
56        match msg {
57            "build/exit" => Self::Exit,
58            "build/initialized" => Self::Initialized,
59            _ => panic!("Only exit and initialized supported."),
60        }
61    }
62}
63
64impl From<Notification> for Message {
65    fn from(notification: Notification) -> Self {
66        Self::Notification(notification)
67    }
68}
69
70impl From<(&'static str, Value)> for Message {
71    fn from(v: (&'static str, Value)) -> Self {
72        Self::Notification(Notification::Custom(v.0, v.1))
73    }
74}
75
76macro_rules! convertible {
77    ($p:ident) => {
78        impl From<$p> for Notification {
79            fn from(msg: $p) -> Self {
80                Self::$p(msg)
81            }
82        }
83        impl From<$p> for Message {
84            fn from(msg: $p) -> Self {
85                Self::Notification(crate::Notification::$p(msg))
86            }
87        }
88    };
89}
90
91convertible!(ShowMessage);
92convertible!(LogMessage);
93convertible!(PublishDiagnostics);
94convertible!(TaskStart);
95convertible!(TaskFinish);
96convertible!(TaskProgress);
97convertible!(BuildTargetDidChange);
98
99impl Serialize for Notification {
100    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
101    where
102        S: serde::Serializer,
103    {
104        let mut obj = s.serialize_struct("Notification", 2)?;
105        obj.serialize_field("method", self.method())?;
106
107        use Notification::*;
108        match self {
109            Exit | Initialized => {}
110            ShowMessage(m) => obj.serialize_field("params", m)?,
111            LogMessage(m) => obj.serialize_field("params", m)?,
112            PublishDiagnostics(m) => obj.serialize_field("params", m)?,
113            TaskStart(m) => obj.serialize_field("params", m)?,
114            TaskFinish(m) => obj.serialize_field("params", m)?,
115            TaskProgress(m) => obj.serialize_field("params", m)?,
116            BuildTargetDidChange(m) => obj.serialize_field("params", m)?,
117            Custom(_, m) => obj.serialize_field("params", m)?,
118        };
119
120        obj.end()
121    }
122}
123
124#[cfg(test)]
125mod se {
126    use super::*;
127    #[test]
128    fn initialized() {
129        let value = &Notification::Initialized;
130        let result = serde_json::to_string(value).unwrap();
131        assert_eq!(result, "{\"method\":\"build/initialized\"}");
132    }
133
134    #[test]
135    fn exit() {
136        let value = &Notification::Exit;
137        let result = serde_json::to_string(value).unwrap();
138        assert_eq!(result, "{\"method\":\"build/exit\"}");
139    }
140
141    #[test]
142    fn show_message() {
143        let value = &Notification::TaskStart(TaskStart::new("some_id"));
144        let result = serde_json::to_string(value).unwrap();
145        assert_eq!(
146            result,
147            "{\"method\":\"build/taskStart\",\"params\":{\"taskId\":{\"id\":\"some_id\"}}}"
148        );
149    }
150
151    #[test]
152    fn custom() {
153        let value = &Notification::Custom("custom".into(), Value::Null);
154        let result = serde_json::to_string(value).unwrap();
155        assert_eq!(result, "{\"method\":\"custom\",\"params\":null}");
156    }
157}
158
159impl<'de> Deserialize<'de> for Notification {
160    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
161    where
162        D: Deserializer<'de>,
163    {
164        const FIELDS: &'static [&'static str] = &["params", "method"];
165        enum Field {
166            Method,
167            Params,
168            Other,
169        }
170
171        impl<'de> Deserialize<'de> for Field {
172            fn deserialize<D>(deserializer: D) -> Result<Field, D::Error>
173            where
174                D: Deserializer<'de>,
175            {
176                struct FieldVisitor;
177
178                impl<'de> Visitor<'de> for FieldVisitor {
179                    type Value = Field;
180
181                    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
182                        formatter.write_str("method and params")
183                    }
184
185                    fn visit_str<E>(self, value: &str) -> Result<Field, E>
186                    where
187                        E: DeError,
188                    {
189                        match value {
190                            "method" => Ok(Field::Method),
191                            "params" => Ok(Field::Params),
192                            _ => Ok(Field::Other),
193                        }
194                    }
195                }
196
197                deserializer.deserialize_identifier(FieldVisitor)
198            }
199        }
200
201        struct NotificationVisitor;
202
203        impl<'de> Visitor<'de> for NotificationVisitor {
204            type Value = Notification;
205
206            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
207                formatter.write_str("struct Notification")
208            }
209
210            fn visit_map<V>(self, mut map: V) -> Result<Notification, V::Error>
211            where
212                V: MapAccess<'de>,
213            {
214                let mut params: Option<serde_json::Value> = None; // this is just lazy
215                let mut method: Option<String> = None; // required for json! to work
216                while let Some(key) = map.next_key()? {
217                    match key {
218                        Field::Params => {
219                            if params.is_some() {
220                                return Err(DeError::duplicate_field("params"));
221                            }
222                            params = Some(map.next_value()?);
223                        }
224                        Field::Method => {
225                            if method.is_some() {
226                                return Err(DeError::duplicate_field("method"));
227                            }
228                            method = Some(map.next_value()?);
229                        }
230                        _ => (),
231                    }
232                }
233
234                let method = method.ok_or_else(|| DeError::missing_field("method"))?;
235                let params = match params {
236                    Some(v) => v,
237                    None => {
238                        if &method != "build/exit" && &method != "build/initialized" {
239                            return Err(DeError::missing_field("params"));
240                        }
241                        serde_json::Value::Null
242                    }
243                };
244
245                fn de<'a, T: Deserialize<'a>, E: DeError>(p: serde_json::Value) -> Result<T, E> {
246                    T::deserialize(p).map_err(DeError::custom)
247                }
248
249                use Notification::*;
250                Ok(match method.as_str() {
251                    "build/exit" => Exit,
252                    "build/initialized" => Initialized,
253                    "build/showMessage" => ShowMessage(de(params)?),
254                    "build/logMessage" => LogMessage(de(params)?),
255                    "build/publishDiagnostics" => PublishDiagnostics(de(params)?),
256                    "build/taskStart" => TaskStart(de(params)?),
257                    "build/taskFinish" => TaskFinish(de(params)?),
258                    "build/taskProgressing" => TaskProgress(de(params)?),
259                    "buildTarget/didChange" => BuildTargetDidChange(de(params)?),
260                    _ => Custom(Box::leak(method.into_boxed_str()), params),
261                })
262            }
263        }
264
265        deserializer.deserialize_struct("Notification", FIELDS, NotificationVisitor)
266    }
267}
268
269#[cfg(test)]
270mod de {
271    use super::*;
272    #[test]
273    fn initialized_without_params() {
274        let value = "{\"method\":\"build/initialized\"}";
275        let msg = serde_json::from_str(value).unwrap();
276        assert!(matches!(msg, Notification::Initialized));
277    }
278
279    #[test]
280    fn initialized_with_params() {
281        let value = serde_json::json!({
282             "jsonrpc": "2.0",
283             "method":"build/initialized"
284        });
285        let result = serde_json::from_value(value).unwrap();
286        assert!(matches!(result, Notification::Initialized));
287    }
288
289    #[test]
290    fn show_message() {
291        let value = "{\"method\":\"build/taskStart\",\"params\":{\"taskId\":{\"id\":\"some_id\"}}}";
292        let result = serde_json::from_str::<Notification>(value).unwrap();
293        assert!(matches!(result, Notification::TaskStart(TaskStart { .. })));
294    }
295}