jupyter_protocol/
messaging.rs

1//! Defines the core message types and structures for the Jupyter messaging protocol.
2//!
3//! This module provides implementations for all message types specified in the
4//! [Jupyter Client documentation](https://jupyter-client.readthedocs.io/en/latest/messaging.html),
5//! including execute requests/replies, completion, inspection, and more.
6//!
7//! # Overview
8//!
9//! The Jupyter messaging protocol is a set of JSON-based message types used to communicate
10//! between Jupyter clients and kernels. This module provides Rust types and utilities to
11//! work with these messages in a type-safe manner.
12//!
13//! # Main Types
14//!
15//! - **[`JupyterMessage`]**: The top-level message structure, representing a complete Jupyter message.
16//! - **[`JupyterMessageContent`]**: An enum representing all possible message content types.
17//! - Various request and reply structures for specific message types (e.g., **[`ExecuteRequest`]**, **[`KernelInfoReply`]**).
18//!
19//! # Examples
20//!
21//! ## Creating an Execute Request
22//!
23//! ```rust
24//! use jupyter_protocol::{ExecuteRequest, JupyterMessage};
25//!
26//! // Create a new execute request with the code to be executed
27//! let execute_request = ExecuteRequest::new("print('Hello, world!')".to_string());
28//!
29//! // Convert the request into a JupyterMessage
30//! let message: JupyterMessage = execute_request.into();
31//! ```
32//!
33//! ## Handling a Received Message
34//!
35//! ```rust
36//! use jupyter_protocol::{JupyterMessage, JupyterMessageContent};
37//!
38//! fn handle_message(msg: JupyterMessage) {
39//!     match msg.content {
40//!         JupyterMessageContent::ExecuteRequest(req) => {
41//!             println!("Received execute request with code: {}", req.code);
42//!         },
43//!         JupyterMessageContent::KernelInfoRequest(_) => {
44//!             println!("Received kernel info request");
45//!         },
46//!         _ => println!("Received other message type"),
47//!     }
48//! }
49//! ```
50use crate::time;
51
52pub use crate::{
53    media::{Media, MediaType},
54    ExecutionCount,
55};
56
57use bytes::Bytes;
58use chrono::{DateTime, Utc};
59use serde::{Deserialize, Serialize};
60use serde_json::{json, Value};
61use std::{collections::HashMap, fmt};
62use uuid::Uuid;
63
64/// Represents the different channels in the Jupyter messaging protocol.
65///
66/// Each channel serves a specific purpose in the communication between
67/// Jupyter clients and kernels.
68///
69/// # Variants
70///
71/// - `Shell`: Used for request/reply-style messages.
72/// - `Control`: Similar to `Shell`, but for high-priority messages.
73/// - `Stdin`: Used for input requests from the kernel.
74/// - `IOPub`: Used for broadcasting results, errors, and other messages.
75/// - `Heartbeat`: Used to check the kernel's responsiveness.
76///
77/// # Example
78///
79/// ```rust
80/// use jupyter_protocol::messaging::Channel;
81///
82/// let channel = Channel::Shell;
83/// match channel {
84///     Channel::Shell => println!("Using the shell channel"),
85///     _ => println!("Using another channel"),
86/// }
87/// ```
88#[derive(Debug, Clone, Serialize, Deserialize)]
89#[serde(rename_all = "lowercase")]
90pub enum Channel {
91    /// Used for request/reply-style messages.
92    Shell,
93    /// Similar to `Shell`, but for high-priority messages.
94    Control,
95    /// Used for input requests from the kernel.
96    Stdin,
97    /// Used for broadcasting results, errors, and other messages.
98    IOPub,
99    /// Used to check the kernel's responsiveness.
100    Heartbeat,
101}
102
103#[derive(Serialize, Deserialize, Debug, Clone)]
104struct UnknownJupyterMessage {
105    pub header: Header,
106    pub parent_header: Option<Header>,
107    pub metadata: Value,
108    pub content: Value,
109    #[serde(skip_serializing, skip_deserializing)]
110    pub buffers: Vec<Bytes>,
111}
112
113/// Represents a Jupyter message header.
114///
115/// The header contains metadata about the message, such as its unique identifier,
116/// the username of the sender, and the message type.
117///
118/// # Fields
119///
120/// - `msg_id`: A unique identifier for the message.
121/// - `username`: The name of the user who sent the message.
122/// - `session`: The session identifier.
123/// - `date`: The timestamp when the message was created.
124/// - `msg_type`: The type of message (e.g., `execute_request`).
125/// - `version`: The version of the messaging protocol.
126///
127/// # Example
128///
129/// ```rust
130/// use jupyter_protocol::messaging::Header;
131///
132/// let header = Header {
133///     msg_id: "12345".to_string(),
134///     username: "user".to_string(),
135///     session: "session1".to_string(),
136///     date: chrono::DateTime::from_timestamp_nanos(1234567890),
137///     msg_type: "execute_request".to_string(),
138///     version: "5.3".to_string(),
139/// };
140/// ```
141#[derive(Serialize, Deserialize, Debug, Clone)]
142pub struct Header {
143    pub msg_id: String,
144    pub username: String,
145    pub session: String,
146    pub date: DateTime<Utc>,
147    pub msg_type: String,
148    pub version: String,
149}
150
151/// Serializes the `parent_header` of a `JupyterMessage`.
152///
153/// Treats `None` as an empty object to conform to Jupyter's messaging guidelines:
154///
155/// > If there is no parent, an empty dict should be used.
156/// >
157/// > — https://jupyter-client.readthedocs.io/en/latest/messaging.html#parent-header
158fn serialize_parent_header<S>(
159    parent_header: &Option<Header>,
160    serializer: S,
161) -> Result<S::Ok, S::Error>
162where
163    S: serde::Serializer,
164{
165    match parent_header {
166        Some(parent_header) => parent_header.serialize(serializer),
167        None => serde_json::Map::new().serialize(serializer),
168    }
169}
170
171/// Deserializes the `parent_header` of a `JupyterMessage`.
172/// 
173/// This function handles the case where the parent header is `None`
174/// or an empty object, and also allows for deserialization from
175/// non-object types.
176fn deserialize_parent_header<'de, D>(deserializer: D) -> Result<Option<Header>, D::Error>
177where
178    D: serde::Deserializer<'de>,
179{
180    use serde::de::Error;
181    let value = Value::deserialize(deserializer)?;
182    if value.is_null() {
183        Ok(None)
184    } else if let Some(obj) = value.as_object() {
185        if obj.is_empty() {
186            Ok(None)
187        } else {
188            // Try to deserialize as Header
189            serde_json::from_value(Value::Object(obj.clone()))
190                .map(Some)
191                .map_err(D::Error::custom)
192        }
193    } else {
194        // Try to deserialize as Header (for non-object types)
195        serde_json::from_value(value)
196            .map(Some)
197            .map_err(D::Error::custom)
198    }
199}
200
201/// A message in the Jupyter protocol format.
202///
203/// A Jupyter message consists of several parts:
204/// - `zmq_identities`: ZeroMQ identities used for routing (not serialized)
205/// - `header`: Metadata about the message
206/// - `parent_header`: Header from parent message, if this is a response
207/// - `metadata`: Additional metadata as JSON
208/// - `content`: The main message content
209/// - `buffers`: Binary buffers for messages that need them (not serialized)
210/// - `channel`: The communication channel this message belongs to
211///
212/// # Example
213///
214/// ```rust
215/// use jupyter_protocol::messaging::{JupyterMessage, JupyterMessageContent, ExecuteRequest};
216///
217/// // Create a new execute_request message
218/// let msg = JupyterMessage::new(
219///     ExecuteRequest {
220///         code: "print('Hello')".to_string(),
221///         silent: false,
222///         store_history: true,
223///         user_expressions: Default::default(),
224///         allow_stdin: true,
225///         stop_on_error: false,
226///     },
227///     None,
228/// );
229/// ```
230///
231/// Messages can be created as responses to other messages by passing the parent:
232///
233/// ```rust
234/// # use jupyter_protocol::messaging::{JupyterMessage, JupyterMessageContent, ReplyStatus, ExecuteRequest, ExecuteReply};
235/// # let parent = JupyterMessage::new(ExecuteRequest {
236/// #     code: "".to_string(), silent: false, store_history: true,
237/// #     user_expressions: Default::default(), allow_stdin: true, stop_on_error: false,
238/// # }, None);
239/// let reply = JupyterMessage::new(
240///     ExecuteReply {
241///         status: ReplyStatus::Ok,
242///         execution_count: jupyter_protocol::ExecutionCount::new(1),
243///         ..Default::default()
244///     },
245///     Some(&parent),
246/// );
247/// ```
248
249#[derive(Deserialize, Serialize, Clone)]
250pub struct JupyterMessage {
251    #[serde(skip_serializing, skip_deserializing)]
252    pub zmq_identities: Vec<Bytes>,
253    pub header: Header,
254    #[serde(
255        serialize_with = "serialize_parent_header",
256        deserialize_with = "deserialize_parent_header"
257    )]
258    pub parent_header: Option<Header>,
259    pub metadata: Value,
260    pub content: JupyterMessageContent,
261    #[serde(skip_serializing, skip_deserializing)]
262    pub buffers: Vec<Bytes>,
263    pub channel: Option<Channel>,
264}
265
266impl JupyterMessage {
267    pub fn new(
268        content: impl Into<JupyterMessageContent>,
269        parent: Option<&JupyterMessage>,
270    ) -> JupyterMessage {
271        // Normally a session ID is per client. A higher level wrapper on this API
272        // should probably create messages based on a `Session` struct that is stateful.
273        // For now, a user can create a message and then set the session ID directly.
274        let session = match parent {
275            Some(parent) => parent.header.session.clone(),
276            None => Uuid::new_v4().to_string(),
277        };
278
279        let content = content.into();
280
281        let header = Header {
282            msg_id: Uuid::new_v4().to_string(),
283            username: "runtimelib".to_string(),
284            session,
285            date: time::utc_now(),
286            msg_type: content.message_type().to_owned(),
287            version: "5.3".to_string(),
288        };
289
290        JupyterMessage {
291            zmq_identities: parent.map_or(Vec::new(), |parent| parent.zmq_identities.clone()),
292            header,
293            parent_header: parent.map(|parent| parent.header.clone()),
294            metadata: json!({}),
295            content,
296            buffers: Vec::new(),
297            channel: None,
298        }
299    }
300
301    pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
302        self.metadata = metadata;
303        self
304    }
305
306    pub fn with_buffers(mut self, buffers: Vec<Bytes>) -> Self {
307        self.buffers = buffers;
308        self
309    }
310
311    pub fn with_parent(mut self, parent: &JupyterMessage) -> Self {
312        self.header.session.clone_from(&parent.header.session);
313        self.parent_header = Some(parent.header.clone());
314        self.zmq_identities.clone_from(&parent.zmq_identities);
315        self
316    }
317
318    pub fn with_zmq_identities(mut self, zmq_identities: Vec<Bytes>) -> Self {
319        self.zmq_identities = zmq_identities;
320        self
321    }
322
323    pub fn with_session(mut self, session: &str) -> Self {
324        self.header.session = session.to_string();
325        self
326    }
327
328    pub fn message_type(&self) -> &str {
329        self.content.message_type()
330    }
331
332    pub fn from_value(message: Value) -> Result<JupyterMessage, anyhow::Error> {
333        let message = serde_json::from_value::<UnknownJupyterMessage>(message)?;
334
335        let content =
336            JupyterMessageContent::from_type_and_content(&message.header.msg_type, message.content);
337
338        let content = match content {
339            Ok(content) => content,
340            Err(err) => {
341                return Err(anyhow::anyhow!(
342                    "Error deserializing content for msg_type `{}`: {}",
343                    &message.header.msg_type,
344                    err
345                ));
346            }
347        };
348
349        let message = JupyterMessage {
350            zmq_identities: Vec::new(),
351            header: message.header,
352            parent_header: message.parent_header,
353            metadata: message.metadata,
354            content,
355            buffers: message.buffers,
356            channel: None,
357        };
358
359        Ok(message)
360    }
361}
362
363impl fmt::Debug for JupyterMessage {
364    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
365        writeln!(
366            f,
367            "\nHeader: {}",
368            serde_json::to_string_pretty(&self.header).unwrap()
369        )?;
370        writeln!(
371            f,
372            "Parent header: {}",
373            if let Some(parent_header) = self.parent_header.as_ref() {
374                serde_json::to_string_pretty(parent_header).unwrap()
375            } else {
376                serde_json::to_string_pretty(&serde_json::Map::new()).unwrap()
377            }
378        )?;
379        writeln!(
380            f,
381            "Metadata: {}",
382            serde_json::to_string_pretty(&self.metadata).unwrap()
383        )?;
384        writeln!(
385            f,
386            "Content: {}\n",
387            serde_json::to_string_pretty(&self.content).unwrap()
388        )?;
389        Ok(())
390    }
391}
392
393#[derive(Serialize, Deserialize, Debug, Clone)]
394#[serde(untagged)]
395pub enum JupyterMessageContent {
396    ClearOutput(ClearOutput),
397    CommClose(CommClose),
398    CommInfoReply(CommInfoReply),
399    CommInfoRequest(CommInfoRequest),
400    CommMsg(CommMsg),
401    CommOpen(CommOpen),
402    CompleteReply(CompleteReply),
403    CompleteRequest(CompleteRequest),
404    DebugReply(DebugReply),
405    DebugRequest(DebugRequest),
406    DisplayData(DisplayData),
407    ErrorOutput(ErrorOutput),
408    ExecuteInput(ExecuteInput),
409    ExecuteReply(ExecuteReply),
410    ExecuteRequest(ExecuteRequest),
411    ExecuteResult(ExecuteResult),
412    HistoryReply(HistoryReply),
413    HistoryRequest(HistoryRequest),
414    InputReply(InputReply),
415    InputRequest(InputRequest),
416    InspectReply(InspectReply),
417    InspectRequest(InspectRequest),
418    InterruptReply(InterruptReply),
419    InterruptRequest(InterruptRequest),
420    IsCompleteReply(IsCompleteReply),
421    IsCompleteRequest(IsCompleteRequest),
422    // This field is much larger than the most frequent ones
423    // so we box it.
424    KernelInfoReply(Box<KernelInfoReply>),
425    KernelInfoRequest(KernelInfoRequest),
426    ShutdownReply(ShutdownReply),
427    ShutdownRequest(ShutdownRequest),
428    Status(Status),
429    StreamContent(StreamContent),
430    UnknownMessage(UnknownMessage),
431    UpdateDisplayData(UpdateDisplayData),
432}
433
434impl JupyterMessageContent {
435    pub fn message_type(&self) -> &str {
436        match self {
437            JupyterMessageContent::ClearOutput(_) => "clear_output",
438            JupyterMessageContent::CommClose(_) => "comm_close",
439            JupyterMessageContent::CommInfoReply(_) => "comm_info_reply",
440            JupyterMessageContent::CommInfoRequest(_) => "comm_info_request",
441            JupyterMessageContent::CommMsg(_) => "comm_msg",
442            JupyterMessageContent::CommOpen(_) => "comm_open",
443            JupyterMessageContent::CompleteReply(_) => "complete_reply",
444            JupyterMessageContent::CompleteRequest(_) => "complete_request",
445            JupyterMessageContent::DebugReply(_) => "debug_reply",
446            JupyterMessageContent::DebugRequest(_) => "debug_request",
447            JupyterMessageContent::DisplayData(_) => "display_data",
448            JupyterMessageContent::ErrorOutput(_) => "error",
449            JupyterMessageContent::ExecuteInput(_) => "execute_input",
450            JupyterMessageContent::ExecuteReply(_) => "execute_reply",
451            JupyterMessageContent::ExecuteRequest(_) => "execute_request",
452            JupyterMessageContent::ExecuteResult(_) => "execute_result",
453            JupyterMessageContent::HistoryReply(_) => "history_reply",
454            JupyterMessageContent::HistoryRequest(_) => "history_request",
455            JupyterMessageContent::InputReply(_) => "input_reply",
456            JupyterMessageContent::InputRequest(_) => "input_request",
457            JupyterMessageContent::InspectReply(_) => "inspect_reply",
458            JupyterMessageContent::InspectRequest(_) => "inspect_request",
459            JupyterMessageContent::InterruptReply(_) => "interrupt_reply",
460            JupyterMessageContent::InterruptRequest(_) => "interrupt_request",
461            JupyterMessageContent::IsCompleteReply(_) => "is_complete_reply",
462            JupyterMessageContent::IsCompleteRequest(_) => "is_complete_request",
463            JupyterMessageContent::KernelInfoReply(_) => "kernel_info_reply",
464            JupyterMessageContent::KernelInfoRequest(_) => "kernel_info_request",
465            JupyterMessageContent::ShutdownReply(_) => "shutdown_reply",
466            JupyterMessageContent::ShutdownRequest(_) => "shutdown_request",
467            JupyterMessageContent::Status(_) => "status",
468            JupyterMessageContent::StreamContent(_) => "stream",
469            JupyterMessageContent::UnknownMessage(unk) => unk.msg_type.as_str(),
470            JupyterMessageContent::UpdateDisplayData(_) => "update_display_data",
471        }
472    }
473
474    pub fn from_type_and_content(msg_type: &str, content: Value) -> serde_json::Result<Self> {
475        match msg_type {
476            "clear_output" => Ok(JupyterMessageContent::ClearOutput(serde_json::from_value(
477                content,
478            )?)),
479
480            "comm_close" => Ok(JupyterMessageContent::CommClose(serde_json::from_value(
481                content,
482            )?)),
483
484            "comm_info_reply" => Ok(JupyterMessageContent::CommInfoReply(
485                serde_json::from_value(content)?,
486            )),
487            "comm_info_request" => Ok(JupyterMessageContent::CommInfoRequest(
488                serde_json::from_value(content)?,
489            )),
490
491            "comm_msg" => Ok(JupyterMessageContent::CommMsg(serde_json::from_value(
492                content,
493            )?)),
494            "comm_open" => Ok(JupyterMessageContent::CommOpen(serde_json::from_value(
495                content,
496            )?)),
497
498            "complete_reply" => Ok(JupyterMessageContent::CompleteReply(
499                serde_json::from_value(content)?,
500            )),
501            "complete_request" => Ok(JupyterMessageContent::CompleteRequest(
502                serde_json::from_value(content)?,
503            )),
504
505            "debug_reply" => Ok(JupyterMessageContent::DebugReply(serde_json::from_value(
506                content,
507            )?)),
508            "debug_request" => Ok(JupyterMessageContent::DebugRequest(serde_json::from_value(
509                content,
510            )?)),
511
512            "display_data" => Ok(JupyterMessageContent::DisplayData(serde_json::from_value(
513                content,
514            )?)),
515
516            "error" => Ok(JupyterMessageContent::ErrorOutput(serde_json::from_value(
517                content,
518            )?)),
519
520            "execute_input" => Ok(JupyterMessageContent::ExecuteInput(serde_json::from_value(
521                content,
522            )?)),
523
524            "execute_reply" => Ok(JupyterMessageContent::ExecuteReply(serde_json::from_value(
525                content,
526            )?)),
527            "execute_request" => Ok(JupyterMessageContent::ExecuteRequest(
528                serde_json::from_value(content)?,
529            )),
530
531            "execute_result" => Ok(JupyterMessageContent::ExecuteResult(
532                serde_json::from_value(content)?,
533            )),
534
535            "history_reply" => Ok(JupyterMessageContent::HistoryReply(serde_json::from_value(
536                content,
537            )?)),
538            "history_request" => Ok(JupyterMessageContent::HistoryRequest(
539                serde_json::from_value(content)?,
540            )),
541
542            "input_reply" => Ok(JupyterMessageContent::InputReply(serde_json::from_value(
543                content,
544            )?)),
545            "input_request" => Ok(JupyterMessageContent::InputRequest(serde_json::from_value(
546                content,
547            )?)),
548
549            "inspect_reply" => Ok(JupyterMessageContent::InspectReply(serde_json::from_value(
550                content,
551            )?)),
552            "inspect_request" => Ok(JupyterMessageContent::InspectRequest(
553                serde_json::from_value(content)?,
554            )),
555
556            "interrupt_reply" => Ok(JupyterMessageContent::InterruptReply(
557                serde_json::from_value(content)?,
558            )),
559            "interrupt_request" => Ok(JupyterMessageContent::InterruptRequest(
560                serde_json::from_value(content)?,
561            )),
562
563            "is_complete_reply" => Ok(JupyterMessageContent::IsCompleteReply(
564                serde_json::from_value(content)?,
565            )),
566            "is_complete_request" => Ok(JupyterMessageContent::IsCompleteRequest(
567                serde_json::from_value(content)?,
568            )),
569
570            "kernel_info_reply" => Ok(JupyterMessageContent::KernelInfoReply(
571                serde_json::from_value(content)?,
572            )),
573            "kernel_info_request" => Ok(JupyterMessageContent::KernelInfoRequest(
574                serde_json::from_value(content)?,
575            )),
576
577            "shutdown_reply" => Ok(JupyterMessageContent::ShutdownReply(
578                serde_json::from_value(content)?,
579            )),
580            "shutdown_request" => Ok(JupyterMessageContent::ShutdownRequest(
581                serde_json::from_value(content)?,
582            )),
583
584            "status" => Ok(JupyterMessageContent::Status(serde_json::from_value(
585                content,
586            )?)),
587
588            "stream" => Ok(JupyterMessageContent::StreamContent(
589                serde_json::from_value(content)?,
590            )),
591
592            "update_display_data" => Ok(JupyterMessageContent::UpdateDisplayData(
593                serde_json::from_value(content)?,
594            )),
595
596            _ => Ok(JupyterMessageContent::UnknownMessage(UnknownMessage {
597                msg_type: msg_type.to_string(),
598                content,
599            })),
600        }
601    }
602}
603
604macro_rules! impl_message_traits {
605    ($($name:ident),*) => {
606        $(
607            impl $name {
608                #[doc = concat!("Create a new `JupyterMessage`, assigning the parent for a `", stringify!($name), "` message.\n")]
609                ///
610                /// This method creates a new `JupyterMessage` with the right content, parent header, and zmq identities, making
611                /// it suitable for sending over ZeroMQ.
612                ///
613                /// # Example
614                /// ```rust
615                /// use jupyter_protocol::messaging::{JupyterMessage, JupyterMessageContent};
616                #[doc = concat!("use jupyter_protocol::", stringify!($name), ";\n")]
617                ///
618                /// let parent_message = JupyterMessage::new(jupyter_protocol::UnknownMessage {
619                ///   msg_type: "example".to_string(),
620                ///   content: serde_json::json!({ "key": "value" }),
621                /// }, None);
622                ///
623                #[doc = concat!("let child_message = ", stringify!($name), "{\n")]
624                ///   ..Default::default()
625                /// }.as_child_of(&parent_message);
626                ///
627                /// // Next you would send the `child_message` over the connection
628                ///
629                /// ```
630                #[must_use]
631                pub fn as_child_of(&self, parent: &JupyterMessage) -> JupyterMessage {
632                    JupyterMessage::new(self.clone(), Some(parent))
633                }
634            }
635
636            impl From<$name> for JupyterMessage {
637                #[doc(hidden)]
638                #[doc = concat!("Create a new `JupyterMessage` for a `", stringify!($name), "`.\n\n")]
639                /// ⚠️ If you use this method with `runtimelib`, you must set the zmq identities yourself. If you
640                /// have a message that "caused" your message to be sent, use that message with `as_child_of` instead.
641                #[must_use]
642                fn from(content: $name) -> Self {
643                    JupyterMessage::new(content, None)
644                }
645            }
646
647            impl From<$name> for JupyterMessageContent {
648                #[doc = concat!("Create a new `JupyterMessageContent` for a `", stringify!($name), "`.\n\n")]
649                #[must_use]
650                fn from(content: $name) -> Self {
651                    JupyterMessageContent::$name(content)
652                }
653            }
654        )*
655    };
656}
657
658impl From<JupyterMessageContent> for JupyterMessage {
659    fn from(content: JupyterMessageContent) -> Self {
660        JupyterMessage::new(content, None)
661    }
662}
663
664impl_message_traits!(
665    ClearOutput,
666    CommClose,
667    CommInfoReply,
668    CommInfoRequest,
669    CommMsg,
670    CommOpen,
671    CompleteReply,
672    CompleteRequest,
673    DebugReply,
674    DebugRequest,
675    DisplayData,
676    ErrorOutput,
677    ExecuteInput,
678    ExecuteReply,
679    ExecuteRequest,
680    ExecuteResult,
681    HistoryReply,
682    // HistoryRequest, // special case due to enum entry
683    InputReply,
684    InputRequest,
685    InspectReply,
686    InspectRequest,
687    InterruptReply,
688    InterruptRequest,
689    IsCompleteReply,
690    IsCompleteRequest,
691    // KernelInfoReply, // special case due to boxing
692    KernelInfoRequest,
693    ShutdownReply,
694    ShutdownRequest,
695    Status,
696    StreamContent,
697    UpdateDisplayData,
698    UnknownMessage
699);
700
701// KernelInfoReply is a special case due to the Boxing requirement
702impl KernelInfoReply {
703    pub fn as_child_of(&self, parent: &JupyterMessage) -> JupyterMessage {
704        JupyterMessage::new(
705            JupyterMessageContent::KernelInfoReply(Box::new(self.clone())),
706            Some(parent),
707        )
708    }
709}
710
711impl From<KernelInfoReply> for JupyterMessage {
712    fn from(content: KernelInfoReply) -> Self {
713        JupyterMessage::new(
714            JupyterMessageContent::KernelInfoReply(Box::new(content)),
715            None,
716        )
717    }
718}
719
720impl From<KernelInfoReply> for JupyterMessageContent {
721    fn from(content: KernelInfoReply) -> Self {
722        JupyterMessageContent::KernelInfoReply(Box::new(content))
723    }
724}
725
726impl HistoryRequest {
727    /// Create a new `JupyterMessage`, assigning the parent for a `HistoryRequest` message.
728    ///
729    /// This method creates a new `JupyterMessage` with the right content, parent header, and zmq identities, making
730    /// it suitable for sending over ZeroMQ.
731    ///
732    /// # Example
733    /// ```rust
734    /// use jupyter_protocol::messaging::{JupyterMessage, JupyterMessageContent};
735    /// use jupyter_protocol::HistoryRequest;
736    ///
737    /// let parent_message = JupyterMessage::new(jupyter_protocol::UnknownMessage {
738    ///   msg_type: "example".to_string(),
739    ///   content: serde_json::json!({ "key": "value" }),
740    /// }, None);
741    ///
742    /// let child_message = HistoryRequest::Range {
743    ///     session: None,
744    ///     start: 0,
745    ///     stop: 10,
746    ///     output: false,
747    ///     raw: false,
748    /// }.as_child_of(&parent_message);
749    ///
750    /// // Next you would send the `child_message` over the connection
751    /// ```
752    #[must_use]
753    pub fn as_child_of(&self, parent: &JupyterMessage) -> JupyterMessage {
754        JupyterMessage::new(self.clone(), Some(parent))
755    }
756}
757
758impl From<HistoryRequest> for JupyterMessage {
759    #[doc(hidden)]
760    /// Create a new `JupyterMessage` for a `HistoryRequest`.
761    /// ⚠️ If you use this method with `runtimelib`, you must set the zmq identities yourself. If you
762    /// have a message that "caused" your message to be sent, use that message with `as_child_of` instead.
763    #[must_use]
764    fn from(content: HistoryRequest) -> Self {
765        JupyterMessage::new(content, None)
766    }
767}
768
769impl From<HistoryRequest> for JupyterMessageContent {
770    /// Create a new `JupyterMessageContent` for a `HistoryRequest`.
771    #[must_use]
772    fn from(content: HistoryRequest) -> Self {
773        JupyterMessageContent::HistoryRequest(content)
774    }
775}
776
777/// Unknown message types are a workaround for generically unknown messages.
778///
779/// ```rust
780/// use jupyter_protocol::messaging::{JupyterMessage, JupyterMessageContent, UnknownMessage};
781/// use serde_json::json;
782///
783/// let msg = UnknownMessage {
784///     msg_type: "example_request".to_string(),
785///     content: json!({ "key": "value" }),
786/// };
787///
788/// let reply_msg = msg.reply(json!({ "status": "ok" }));
789/// ```
790///
791#[derive(Serialize, Deserialize, Debug, Clone)]
792pub struct UnknownMessage {
793    #[serde(skip_serializing, skip_deserializing)]
794    pub msg_type: String,
795    #[serde(flatten)]
796    pub content: Value,
797}
798impl Default for UnknownMessage {
799    fn default() -> Self {
800        Self {
801            msg_type: "unknown".to_string(),
802            content: Value::Null,
803        }
804    }
805}
806
807impl UnknownMessage {
808    // Create a reply message for an unknown message, assuming `content` is known.
809    // Useful for when runtimelib does not support the message type.
810    // Send a PR to add support for the message type!
811    pub fn reply(&self, content: serde_json::Value) -> JupyterMessageContent {
812        JupyterMessageContent::UnknownMessage(UnknownMessage {
813            msg_type: self.msg_type.replace("_request", "_reply"),
814            content,
815        })
816    }
817}
818
819/// All reply messages have a `status` field.
820#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
821#[serde(rename_all = "lowercase")]
822pub enum ReplyStatus {
823    #[default]
824    Ok,
825    Error,
826    Aborted,
827}
828
829#[derive(Serialize, Deserialize, Debug, Clone, Default)]
830pub struct ReplyError {
831    pub ename: String,
832    pub evalue: String,
833    pub traceback: Vec<String>,
834}
835
836/// Clear output of a single cell / output area.
837#[derive(Serialize, Deserialize, Debug, Clone, Default)]
838pub struct ClearOutput {
839    /// Wait to clear the output until new output is available.  Clears the
840    /// existing output immediately before the new output is displayed.
841    /// Useful for creating simple animations with minimal flickering.
842    pub wait: bool,
843}
844
845/// A request for code execution.
846///
847/// See <https://jupyter-client.readthedocs.io/en/latest/messaging.html#execute>
848#[derive(Serialize, Deserialize, Debug, Clone)]
849pub struct ExecuteRequest {
850    pub code: String,
851    pub silent: bool,
852    pub store_history: bool,
853    #[serde(serialize_with = "serialize_user_expressions")]
854    pub user_expressions: Option<HashMap<String, String>>,
855    #[serde(default = "default_allow_stdin")]
856    pub allow_stdin: bool,
857    #[serde(default = "default_stop_on_error")]
858    pub stop_on_error: bool,
859}
860
861/// Serializes the `user_expressions`.
862///
863/// Treats `None` as an empty object to conform to Jupyter's messaging guidelines.
864fn serialize_user_expressions<S>(
865    user_expressions: &Option<HashMap<String, String>>,
866    serializer: S,
867) -> Result<S::Ok, S::Error>
868where
869    S: serde::Serializer,
870{
871    match user_expressions {
872        Some(user_expressions) => user_expressions.serialize(serializer),
873        None => serde_json::Map::new().serialize(serializer),
874    }
875}
876
877fn default_allow_stdin() -> bool {
878    false
879}
880
881fn default_stop_on_error() -> bool {
882    true
883}
884
885impl ExecuteRequest {
886    pub fn new(code: String) -> Self {
887        Self {
888            code,
889            ..Default::default()
890        }
891    }
892}
893
894impl Default for ExecuteRequest {
895    fn default() -> Self {
896        Self {
897            code: "".to_string(),
898            silent: false,
899            store_history: true,
900            user_expressions: None,
901            allow_stdin: false,
902            stop_on_error: true,
903        }
904    }
905}
906
907/// A reply to an execute request. This is not the output of execution, as this is the reply over
908/// the `shell` socket. Any number of outputs can be emitted as `StreamContent`, `DisplayData`,
909/// `UpdateDisplayData`, `ExecuteResult`, and `ErrorOutput`. This message is used to communicate
910/// the status of the execution request, the execution count, and any user expressions that
911/// were requested.
912///
913/// See <https://jupyter-client.readthedocs.io/en/latest/messaging.html#execution-results>
914#[derive(Serialize, Deserialize, Debug, Clone)]
915pub struct ExecuteReply {
916    pub status: ReplyStatus,
917    pub execution_count: ExecutionCount,
918
919    #[serde(default)]
920    pub payload: Vec<Payload>,
921    pub user_expressions: Option<HashMap<String, String>>,
922
923    #[serde(flatten, skip_serializing_if = "Option::is_none")]
924    pub error: Option<Box<ReplyError>>,
925}
926impl Default for ExecuteReply {
927    fn default() -> Self {
928        Self {
929            status: ReplyStatus::Ok,
930            execution_count: ExecutionCount::new(0),
931            payload: Vec::new(),
932            user_expressions: None,
933            error: None,
934        }
935    }
936}
937
938/// Payloads are a way to trigger frontend actions from the kernel.
939/// They are stated as deprecated, however they are in regular use via `?` in IPython
940///
941/// See <https://jupyter-client.readthedocs.io/en/latest/messaging.html#payloads-deprecated>
942#[derive(Serialize, Deserialize, Debug, Clone)]
943#[serde(rename_all = "snake_case")]
944#[serde(tag = "source")]
945pub enum Payload {
946    Page {
947        data: Media,
948        start: usize,
949    },
950    SetNextInput {
951        text: String,
952        replace: bool,
953    },
954    EditMagic {
955        filename: String,
956        line_number: usize,
957    },
958    AskExit {
959        // sic
960        keepkernel: bool,
961    },
962}
963
964/// A request for information about the kernel.
965///
966/// See <https://jupyter-client.readthedocs.io/en/latest/messaging.html#kernel-info>
967#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
968pub struct KernelInfoRequest {}
969
970/// A reply containing information about the kernel.
971///
972/// See <https://jupyter-client.readthedocs.io/en/latest/messaging.html#kernel-info>
973#[derive(Serialize, Deserialize, Debug, Clone)]
974pub struct KernelInfoReply {
975    pub status: ReplyStatus,
976    pub protocol_version: String,
977    pub implementation: String,
978    pub implementation_version: String,
979    pub language_info: LanguageInfo,
980    pub banner: String,
981    pub help_links: Vec<HelpLink>,
982    #[serde(default = "default_debugger")]
983    pub debugger: bool,
984    #[serde(flatten, skip_serializing_if = "Option::is_none")]
985    pub error: Option<Box<ReplyError>>,
986}
987
988fn default_debugger() -> bool {
989    false
990}
991
992#[derive(Serialize, Deserialize, Debug, Clone)]
993#[serde(untagged)]
994pub enum CodeMirrorMode {
995    Simple(String),
996    CustomMode { name: String, version: usize },
997}
998
999#[derive(Serialize, Deserialize, Debug, Clone)]
1000pub struct CodeMirrorModeObject {
1001    pub name: String,
1002    pub version: usize,
1003}
1004
1005impl CodeMirrorMode {
1006    pub fn typescript() -> Self {
1007        Self::Simple("typescript".to_string())
1008    }
1009
1010    pub fn python() -> Self {
1011        Self::Simple("python".to_string())
1012    }
1013
1014    pub fn ipython_code_mirror_mode() -> Self {
1015        Self::CustomMode {
1016            name: "ipython".to_string(),
1017            version: 3,
1018        }
1019    }
1020}
1021
1022#[derive(Serialize, Deserialize, Debug, Clone)]
1023pub struct LanguageInfo {
1024    pub name: String,
1025    pub version: String,
1026    pub mimetype: String,
1027    pub file_extension: String,
1028    pub pygments_lexer: String,
1029    pub codemirror_mode: CodeMirrorMode,
1030    pub nbconvert_exporter: String,
1031}
1032
1033#[derive(Serialize, Deserialize, Debug, Clone)]
1034pub struct HelpLink {
1035    pub text: String,
1036    pub url: String,
1037}
1038
1039#[derive(Serialize, Deserialize, Debug, Clone)]
1040pub enum Stdio {
1041    #[serde(rename = "stdout")]
1042    Stdout,
1043    #[serde(rename = "stderr")]
1044    Stderr,
1045}
1046
1047/// A `stream` message on the `iopub` channel. These are also known as "stdout" and "stderr".
1048///
1049/// See [Streams](https://jupyter-client.readthedocs.io/en/latest/messaging.html#streams-stdout-stderr-etc).
1050///
1051/// ## Example
1052/// The UI/client sends an `execute_request` message to the kernel.
1053///
1054/// ```rust
1055/// use jupyter_protocol::{ExecuteRequest, JupyterMessage};
1056/// // The UI/client sends an `execute_request` message to the kernel.
1057///
1058/// let execute_request = ExecuteRequest {
1059///     code: "print('Hello, World!')".to_string(),
1060///     silent: false,
1061///     store_history: true,
1062///     user_expressions: None,
1063///     allow_stdin: false,
1064///     stop_on_error: true,
1065/// };
1066///
1067/// let incoming_message: JupyterMessage = execute_request.into();
1068///
1069/// // ...
1070///
1071///
1072/// // On the kernel side, we receive the `execute_request` message.
1073/// //
1074/// // As a side effect of execution, the kernel can send `stream` messages to the UI/client.
1075/// // These are from using `print()`, `console.log()`, or similar. Anything on STDOUT or STDERR.
1076///
1077/// use jupyter_protocol::{StreamContent, Stdio};
1078///
1079/// let message = StreamContent {
1080///   name: Stdio::Stdout,
1081///   text: "Hello, World".to_string()
1082/// // To inform the UI that the kernel is emitting this stdout in response to the execution, we
1083/// // use `as_child_of` to set the parent header.
1084/// }.as_child_of(&incoming_message);
1085///
1086/// // next, send the `message` back over the iopub connection
1087/// ```
1088#[derive(Serialize, Deserialize, Debug, Clone)]
1089pub struct StreamContent {
1090    pub name: Stdio,
1091    pub text: String,
1092}
1093impl Default for StreamContent {
1094    fn default() -> Self {
1095        Self {
1096            name: Stdio::Stdout,
1097            text: String::new(),
1098        }
1099    }
1100}
1101
1102impl StreamContent {
1103    pub fn stdout(text: &str) -> Self {
1104        Self {
1105            name: Stdio::Stdout,
1106            text: text.to_string(),
1107        }
1108    }
1109
1110    pub fn stderr(text: &str) -> Self {
1111        Self {
1112            name: Stdio::Stderr,
1113            text: text.to_string(),
1114        }
1115    }
1116}
1117
1118/// Optional metadata for a display data to allow for updating an output.
1119#[derive(Serialize, Deserialize, Debug, Clone, Default)]
1120pub struct Transient {
1121    #[serde(skip_serializing_if = "Option::is_none")]
1122    pub display_id: Option<String>,
1123}
1124
1125/// A `display_data` message on the `iopub` channel.
1126///
1127/// See [Display Data](https://jupyter-client.readthedocs.io/en/latest/messaging.html#display-data).
1128///
1129/// ## Example
1130///
1131/// The UI/client sends an `execute_request` message to the kernel.
1132///
1133/// ```rust
1134/// use jupyter_protocol::{ExecuteRequest, JupyterMessage};
1135///
1136/// let execute_request: JupyterMessage = ExecuteRequest {
1137///     code: "print('Hello, World!')".to_string(),
1138///     silent: false,
1139///     store_history: true,
1140///     user_expressions: None,
1141///     allow_stdin: false,
1142///     stop_on_error: true,
1143/// }.into();
1144///
1145/// // As a side effect of execution, the kernel can send `display_data` messages to the UI/client.
1146///
1147/// use jupyter_protocol::{DisplayData, Media, MediaType};
1148///
1149/// let raw = r#"{
1150///     "text/plain": "Hello, world!",
1151///     "text/html": "<h1>Hello, world!</h1>"
1152/// }"#;
1153///
1154/// let bundle: Media = serde_json::from_str(raw).unwrap();
1155///
1156/// let message = DisplayData {
1157///    data: bundle,
1158///    metadata: Default::default(),
1159///    transient: None,
1160/// }.as_child_of(&execute_request);
1161/// // Send back the response over the iopub connection
1162///
1163/// ```
1164#[derive(Serialize, Deserialize, Debug, Clone, Default)]
1165pub struct DisplayData {
1166    pub data: Media,
1167    pub metadata: serde_json::Map<String, Value>,
1168    #[serde(default, skip_serializing_if = "Option::is_none")]
1169    pub transient: Option<Transient>,
1170}
1171
1172impl DisplayData {
1173    pub fn new(data: Media) -> Self {
1174        Self {
1175            data,
1176            metadata: Default::default(),
1177            transient: Default::default(),
1178        }
1179    }
1180}
1181
1182impl From<Vec<MediaType>> for DisplayData {
1183    fn from(content: Vec<MediaType>) -> Self {
1184        Self::new(Media::new(content))
1185    }
1186}
1187
1188impl From<MediaType> for DisplayData {
1189    fn from(content: MediaType) -> Self {
1190        Self::new(Media::new(vec![content]))
1191    }
1192}
1193
1194/// An `update_display_data` message on the `iopub` channel.
1195/// See [Update Display Data](https://jupyter-client.readthedocs.io/en/latest/messaging.html#update-display-data).
1196#[derive(Serialize, Deserialize, Debug, Clone, Default)]
1197pub struct UpdateDisplayData {
1198    pub data: Media,
1199    pub metadata: serde_json::Map<String, Value>,
1200    pub transient: Transient,
1201}
1202
1203impl UpdateDisplayData {
1204    pub fn new(data: Media, display_id: &str) -> Self {
1205        Self {
1206            data,
1207            metadata: Default::default(),
1208            transient: Transient {
1209                display_id: Some(display_id.to_string()),
1210            },
1211        }
1212    }
1213}
1214
1215/// An `execute_input` message on the `iopub` channel.
1216/// See [Execute Input](https://jupyter-client.readthedocs.io/en/latest/messaging.html#execute-input).
1217///
1218/// To let all frontends know what code is being executed at any given time, these messages contain a re-broadcast of the code portion of an execute_request, along with the execution_count.
1219///
1220#[derive(Serialize, Deserialize, Debug, Clone)]
1221pub struct ExecuteInput {
1222    pub code: String,
1223    pub execution_count: ExecutionCount,
1224}
1225impl Default for ExecuteInput {
1226    fn default() -> Self {
1227        Self {
1228            code: String::new(),
1229            execution_count: ExecutionCount::new(0),
1230        }
1231    }
1232}
1233
1234/// An `execute_result` message on the `iopub` channel.
1235/// See [Execute Result](https://jupyter-client.readthedocs.io/en/latest/messaging.html#execute-result).
1236///
1237/// The is the "result", in the REPL sense from execution. As an example, the following Python code:
1238///
1239/// ```python
1240/// >>> 3 + 4
1241/// 7
1242/// ```
1243///
1244/// would have an `execute_result` message with the following content:
1245///
1246/// ```json
1247/// {
1248///     "execution_count": 1,
1249///     "data": {
1250///         "text/plain": "7"
1251///     },
1252///     "metadata": {},
1253///     "transient": {}
1254/// }
1255/// ```
1256///
1257#[derive(Serialize, Deserialize, Debug, Clone)]
1258pub struct ExecuteResult {
1259    pub execution_count: ExecutionCount,
1260    pub data: Media,
1261    pub metadata: serde_json::Map<String, Value>,
1262    pub transient: Option<Transient>,
1263}
1264impl Default for ExecuteResult {
1265    fn default() -> Self {
1266        Self {
1267            execution_count: ExecutionCount::new(0),
1268            data: Media::default(),
1269            metadata: serde_json::Map::new(),
1270            transient: None,
1271        }
1272    }
1273}
1274
1275impl ExecuteResult {
1276    pub fn new(execution_count: ExecutionCount, data: Media) -> Self {
1277        Self {
1278            execution_count,
1279            data,
1280            metadata: Default::default(),
1281            transient: None,
1282        }
1283    }
1284}
1285
1286impl From<(ExecutionCount, Vec<MediaType>)> for ExecuteResult {
1287    fn from((execution_count, content): (ExecutionCount, Vec<MediaType>)) -> Self {
1288        Self::new(execution_count, content.into())
1289    }
1290}
1291
1292impl From<(ExecutionCount, MediaType)> for ExecuteResult {
1293    fn from((execution_count, content): (ExecutionCount, MediaType)) -> Self {
1294        Self::new(execution_count, content.into())
1295    }
1296}
1297
1298/// An `error` message on the `iopub` channel.
1299/// See [Error](https://jupyter-client.readthedocs.io/en/latest/messaging.html#execution-errors).
1300///
1301/// These are errors that occur during execution from user code. Syntax errors, runtime errors, etc.
1302///
1303#[derive(Serialize, Deserialize, Debug, Clone, Default)]
1304pub struct ErrorOutput {
1305    pub ename: String,
1306    pub evalue: String,
1307    pub traceback: Vec<String>,
1308}
1309
1310/// A `comm_open` message on the `iopub` channel.
1311///
1312/// See [Comm Open](https://jupyter-client.readthedocs.io/en/latest/messaging.html#opening-a-comm).
1313///
1314/// Comm messages are one-way communications to update comm state, used for
1315/// synchronizing widget state, or simply requesting actions of a comm’s
1316/// counterpart.
1317///
1318/// Opening a Comm produces a `comm_open` message, to be sent to the other side:
1319///
1320/// ```json
1321/// {
1322///   "comm_id": "u-u-i-d",
1323///   "target_name": "my_comm",
1324///   "data": {}
1325/// }
1326/// ```
1327///
1328/// Every Comm has an ID and a target name. The code handling the message on
1329/// the receiving side is responsible for maintaining a mapping of target_name
1330/// keys to constructors. After a `comm_open` message has been sent, there
1331/// should be a corresponding Comm instance on both sides. The data key is
1332/// always a object with any extra JSON information used in initialization of
1333/// the comm.
1334///
1335/// If the `target_name` key is not found on the receiving side, then it should
1336/// immediately reply with a `comm_close` message to avoid an inconsistent state.
1337#[derive(Serialize, Deserialize, Debug, Clone)]
1338pub struct CommOpen {
1339    pub comm_id: CommId,
1340    pub target_name: String,
1341    pub data: serde_json::Map<String, Value>,
1342}
1343impl Default for CommOpen {
1344    fn default() -> Self {
1345        Self {
1346            comm_id: CommId("".to_string()),
1347            target_name: String::new(),
1348            data: serde_json::Map::new(),
1349        }
1350    }
1351}
1352
1353/// A `comm_msg` message on the `iopub` channel.
1354///
1355/// Comm messages are one-way communications to update comm state, used for
1356/// synchronizing widget state, or simply requesting actions of a comm’s
1357/// counterpart.
1358///
1359/// Essentially, each comm pair defines their own message specification
1360/// implemented inside the data object.
1361///
1362/// There are no expected replies.
1363///
1364/// ```json
1365/// {
1366///   "comm_id": "u-u-i-d",
1367///   "data": {}
1368/// }
1369/// ```
1370///
1371#[derive(Serialize, Deserialize, Debug, Clone)]
1372pub struct CommMsg {
1373    pub comm_id: CommId,
1374    pub data: serde_json::Map<String, Value>,
1375}
1376impl Default for CommMsg {
1377    fn default() -> Self {
1378        Self {
1379            comm_id: CommId("".to_string()),
1380            data: serde_json::Map::new(),
1381        }
1382    }
1383}
1384
1385#[derive(Serialize, Deserialize, Debug, Clone, Default)]
1386pub struct CommInfoRequest {
1387    pub target_name: String,
1388}
1389
1390#[derive(Eq, Hash, PartialEq, Serialize, Deserialize, Debug, Clone)]
1391pub struct CommId(pub String);
1392
1393impl From<CommId> for String {
1394    fn from(comm_id: CommId) -> Self {
1395        comm_id.0
1396    }
1397}
1398
1399impl From<String> for CommId {
1400    fn from(comm_id: String) -> Self {
1401        Self(comm_id)
1402    }
1403}
1404
1405#[derive(Serialize, Deserialize, Debug, Clone)]
1406pub struct CommInfo {
1407    pub target_name: String,
1408}
1409
1410#[derive(Serialize, Deserialize, Debug, Clone)]
1411pub struct CommInfoReply {
1412    pub status: ReplyStatus,
1413    pub comms: HashMap<CommId, CommInfo>,
1414    // pub comms: HashMap<CommId, CommInfo>,
1415    #[serde(flatten, skip_serializing_if = "Option::is_none")]
1416    pub error: Option<Box<ReplyError>>,
1417}
1418impl Default for CommInfoReply {
1419    fn default() -> Self {
1420        Self {
1421            status: ReplyStatus::Ok,
1422            comms: HashMap::new(),
1423            error: None,
1424        }
1425    }
1426}
1427
1428/// A `comm_close` message on the `iopub` channel.
1429///
1430/// Since comms live on both sides, when a comm is destroyed the other side must
1431/// be notified. This is done with a comm_close message.
1432#[derive(Serialize, Deserialize, Debug, Clone)]
1433pub struct CommClose {
1434    pub comm_id: CommId,
1435    pub data: serde_json::Map<String, Value>,
1436}
1437impl Default for CommClose {
1438    fn default() -> Self {
1439        Self {
1440            comm_id: CommId("".to_string()),
1441            data: serde_json::Map::new(),
1442        }
1443    }
1444}
1445
1446#[derive(Serialize, Deserialize, Debug, Clone, Default)]
1447/// Request to shut down the kernel.
1448///
1449/// Upon receiving this message, the kernel will send a reply and then shut itself down.
1450/// If `restart` is True, the kernel will restart itself after shutting down.
1451///
1452/// See <https://jupyter-client.readthedocs.io/en/latest/messaging.html#kernel-shutdown>
1453pub struct ShutdownRequest {
1454    pub restart: bool,
1455}
1456
1457#[derive(Serialize, Deserialize, Debug, Clone, Default)]
1458/// Request to interrupt the kernel.
1459///
1460/// This message is used when the kernel's `interrupt_mode` is set to "message"
1461/// in its kernelspec. It allows the kernel to be interrupted via a message
1462/// instead of an operating system signal.
1463///
1464/// See <https://jupyter-client.readthedocs.io/en/latest/messaging.html#kernel-interrupt>
1465pub struct InterruptRequest {}
1466
1467#[derive(Serialize, Deserialize, Debug, Clone)]
1468/// Reply to an interrupt request.
1469///
1470/// This message is sent by the kernel in response to an `InterruptRequest`.
1471/// It indicates whether the interrupt was successful.
1472///
1473/// See <https://jupyter-client.readthedocs.io/en/latest/messaging.html#kernel-interrupt>
1474pub struct InterruptReply {
1475    pub status: ReplyStatus,
1476
1477    #[serde(flatten, skip_serializing_if = "Option::is_none")]
1478    pub error: Option<Box<ReplyError>>,
1479}
1480
1481impl Default for InterruptReply {
1482    fn default() -> Self {
1483        Self::new()
1484    }
1485}
1486
1487impl InterruptReply {
1488    pub fn new() -> Self {
1489        Self {
1490            status: ReplyStatus::Ok,
1491            error: None,
1492        }
1493    }
1494}
1495
1496#[derive(Serialize, Deserialize, Debug, Clone)]
1497/// Reply to a shutdown request.
1498///
1499/// This message is sent by the kernel in response to a `ShutdownRequest`.
1500/// It confirms that the kernel is shutting down.
1501///
1502/// See <https://jupyter-client.readthedocs.io/en/latest/messaging.html#kernel-shutdown>
1503pub struct ShutdownReply {
1504    pub restart: bool,
1505    pub status: ReplyStatus,
1506
1507    #[serde(flatten, skip_serializing_if = "Option::is_none")]
1508    pub error: Option<Box<ReplyError>>,
1509}
1510impl Default for ShutdownReply {
1511    fn default() -> Self {
1512        Self {
1513            restart: false,
1514            status: ReplyStatus::Ok,
1515            error: None,
1516        }
1517    }
1518}
1519
1520#[derive(Serialize, Deserialize, Debug, Clone)]
1521/// Request for input from the frontend.
1522///
1523/// This message is sent by the kernel when it needs to prompt the user for input.
1524/// It's typically used to implement functions like Python's `input()` or R's `readline()`.
1525///
1526/// See <https://jupyter-client.readthedocs.io/en/latest/messaging.html#messages-on-the-stdin-router-dealer-channel>
1527pub struct InputRequest {
1528    pub prompt: String,
1529    pub password: bool,
1530}
1531impl Default for InputRequest {
1532    fn default() -> Self {
1533        Self {
1534            prompt: "> ".to_string(),
1535            password: false,
1536        }
1537    }
1538}
1539
1540#[derive(Serialize, Deserialize, Debug, Clone)]
1541/// Reply to an input request.
1542///
1543/// This message is sent by the frontend in response to an `InputRequest`.
1544/// It contains the user's input.
1545///
1546/// See <https://jupyter-client.readthedocs.io/en/latest/messaging.html#messages-on-the-stdin-router-dealer-channel>
1547pub struct InputReply {
1548    pub value: String,
1549
1550    pub status: ReplyStatus,
1551    #[serde(flatten, skip_serializing_if = "Option::is_none")]
1552    pub error: Option<Box<ReplyError>>,
1553}
1554impl Default for InputReply {
1555    fn default() -> Self {
1556        Self {
1557            value: String::new(),
1558            status: ReplyStatus::Ok,
1559            error: None,
1560        }
1561    }
1562}
1563
1564/// A `inspect_request` message on the `shell` channel.
1565///
1566/// Code can be inspected to show useful information to the user.
1567/// It is up to the Kernel to decide what information should be displayed, and its formatting.
1568///
1569#[derive(Serialize, Deserialize, Debug, Clone)]
1570pub struct InspectRequest {
1571    /// The code context in which introspection is requested
1572    /// this may be up to an entire multiline cell.
1573    pub code: String,
1574    /// The cursor position within 'code' (in unicode characters) where inspection is requested
1575    pub cursor_pos: usize,
1576    /// The level of detail desired.  In IPython, the default (0) is equivalent to typing
1577    /// 'x?' at the prompt, 1 is equivalent to 'x??'.
1578    /// The difference is up to kernels, but in IPython level 1 includes the source code
1579    /// if available.
1580    pub detail_level: Option<usize>,
1581}
1582impl Default for InspectRequest {
1583    fn default() -> Self {
1584        Self {
1585            code: String::new(),
1586            cursor_pos: 0,
1587            detail_level: Some(0),
1588        }
1589    }
1590}
1591
1592#[derive(Serialize, Deserialize, Debug, Clone)]
1593pub struct InspectReply {
1594    pub found: bool,
1595    pub data: Media,
1596    pub metadata: serde_json::Map<String, Value>,
1597
1598    pub status: ReplyStatus,
1599    #[serde(flatten, skip_serializing_if = "Option::is_none")]
1600    pub error: Option<Box<ReplyError>>,
1601}
1602impl Default for InspectReply {
1603    fn default() -> Self {
1604        Self {
1605            found: false,
1606            data: Media::default(),
1607            metadata: serde_json::Map::new(),
1608            status: ReplyStatus::Ok,
1609            error: None,
1610        }
1611    }
1612}
1613
1614/// A request for code completion suggestions.
1615///
1616/// See <https://jupyter-client.readthedocs.io/en/latest/messaging.html#completion>
1617#[derive(Serialize, Deserialize, Debug, Clone, Default)]
1618pub struct CompleteRequest {
1619    pub code: String,
1620    pub cursor_pos: usize,
1621}
1622
1623/// A reply containing code completion suggestions.
1624///
1625/// See <https://jupyter-client.readthedocs.io/en/latest/messaging.html#completion>
1626#[derive(Serialize, Deserialize, Debug, Clone)]
1627pub struct CompleteReply {
1628    pub matches: Vec<String>,
1629    pub cursor_start: usize,
1630    pub cursor_end: usize,
1631    pub metadata: serde_json::Map<String, Value>,
1632
1633    pub status: ReplyStatus,
1634    #[serde(flatten, skip_serializing_if = "Option::is_none")]
1635    pub error: Option<Box<ReplyError>>,
1636}
1637impl Default for CompleteReply {
1638    fn default() -> Self {
1639        Self {
1640            matches: Vec::new(),
1641            cursor_start: 0,
1642            cursor_end: 0,
1643            metadata: serde_json::Map::new(),
1644            status: ReplyStatus::Ok,
1645            error: None,
1646        }
1647    }
1648}
1649
1650#[derive(Serialize, Deserialize, Debug, Clone)]
1651pub struct DebugRequest {
1652    #[serde(flatten)]
1653    pub content: Value,
1654}
1655impl Default for DebugRequest {
1656    fn default() -> Self {
1657        Self {
1658            content: Value::Null,
1659        }
1660    }
1661}
1662
1663#[derive(Serialize, Deserialize, Debug, Clone)]
1664pub struct DebugReply {
1665    #[serde(flatten)]
1666    pub content: Value,
1667}
1668impl Default for DebugReply {
1669    fn default() -> Self {
1670        Self {
1671            content: Value::Null,
1672        }
1673    }
1674}
1675
1676#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
1677#[serde(rename_all = "snake_case")]
1678pub enum IsCompleteReplyStatus {
1679    /// The code is incomplete, and the frontend should prompt the user for more
1680    /// input.
1681    Incomplete,
1682    /// The code is ready to be executed.
1683    Complete,
1684    /// The code is invalid, yet can be sent for execution to see a syntax error.
1685    Invalid,
1686    /// The kernel is unable to determine status. The frontend should also
1687    /// handle the kernel not replying promptly. It may default to sending the
1688    /// code for execution, or it may implement simple fallback heuristics for
1689    /// whether to execute the code (e.g. execute after a blank line).
1690    Unknown,
1691}
1692
1693#[derive(Serialize, Deserialize, Debug, Clone)]
1694pub struct IsCompleteReply {
1695    /// Unlike other reply messages, the status is unique to this message, using `IsCompleteReplyStatus`
1696    /// instead of `ReplyStatus`.
1697    pub status: IsCompleteReplyStatus,
1698    /// If status is 'incomplete', indent should contain the characters to use
1699    /// to indent the next line. This is only a hint: frontends may ignore it
1700    /// and use their own autoindentation rules. For other statuses, this
1701    /// field does not exist.
1702    pub indent: String,
1703}
1704impl Default for IsCompleteReply {
1705    fn default() -> Self {
1706        Self {
1707            status: IsCompleteReplyStatus::Unknown,
1708            indent: String::new(),
1709        }
1710    }
1711}
1712
1713impl IsCompleteReply {
1714    pub fn new(status: IsCompleteReplyStatus, indent: String) -> Self {
1715        Self { status, indent }
1716    }
1717
1718    pub fn incomplete(indent: String) -> Self {
1719        Self::new(IsCompleteReplyStatus::Incomplete, indent)
1720    }
1721
1722    pub fn complete() -> Self {
1723        Self::new(IsCompleteReplyStatus::Complete, String::new())
1724    }
1725
1726    pub fn invalid() -> Self {
1727        Self::new(IsCompleteReplyStatus::Invalid, String::new())
1728    }
1729
1730    pub fn unknown() -> Self {
1731        Self::new(IsCompleteReplyStatus::Unknown, String::new())
1732    }
1733}
1734
1735#[derive(Serialize, Deserialize, Debug, Clone)]
1736#[serde(tag = "hist_access_type")]
1737pub enum HistoryRequest {
1738    #[serde(rename = "range")]
1739    Range {
1740        session: Option<i32>,
1741        start: i32,
1742        stop: i32,
1743        output: bool,
1744        raw: bool,
1745    },
1746    #[serde(rename = "tail")]
1747    Tail { n: i32, output: bool, raw: bool },
1748    #[serde(rename = "search")]
1749    Search {
1750        pattern: String,
1751        unique: bool,
1752        output: bool,
1753        raw: bool,
1754    },
1755}
1756impl Default for HistoryRequest {
1757    fn default() -> Self {
1758        Self::Range {
1759            session: None,
1760            start: 0,
1761            stop: 0,
1762            output: false,
1763            raw: false,
1764        }
1765    }
1766}
1767
1768#[derive(Serialize, Deserialize, Debug, Clone)]
1769#[serde(untagged)]
1770pub enum HistoryEntry {
1771    // When history_request.output is false
1772    // (session, line_number, input)
1773    Input(usize, usize, String),
1774    // When history_request.output is true
1775    // (session, line_number, (input, output))
1776    InputOutput(usize, usize, (String, String)),
1777}
1778
1779/// A reply containing execution history.
1780///
1781/// See <https://jupyter-client.readthedocs.io/en/latest/messaging.html#history>
1782#[derive(Serialize, Deserialize, Debug, Clone)]
1783pub struct HistoryReply {
1784    pub history: Vec<HistoryEntry>,
1785
1786    pub status: ReplyStatus,
1787    #[serde(flatten, skip_serializing_if = "Option::is_none")]
1788    pub error: Option<Box<ReplyError>>,
1789}
1790impl Default for HistoryReply {
1791    fn default() -> Self {
1792        Self {
1793            history: Vec::new(),
1794            status: ReplyStatus::Ok,
1795            error: None,
1796        }
1797    }
1798}
1799
1800impl HistoryReply {
1801    pub fn new(history: Vec<HistoryEntry>) -> Self {
1802        Self {
1803            history,
1804            status: ReplyStatus::Ok,
1805            error: None,
1806        }
1807    }
1808}
1809
1810/// A request to check if the code is complete and ready for execution.
1811///
1812/// See <https://jupyter-client.readthedocs.io/en/latest/messaging.html#code-completeness>
1813#[derive(Serialize, Deserialize, Debug, Clone, Default)]
1814pub struct IsCompleteRequest {
1815    pub code: String,
1816}
1817
1818#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
1819#[serde(rename_all = "lowercase")]
1820pub enum ExecutionState {
1821    Busy,
1822    Idle,
1823}
1824
1825impl ExecutionState {
1826    pub fn as_str(&self) -> &str {
1827        match self {
1828            ExecutionState::Busy => "busy",
1829            ExecutionState::Idle => "idle",
1830        }
1831    }
1832}
1833
1834/// A message indicating the current status of the kernel.
1835///
1836/// See <https://jupyter-client.readthedocs.io/en/latest/messaging.html#kernel-status>
1837#[derive(Serialize, Deserialize, Debug, Clone)]
1838pub struct Status {
1839    pub execution_state: ExecutionState,
1840}
1841impl Default for Status {
1842    fn default() -> Self {
1843        Self {
1844            execution_state: ExecutionState::Idle,
1845        }
1846    }
1847}
1848
1849impl Status {
1850    pub fn busy() -> Self {
1851        Self {
1852            execution_state: ExecutionState::Busy,
1853        }
1854    }
1855
1856    pub fn idle() -> Self {
1857        Self {
1858            execution_state: ExecutionState::Idle,
1859        }
1860    }
1861}
1862
1863#[cfg(test)]
1864mod test {
1865    use serde_json::json;
1866
1867    use super::*;
1868
1869    #[test]
1870    fn test_execute_request_serialize() {
1871        let request = ExecuteRequest {
1872            code: "print('Hello, World!')".to_string(),
1873            silent: false,
1874            store_history: true,
1875            user_expressions: Some(HashMap::new()),
1876            allow_stdin: false,
1877            stop_on_error: true,
1878        };
1879        let request_value = serde_json::to_value(request).unwrap();
1880
1881        let expected_request_value = serde_json::json!({
1882            "code": "print('Hello, World!')",
1883            "silent": false,
1884            "store_history": true,
1885            "user_expressions": {},
1886            "allow_stdin": false,
1887            "stop_on_error": true
1888        });
1889
1890        assert_eq!(request_value, expected_request_value);
1891    }
1892
1893    #[test]
1894    fn test_execute_request_user_expressions_serializes_to_empty_dict() {
1895        let request = ExecuteRequest {
1896            code: "print('Hello, World!')".to_string(),
1897            silent: false,
1898            store_history: true,
1899            user_expressions: None,
1900            allow_stdin: false,
1901            stop_on_error: true,
1902        };
1903        let request_value = serde_json::to_value(request).unwrap();
1904
1905        let expected_request_value = serde_json::json!({
1906            "code": "print('Hello, World!')",
1907            "silent": false,
1908            "store_history": true,
1909            "user_expressions": {},
1910            "allow_stdin": false,
1911            "stop_on_error": true
1912        });
1913
1914        assert_eq!(request_value, expected_request_value);
1915    }
1916
1917    #[test]
1918    fn test_into_various() {
1919        let kernel_info_request = KernelInfoRequest {};
1920        let content: JupyterMessageContent = kernel_info_request.clone().into();
1921        let message: JupyterMessage = content.into();
1922        assert!(message.parent_header.is_none());
1923        match message.content {
1924            JupyterMessageContent::KernelInfoRequest(req) => {
1925                assert_eq!(req, kernel_info_request);
1926            }
1927            _ => panic!("Expected KernelInfoRequest"),
1928        }
1929
1930        let kernel_info_request = KernelInfoRequest {};
1931        let message: JupyterMessage = kernel_info_request.clone().into();
1932        assert!(message.parent_header.is_none());
1933        match message.content {
1934            JupyterMessageContent::KernelInfoRequest(req) => {
1935                assert_eq!(req, kernel_info_request);
1936            }
1937            _ => panic!("Expected KernelInfoRequest"),
1938        }
1939    }
1940
1941    #[test]
1942    fn test_default() {
1943        let msg: JupyterMessage = ExecuteRequest {
1944            code: "import this".to_string(),
1945            ..Default::default()
1946        }
1947        .into();
1948
1949        assert_eq!(msg.header.msg_type, "execute_request");
1950        assert_eq!(msg.header.msg_id.len(), 36);
1951
1952        match msg.content {
1953            JupyterMessageContent::ExecuteRequest(req) => {
1954                assert_eq!(req.code, "import this");
1955                assert!(!req.silent);
1956                assert!(req.store_history);
1957                assert_eq!(req.user_expressions, None);
1958                assert!(!req.allow_stdin);
1959                assert!(req.stop_on_error);
1960            }
1961            _ => panic!("Expected ExecuteRequest"),
1962        }
1963    }
1964
1965    #[test]
1966    fn test_deserialize_payload() {
1967        let raw_execute_reply_content = r#"
1968        {
1969            "status": "ok",
1970            "execution_count": 1,
1971            "payload": [{
1972                "source": "page",
1973                "data": {
1974                    "text/html": "<h1>Hello</h1>",
1975                    "text/plain": "Hello"
1976                },
1977                "start": 0
1978            }],
1979            "user_expressions": {}
1980        }
1981        "#;
1982
1983        let execute_reply: ExecuteReply = serde_json::from_str(raw_execute_reply_content).unwrap();
1984
1985        assert_eq!(execute_reply.status, ReplyStatus::Ok);
1986        assert_eq!(execute_reply.execution_count, ExecutionCount::new(1));
1987
1988        let payload = execute_reply.payload.clone();
1989
1990        assert_eq!(payload.len(), 1);
1991        let payload = payload.first().unwrap();
1992
1993        let media = match payload {
1994            Payload::Page { data, .. } => data,
1995            _ => panic!("Expected Page payload type"),
1996        };
1997
1998        let media = serde_json::to_value(media).unwrap();
1999
2000        let expected_media = serde_json::json!({
2001            "text/html": "<h1>Hello</h1>",
2002            "text/plain": "Hello"
2003        });
2004
2005        assert_eq!(media, expected_media);
2006    }
2007
2008    #[test]
2009    pub fn test_display_data_various_data() {
2010        let display_data = DisplayData {
2011            data: serde_json::from_value(json!({
2012                "text/plain": "Hello, World!",
2013                "text/html": "<h1>Hello, World!</h1>",
2014                "application/json": {
2015                    "hello": "world",
2016                    "foo": "bar",
2017                    "ok": [1, 2, 3],
2018                }
2019            }))
2020            .unwrap(),
2021            ..Default::default()
2022        };
2023
2024        let display_data_value = serde_json::to_value(display_data).unwrap();
2025
2026        let expected_display_data_value = serde_json::json!({
2027            "data": {
2028                "text/plain": "Hello, World!",
2029                "text/html": "<h1>Hello, World!</h1>",
2030                "application/json": {
2031                    "hello": "world",
2032                    "foo": "bar",
2033                    "ok": [1, 2, 3]
2034                }
2035            },
2036            "metadata": {}
2037        });
2038
2039        assert_eq!(display_data_value, expected_display_data_value);
2040    }
2041
2042    use std::mem::size_of;
2043
2044    macro_rules! size_of_variant {
2045        ($variant:ty) => {
2046            let size = size_of::<$variant>();
2047            println!("The size of {} is: {} bytes", stringify!($variant), size);
2048
2049            assert!(size <= 96);
2050        };
2051    }
2052
2053    #[test]
2054    fn test_enum_variant_sizes() {
2055        size_of_variant!(ClearOutput);
2056        size_of_variant!(CommClose);
2057        size_of_variant!(CommInfoReply);
2058        size_of_variant!(CommInfoRequest);
2059        size_of_variant!(CommMsg);
2060        size_of_variant!(CommOpen);
2061        size_of_variant!(CompleteReply);
2062        size_of_variant!(CompleteRequest);
2063        size_of_variant!(DebugReply);
2064        size_of_variant!(DebugRequest);
2065        size_of_variant!(DisplayData);
2066        size_of_variant!(ErrorOutput);
2067        size_of_variant!(ExecuteInput);
2068        size_of_variant!(ExecuteReply);
2069        size_of_variant!(ExecuteRequest);
2070        size_of_variant!(ExecuteResult);
2071        size_of_variant!(HistoryReply);
2072        size_of_variant!(HistoryRequest);
2073        size_of_variant!(InputReply);
2074        size_of_variant!(InputRequest);
2075        size_of_variant!(InspectReply);
2076        size_of_variant!(InspectRequest);
2077        size_of_variant!(InterruptReply);
2078        size_of_variant!(InterruptRequest);
2079        size_of_variant!(IsCompleteReply);
2080        size_of_variant!(IsCompleteRequest);
2081        size_of_variant!(Box<KernelInfoReply>);
2082        size_of_variant!(KernelInfoRequest);
2083        size_of_variant!(ShutdownReply);
2084        size_of_variant!(ShutdownRequest);
2085        size_of_variant!(Status);
2086        size_of_variant!(StreamContent);
2087        size_of_variant!(UnknownMessage);
2088        size_of_variant!(UpdateDisplayData);
2089    }
2090
2091    #[test]
2092    fn test_jupyter_message_content_enum_size() {
2093        let size = size_of::<JupyterMessageContent>();
2094        println!("The size of JupyterMessageContent is: {}", size);
2095        assert!(size > 0);
2096        assert!(size <= 96);
2097    }
2098
2099    #[test]
2100    fn test_jupyter_message_parent_header_serializes_to_empty_dict() {
2101        let request = ExecuteRequest {
2102            code: "1 + 1".to_string(),
2103            ..Default::default()
2104        };
2105        let message = JupyterMessage::from(request);
2106
2107        let serialized_message = serde_json::to_value(message).unwrap();
2108
2109        // Test that the `parent_header` field is an empty object.
2110        let parent_header = serialized_message.get("parent_header").unwrap();
2111        assert!(parent_header.is_object());
2112        assert!(parent_header.as_object().unwrap().is_empty());
2113    }
2114
2115    #[test]
2116    fn test_user_expressions_serialization() {
2117        let request = ExecuteRequest {
2118            code: "pass".to_string(),
2119            silent: false,
2120            store_history: true,
2121            user_expressions: Some(HashMap::from([(
2122                String::from("expression"),
2123                String::from("42 + 7"),
2124            )])),
2125            allow_stdin: false,
2126            stop_on_error: true,
2127        };
2128        let request_value = serde_json::to_value(request.clone()).unwrap();
2129
2130        let expected_request_value = serde_json::json!({
2131            "code": "pass",
2132            "silent": false,
2133            "store_history": true,
2134            "user_expressions": {"expression": "42 + 7"},
2135            "allow_stdin": false,
2136            "stop_on_error": true
2137        });
2138
2139        assert_eq!(request_value, expected_request_value);
2140
2141        let deserialized_request: ExecuteRequest = serde_json::from_value(request_value).unwrap();
2142        assert_eq!(
2143            deserialized_request.user_expressions,
2144            request.user_expressions
2145        );
2146    }
2147
2148    #[test]
2149    fn test_jupyter_message_parent_header_deserialize() {
2150        let msg = r#"
2151  {
2152    "buffers": [],
2153    "channel": "shell",
2154    "content": {},
2155    "header": {
2156        "date": "2025-05-14T14:32:23.490Z",
2157        "msg_id": "44bd6b44-78a1-4892-87df-c0861a005d56",
2158        "msg_type": "kernel_info_request",
2159        "session": "b75bddaa-6d69-4340-ba13-81516192370e",
2160        "username": "",
2161        "version": "5.2"
2162    },
2163    "metadata": {},
2164    "parent_header": {
2165        "date": "2025-05-14T14:32:23.490Z",
2166        "msg_id": "2aaf8916-6b83-4f5a-80dd-633e94f5d8e1",
2167        "msg_type": "kernel_info_request",
2168        "session": "e2a3165d-76a8-4fef-850f-712102589660",
2169        "username": "",
2170        "version": "5.2"
2171    }
2172}
2173        "#;
2174
2175        let message: JupyterMessage = serde_json::from_str(msg).unwrap();
2176        assert!(message.parent_header.is_some());
2177        assert_eq!(
2178            message.parent_header.as_ref().unwrap().msg_type,
2179            "kernel_info_request"
2180        );
2181        assert_eq!(
2182            message.parent_header.as_ref().unwrap().msg_id,
2183            "2aaf8916-6b83-4f5a-80dd-633e94f5d8e1"
2184        );
2185        assert_eq!(
2186            message.header.msg_id,
2187            "44bd6b44-78a1-4892-87df-c0861a005d56"
2188        );
2189    }
2190
2191    #[test]
2192    fn test_jupyter_message_empty_parent_header_deserialize() {
2193        let msg = r#"
2194  {
2195    "buffers": [],
2196    "channel": "shell",
2197    "content": {},
2198    "header": {
2199        "date": "2025-05-14T14:32:23.490Z",
2200        "msg_id": "44bd6b44-78a1-4892-87df-c0861a005d56",
2201        "msg_type": "kernel_info_request",
2202        "session": "b75bddaa-6d69-4340-ba13-81516192370e",
2203        "username": "",
2204        "version": "5.2"
2205    },
2206    "metadata": {},
2207    "parent_header": {}
2208}
2209        "#;
2210
2211        let message: JupyterMessage = serde_json::from_str(msg).unwrap();
2212        assert!(message.parent_header.is_none());
2213        assert_eq!(message.header.msg_type, "kernel_info_request");
2214        assert_eq!(
2215            message.header.msg_id,
2216            "44bd6b44-78a1-4892-87df-c0861a005d56"
2217        );
2218    }
2219}