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