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