1use serde::{Deserialize, Serialize};
7use serde_json::Value as JsonValue;
8use std::collections::HashMap;
9use uuid::Uuid;
10
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
13#[serde(rename_all = "snake_case")]
14pub enum MessageType {
15 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 ShutdownRequest,
31 ShutdownReply,
32 InterruptRequest,
33 InterruptReply,
34
35 Status,
37 Stream,
38 DisplayData,
39 ExecuteInput,
40 ExecuteResult,
41 Error,
42
43 InputRequest,
45 InputReply,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct JupyterMessage {
51 pub header: MessageHeader,
53 pub parent_header: Option<MessageHeader>,
55 pub metadata: HashMap<String, JsonValue>,
57 pub content: JsonValue,
59 #[serde(skip_serializing_if = "Vec::is_empty", default)]
61 pub buffers: Vec<Vec<u8>>,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct MessageHeader {
67 pub msg_id: String,
69 pub msg_type: MessageType,
71 pub session: String,
73 pub date: String,
75 pub version: String,
77 pub username: String,
79}
80
81impl MessageHeader {
82 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 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 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 pub fn to_json(&self) -> serde_json::Result<String> {
120 serde_json::to_string(self)
121 }
122
123 pub fn from_json(json: &str) -> serde_json::Result<Self> {
125 serde_json::from_str(json)
126 }
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize)]
131pub struct ExecuteRequest {
132 pub code: String,
134 pub silent: bool,
136 pub store_history: bool,
138 #[serde(default)]
140 pub user_expressions: HashMap<String, String>,
141 pub allow_stdin: bool,
143 #[serde(default)]
145 pub stop_on_error: bool,
146}
147
148#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct ExecuteReply {
151 pub status: ExecutionStatus,
153 pub execution_count: u64,
155 #[serde(default)]
157 pub user_expressions: HashMap<String, JsonValue>,
158 #[serde(default)]
160 pub payload: Vec<JsonValue>,
161}
162
163#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
165#[serde(rename_all = "lowercase")]
166pub enum ExecutionStatus {
167 Ok,
168 Error,
169 Abort,
170}
171
172#[derive(Debug, Clone, Serialize, Deserialize)]
174pub struct KernelInfoReply {
175 pub protocol_version: String,
177 pub implementation: String,
179 pub implementation_version: String,
181 pub language_info: LanguageInfo,
183 pub banner: String,
185 #[serde(default)]
187 pub debugger: bool,
188 #[serde(default)]
190 pub help_links: Vec<HelpLink>,
191}
192
193#[derive(Debug, Clone, Serialize, Deserialize)]
194pub struct LanguageInfo {
195 pub name: String,
197 pub version: String,
199 pub mimetype: String,
201 pub file_extension: String,
203 pub pygments_lexer: String,
205 pub codemirror_mode: String,
207}
208
209#[derive(Debug, Clone, Serialize, Deserialize)]
210pub struct HelpLink {
211 pub text: String,
213 pub url: String,
215}
216
217#[derive(Debug, Clone, Serialize, Deserialize)]
219pub struct Status {
220 pub execution_state: ExecutionState,
222}
223
224#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
226#[serde(rename_all = "lowercase")]
227pub enum ExecutionState {
228 Starting,
229 Idle,
230 Busy,
231}
232
233#[derive(Debug, Clone, Serialize, Deserialize)]
235pub struct Stream {
236 pub name: String,
238 pub text: String,
240}
241
242#[derive(Debug, Clone, Serialize, Deserialize)]
244pub struct ErrorContent {
245 pub ename: String,
247 pub evalue: String,
249 pub traceback: Vec<String>,
251}
252
253#[derive(Debug, Clone, Serialize, Deserialize)]
255pub struct ExecuteResult {
256 pub execution_count: u64,
258 pub data: HashMap<String, JsonValue>,
260 #[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}