runmat_kernel/
protocol.rs

1//! Jupyter messaging protocol implementation
2//!
3//! Implements the Jupyter kernel protocol v5.3 for communication between
4//! the kernel and Jupyter frontends.
5
6use serde::{Deserialize, Serialize};
7use serde_json::Value as JsonValue;
8use std::collections::HashMap;
9use uuid::Uuid;
10
11/// Jupyter message types as defined in the protocol
12#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
13#[serde(rename_all = "snake_case")]
14pub enum MessageType {
15    // Shell channel
16    ExecuteRequest,
17    ExecuteReply,
18    InspectRequest,
19    InspectReply,
20    CompleteRequest,
21    CompleteReply,
22    HistoryRequest,
23    HistoryReply,
24    IsCompleteRequest,
25    IsCompleteReply,
26    KernelInfoRequest,
27    KernelInfoReply,
28
29    // Control channel
30    ShutdownRequest,
31    ShutdownReply,
32    InterruptRequest,
33    InterruptReply,
34
35    // IOPub channel
36    Status,
37    Stream,
38    DisplayData,
39    ExecuteInput,
40    ExecuteResult,
41    Error,
42
43    // Stdin channel
44    InputRequest,
45    InputReply,
46}
47
48/// Complete Jupyter message structure
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct JupyterMessage {
51    /// Message header containing metadata
52    pub header: MessageHeader,
53    /// Parent message header (if this is a reply)
54    pub parent_header: Option<MessageHeader>,
55    /// Message metadata
56    pub metadata: HashMap<String, JsonValue>,
57    /// Message content (varies by message type)
58    pub content: JsonValue,
59    /// Buffer data for binary content
60    #[serde(skip_serializing_if = "Vec::is_empty", default)]
61    pub buffers: Vec<Vec<u8>>,
62}
63
64/// Message header with identification and routing information
65#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct MessageHeader {
67    /// Unique message identifier
68    pub msg_id: String,
69    /// Message type
70    pub msg_type: MessageType,
71    /// Jupyter session identifier
72    pub session: String,
73    /// Message creation timestamp (ISO 8601)
74    pub date: String,
75    /// Jupyter protocol version
76    pub version: String,
77    /// Username who sent the message
78    pub username: String,
79}
80
81impl MessageHeader {
82    /// Create a new message header
83    pub fn new(msg_type: MessageType, session: &str) -> Self {
84        Self {
85            msg_id: Uuid::new_v4().to_string(),
86            msg_type,
87            session: session.to_string(),
88            date: chrono::Utc::now().to_rfc3339(),
89            version: "5.3".to_string(),
90            username: "kernel".to_string(),
91        }
92    }
93}
94
95impl JupyterMessage {
96    /// Create a new message
97    pub fn new(msg_type: MessageType, session: &str, content: JsonValue) -> Self {
98        Self {
99            header: MessageHeader::new(msg_type, session),
100            parent_header: None,
101            metadata: HashMap::new(),
102            content,
103            buffers: Vec::new(),
104        }
105    }
106
107    /// Create a reply message to a parent
108    pub fn reply(parent: &JupyterMessage, msg_type: MessageType, content: JsonValue) -> Self {
109        Self {
110            header: MessageHeader::new(msg_type, &parent.header.session),
111            parent_header: Some(parent.header.clone()),
112            metadata: HashMap::new(),
113            content,
114            buffers: Vec::new(),
115        }
116    }
117
118    /// Serialize message to JSON
119    pub fn to_json(&self) -> serde_json::Result<String> {
120        serde_json::to_string(self)
121    }
122
123    /// Deserialize message from JSON
124    pub fn from_json(json: &str) -> serde_json::Result<Self> {
125        serde_json::from_str(json)
126    }
127}
128
129/// Execute request content
130#[derive(Debug, Clone, Serialize, Deserialize)]
131pub struct ExecuteRequest {
132    /// Source code to execute
133    pub code: String,
134    /// Whether to store this execution in history
135    pub silent: bool,
136    /// Whether to store intermediate results
137    pub store_history: bool,
138    /// User variables to be made available
139    #[serde(default)]
140    pub user_expressions: HashMap<String, String>,
141    /// Whether to allow stdin requests
142    pub allow_stdin: bool,
143    /// Whether to stop on error
144    #[serde(default)]
145    pub stop_on_error: bool,
146}
147
148/// Execute reply content
149#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct ExecuteReply {
151    /// Execution status
152    pub status: ExecutionStatus,
153    /// Execution counter
154    pub execution_count: u64,
155    /// User expressions results (if requested)
156    #[serde(default)]
157    pub user_expressions: HashMap<String, JsonValue>,
158    /// Payload for additional actions
159    #[serde(default)]
160    pub payload: Vec<JsonValue>,
161}
162
163/// Execution status
164#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
165#[serde(rename_all = "lowercase")]
166pub enum ExecutionStatus {
167    Ok,
168    Error,
169    Abort,
170}
171
172/// Kernel info reply content
173#[derive(Debug, Clone, Serialize, Deserialize)]
174pub struct KernelInfoReply {
175    /// Protocol version
176    pub protocol_version: String,
177    /// Implementation name
178    pub implementation: String,
179    /// Implementation version
180    pub implementation_version: String,
181    /// Language information
182    pub language_info: LanguageInfo,
183    /// Kernel banner
184    pub banner: String,
185    /// Debugging support
186    #[serde(default)]
187    pub debugger: bool,
188    /// Help links
189    #[serde(default)]
190    pub help_links: Vec<HelpLink>,
191}
192
193#[derive(Debug, Clone, Serialize, Deserialize)]
194pub struct LanguageInfo {
195    /// Language name
196    pub name: String,
197    /// Language version
198    pub version: String,
199    /// MIME type for code
200    pub mimetype: String,
201    /// File extension
202    pub file_extension: String,
203    /// Pygments lexer name
204    pub pygments_lexer: String,
205    /// CodeMirror mode
206    pub codemirror_mode: String,
207}
208
209#[derive(Debug, Clone, Serialize, Deserialize)]
210pub struct HelpLink {
211    /// Link text
212    pub text: String,
213    /// Link URL
214    pub url: String,
215}
216
217/// Kernel status message content
218#[derive(Debug, Clone, Serialize, Deserialize)]
219pub struct Status {
220    /// Current execution state
221    pub execution_state: ExecutionState,
222}
223
224/// Kernel execution state
225#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
226#[serde(rename_all = "lowercase")]
227pub enum ExecutionState {
228    Starting,
229    Idle,
230    Busy,
231}
232
233/// Stream output content
234#[derive(Debug, Clone, Serialize, Deserialize)]
235pub struct Stream {
236    /// Stream name (stdout, stderr)
237    pub name: String,
238    /// Stream text content
239    pub text: String,
240}
241
242/// Error content
243#[derive(Debug, Clone, Serialize, Deserialize)]
244pub struct ErrorContent {
245    /// Error name/type
246    pub ename: String,
247    /// Error value/message
248    pub evalue: String,
249    /// Error traceback
250    pub traceback: Vec<String>,
251}
252
253/// Execute result content
254#[derive(Debug, Clone, Serialize, Deserialize)]
255pub struct ExecuteResult {
256    /// Execution counter
257    pub execution_count: u64,
258    /// Result data in various formats
259    pub data: HashMap<String, JsonValue>,
260    /// Result metadata
261    #[serde(default)]
262    pub metadata: HashMap<String, JsonValue>,
263}
264
265#[cfg(test)]
266mod tests {
267    use super::*;
268
269    #[test]
270    fn test_message_creation() {
271        let content = serde_json::json!({"code": "x = 1 + 2"});
272        let msg = JupyterMessage::new(MessageType::ExecuteRequest, "test-session", content);
273
274        assert_eq!(msg.header.msg_type, MessageType::ExecuteRequest);
275        assert_eq!(msg.header.session, "test-session");
276        assert!(!msg.header.msg_id.is_empty());
277        assert!(msg.parent_header.is_none());
278    }
279
280    #[test]
281    fn test_reply_message() {
282        let request_content = serde_json::json!({"code": "x = 1"});
283        let request = JupyterMessage::new(MessageType::ExecuteRequest, "test", request_content);
284
285        let reply_content = serde_json::json!({"status": "ok"});
286        let reply = JupyterMessage::reply(&request, MessageType::ExecuteReply, reply_content);
287
288        assert_eq!(reply.header.msg_type, MessageType::ExecuteReply);
289        assert_eq!(reply.header.session, "test");
290        assert!(reply.parent_header.is_some());
291        assert_eq!(reply.parent_header.unwrap().msg_id, request.header.msg_id);
292    }
293
294    #[test]
295    fn test_execute_request_serialization() {
296        let execute_req = ExecuteRequest {
297            code: "disp('hello')".to_string(),
298            silent: false,
299            store_history: true,
300            user_expressions: HashMap::new(),
301            allow_stdin: false,
302            stop_on_error: true,
303        };
304
305        let json = serde_json::to_string(&execute_req).unwrap();
306        let parsed: ExecuteRequest = serde_json::from_str(&json).unwrap();
307
308        assert_eq!(execute_req.code, parsed.code);
309        assert_eq!(execute_req.silent, parsed.silent);
310    }
311
312    #[test]
313    fn test_message_json_roundtrip() {
314        let content = serde_json::json!({
315            "code": "x = magic(3)",
316            "silent": false
317        });
318
319        let original = JupyterMessage::new(MessageType::ExecuteRequest, "test", content);
320        let json = original.to_json().unwrap();
321        let parsed = JupyterMessage::from_json(&json).unwrap();
322
323        assert_eq!(original.header.msg_type, parsed.header.msg_type);
324        assert_eq!(original.header.session, parsed.header.session);
325        assert_eq!(original.content, parsed.content);
326    }
327
328    #[test]
329    fn test_status_message() {
330        let status = Status {
331            execution_state: ExecutionState::Busy,
332        };
333
334        let content = serde_json::to_value(&status).unwrap();
335        let msg = JupyterMessage::new(MessageType::Status, "test", content);
336
337        assert_eq!(msg.header.msg_type, MessageType::Status);
338    }
339}