neva/types/
notification.rs

1//! Utilities for Notifications
2
3use serde::{Serialize, Deserialize};
4use serde::de::DeserializeOwned;
5use crate::types::{RequestId, Message, JSONRPC_VERSION};
6#[cfg(feature = "server")]
7use crate::{error::Error, types::{FromRequest, Request}};
8
9pub use log_message::{
10    LogMessage, 
11    LoggingLevel, 
12    SetLevelRequestParams
13};
14
15#[cfg(feature = "server")]
16use crate::app::handler::{FromHandlerParams, HandlerParams};
17
18pub use progress::ProgressNotification;
19
20#[cfg(feature = "tracing")]
21pub use formatter::NotificationFormatter;
22
23mod progress;
24mod log_message;
25#[cfg(feature = "tracing")]
26mod formatter;
27#[cfg(feature = "tracing")]
28pub mod fmt;
29
30/// List of commands for Notifications
31pub mod commands {
32    /// Notification name that indicates that the notifications have initialized.
33    pub const INITIALIZED: &str = "notifications/initialized";
34    
35    /// Notification name that indicates that notifications have been canceled.
36    pub const CANCELLED: &str = "notifications/cancelled";
37    
38    /// Notification name that indicates that a new log message has been received.
39    pub const MESSAGE: &str = "notifications/message";
40    
41    /// Notification name that indicates that a progress notification has been received.
42    pub const PROGRESS: &str = "notifications/progress";
43    
44    /// Notification name that indicates that a log message has been received on stderr.
45    pub const STDERR: &str = "notifications/stderr";
46    
47    /// Command name that sets the log level.
48    pub const SET_LOG_LEVEL: &str = "logging/setLevel";
49}
50
51/// A notification which does not expect a response.
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct Notification {
54    /// JSON-RPC protocol version. 
55    ///
56    /// > Note: always 2.0.
57    pub jsonrpc: String,
58
59    /// Name of the notification method.
60    pub method: String,
61
62    /// Optional parameters for the notifications.
63    #[serde(skip_serializing_if = "Option::is_none")]
64    pub params: Option<serde_json::Value>,
65
66    /// Current MCP Session ID
67    #[serde(skip)]
68    pub session_id: Option<uuid::Uuid>,
69}
70
71/// This notification can be sent by either side to indicate that it is cancelling 
72/// a previously-issued request.
73/// 
74/// The request **SHOULD** still be in-flight, but due to communication latency, 
75/// it is always possible that this notification **MAY** arrive after the request has already finished.
76/// 
77/// This notification indicates that the result will be unused, 
78/// so any associated processing **SHOULD** cease.
79/// 
80/// A client **MUST NOT** attempt to cancel its `initialize` request.
81#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct CancelledNotificationParams {
83    /// The ID of the request to cancel.
84    /// 
85    /// This **MUST** correspond to the ID of a request previously issued in the same direction.
86    #[serde(rename = "requestId")]
87    pub request_id: RequestId,
88    
89    /// An optional string describing the reason for the cancellation. 
90    /// This **MAY** be logged or presented to the user.
91    #[serde(skip_serializing_if = "Option::is_none")]
92    pub reason: Option<String>,
93}
94
95impl From<Notification> for Message {
96    #[inline]
97    fn from(notification: Notification) -> Self {
98        Self::Notification(notification)
99    }
100}
101
102#[cfg(feature = "server")]
103impl FromHandlerParams for CancelledNotificationParams {
104    #[inline]
105    fn from_params(params: &HandlerParams) -> Result<Self, Error> {
106        let req = Request::from_params(params)?;
107        Self::from_request(req)
108    }
109}
110
111impl Notification {
112    /// Create a new [`Notification`]
113    #[inline]
114    pub fn new(method: &str, params: Option<serde_json::Value>) -> Self {
115        Self { 
116            jsonrpc: JSONRPC_VERSION.into(),
117            session_id: None,
118            method: method.into(), 
119            params
120        }
121    }
122
123    /// Returns the full id (session_id?/"(no_id)")
124    pub fn full_id(&self) -> RequestId {
125        let id = RequestId::default();
126        if let Some(session_id) = self.session_id {
127            id.concat(RequestId::Uuid(session_id))
128        } else {
129            id
130        }
131    }
132    
133    /// Parses [`Notification`] params into specified type
134    #[inline]
135    pub fn params<T: DeserializeOwned>(&self) -> Option<T> {
136        match self.params { 
137            Some(ref params) => serde_json::from_value(params.clone()).ok(),
138            None => None,
139        }
140    }
141    
142    /// Writes the [`Notification`]
143    #[inline]
144    #[cfg(feature = "tracing")]
145    pub fn write(self) {
146        let is_stderr = self.is_stderr();
147        let Some(params) = self.params else { return; };
148        if is_stderr {
149            Self::write_err_internal(params);
150        } else {
151            match serde_json::from_value::<LogMessage>(params.clone()) { 
152                Ok(log) => log.write(),
153                Err(err) => tracing::error!(logger = "neva", "{}", err),
154            }
155        }
156    }
157    
158    /// Returns `true` is the [`Notification`] received with method `notifications/stderr`
159    #[inline]
160    pub fn is_stderr(&self) -> bool {
161        self.method.as_str() == commands::STDERR
162    }
163    
164    /// Writes the [`Notification`] as [`LoggingLevel::Error`]
165    #[inline]
166    #[cfg(feature = "tracing")]
167    pub fn write_err(self) {
168        if let Some(params) = self.params {
169            Self::write_err_internal(params)
170        }
171    }
172    
173    /// Serializes the [`Notification`] to JSON string
174    pub fn to_json(self) -> String {
175        serde_json::to_string(&self).unwrap()
176    }
177    
178    #[inline]
179    #[cfg(feature = "tracing")]
180    fn write_err_internal(params: serde_json::Value) {
181        let err = params
182            .get("content")
183            .unwrap_or(&params);
184        tracing::error!("{}", err);
185    }
186}
187
188#[cfg(test)]
189mod tests {
190    use serde_json::json;
191    use super::*;
192    
193    #[test]
194    fn it_creates_new_notification() {
195        let notification = Notification::new("test", Some(json!({ "param": "value" })));
196        
197        assert_eq!(notification.jsonrpc, "2.0");
198        assert_eq!(notification.method, "test");
199        
200        let params_json = serde_json::to_string(&notification.params.unwrap()).unwrap();
201        
202        assert_eq!(params_json, r#"{"param":"value"}"#);
203    }
204}