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