Skip to main content

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    #[serde(
107        serialize_with = "serialize_parent_header",
108        deserialize_with = "deserialize_parent_header"
109    )]
110    pub parent_header: Option<Header>,
111    pub metadata: Value,
112    pub content: Value,
113    #[serde(skip_serializing, skip_deserializing)]
114    pub buffers: Vec<Bytes>,
115    pub channel: Option<Channel>,
116}
117
118/// Represents a Jupyter message header.
119///
120/// The header contains metadata about the message, such as its unique identifier,
121/// the username of the sender, and the message type.
122///
123/// # Fields
124///
125/// - `msg_id`: A unique identifier for the message.
126/// - `username`: The name of the user who sent the message.
127/// - `session`: The session identifier.
128/// - `date`: The timestamp when the message was created. Defaults to UNIX_EPOCH if
129///   not provided by the kernel (non-conformant behavior).
130/// - `msg_type`: The type of message (e.g., `execute_request`).
131/// - `version`: The version of the messaging protocol.
132///
133/// # Example
134///
135/// ```rust
136/// use jupyter_protocol::messaging::Header;
137///
138/// let header = Header {
139///     msg_id: "12345".to_string(),
140///     username: "user".to_string(),
141///     session: "session1".to_string(),
142///     date: chrono::DateTime::from_timestamp_nanos(1234567890),
143///     msg_type: "execute_request".to_string(),
144///     version: "5.3".to_string(),
145/// };
146/// ```
147#[derive(Serialize, Deserialize, Debug, Clone)]
148pub struct Header {
149    pub msg_id: String,
150    pub username: String,
151    pub session: String,
152    /// The timestamp when the message was created.
153    /// Defaults to UNIX_EPOCH (1970-01-01T00:00:00Z) if not provided by the kernel.
154    /// Some kernels (e.g., Almond) omit this field, which is non-conformant behavior.
155    #[serde(default, deserialize_with = "deserialize_date_with_default")]
156    pub date: DateTime<Utc>,
157    pub msg_type: String,
158    pub version: String,
159}
160
161/// Serializes the `parent_header` of a `JupyterMessage`.
162///
163/// Treats `None` as an empty object to conform to Jupyter's messaging guidelines:
164///
165/// > If there is no parent, an empty dict should be used.
166/// >
167/// > — https://jupyter-client.readthedocs.io/en/latest/messaging.html#parent-header
168fn serialize_parent_header<S>(
169    parent_header: &Option<Header>,
170    serializer: S,
171) -> Result<S::Ok, S::Error>
172where
173    S: serde::Serializer,
174{
175    match parent_header {
176        Some(parent_header) => parent_header.serialize(serializer),
177        None => serde_json::Map::new().serialize(serializer),
178    }
179}
180
181/// Deserializes the `parent_header` of a `JupyterMessage`.
182///
183/// This function handles the case where the parent header is `None`
184/// or an empty object, and also allows for deserialization from
185/// non-object types.
186pub fn deserialize_parent_header<'de, D>(deserializer: D) -> Result<Option<Header>, D::Error>
187where
188    D: serde::Deserializer<'de>,
189{
190    use serde::de::Error;
191    let value = Value::deserialize(deserializer)?;
192    if value.is_null() {
193        Ok(None)
194    } else if let Some(obj) = value.as_object() {
195        if obj.is_empty() {
196            Ok(None)
197        } else {
198            // Try to deserialize as Header
199            serde_json::from_value(Value::Object(obj.clone()))
200                .map(Some)
201                .map_err(D::Error::custom)
202        }
203    } else {
204        // Try to deserialize as Header (for non-object types)
205        serde_json::from_value(value)
206            .map(Some)
207            .map_err(D::Error::custom)
208    }
209}
210
211/// Deserializes the `date` field, defaulting to UNIX_EPOCH if missing or null.
212///
213/// Some kernels (e.g., Almond) don't include the `date` field in message headers,
214/// which violates the Jupyter protocol spec. This deserializer tolerates missing
215/// dates by defaulting to UNIX epoch (1970-01-01T00:00:00Z).
216///
217/// If you see UNIX_EPOCH as the date, it likely means the kernel omitted the field.
218fn deserialize_date_with_default<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
219where
220    D: serde::Deserializer<'de>,
221{
222    let opt = Option::<DateTime<Utc>>::deserialize(deserializer)?;
223    Ok(opt.unwrap_or_else(|| DateTime::<Utc>::from_timestamp(0, 0).expect("UNIX_EPOCH is valid")))
224}
225
226/// A message in the Jupyter protocol format.
227///
228/// A Jupyter message consists of several parts:
229/// - `zmq_identities`: ZeroMQ identities used for routing (not serialized)
230/// - `header`: Metadata about the message
231/// - `parent_header`: Header from parent message, if this is a response
232/// - `metadata`: Additional metadata as JSON
233/// - `content`: The main message content
234/// - `buffers`: Binary buffers for messages that need them (not serialized)
235/// - `channel`: The communication channel this message belongs to
236///
237/// # Example
238///
239/// ```rust
240/// use jupyter_protocol::messaging::{JupyterMessage, JupyterMessageContent, ExecuteRequest};
241///
242/// // Create a new execute_request message
243/// let msg = JupyterMessage::new(
244///     ExecuteRequest {
245///         code: "print('Hello')".to_string(),
246///         silent: false,
247///         store_history: true,
248///         user_expressions: Default::default(),
249///         allow_stdin: true,
250///         stop_on_error: false,
251///     },
252///     None,
253/// );
254/// ```
255///
256/// Messages can be created as responses to other messages by passing the parent:
257///
258/// ```rust
259/// # use jupyter_protocol::messaging::{JupyterMessage, JupyterMessageContent, ReplyStatus, ExecuteRequest, ExecuteReply};
260/// # let parent = JupyterMessage::new(ExecuteRequest {
261/// #     code: "".to_string(), silent: false, store_history: true,
262/// #     user_expressions: Default::default(), allow_stdin: true, stop_on_error: false,
263/// # }, None);
264/// let reply = JupyterMessage::new(
265///     ExecuteReply {
266///         status: ReplyStatus::Ok,
267///         execution_count: jupyter_protocol::ExecutionCount::new(1),
268///         ..Default::default()
269///     },
270///     Some(&parent),
271/// );
272/// ```
273
274#[derive(Serialize, Clone)]
275pub struct JupyterMessage {
276    #[serde(skip_serializing, skip_deserializing)]
277    pub zmq_identities: Vec<Bytes>,
278    pub header: Header,
279    #[serde(
280        serialize_with = "serialize_parent_header",
281        deserialize_with = "deserialize_parent_header"
282    )]
283    pub parent_header: Option<Header>,
284    pub metadata: Value,
285    pub content: JupyterMessageContent,
286    #[serde(skip_serializing, skip_deserializing)]
287    pub buffers: Vec<Bytes>,
288    pub channel: Option<Channel>,
289}
290
291impl JupyterMessage {
292    pub fn new(
293        content: impl Into<JupyterMessageContent>,
294        parent: Option<&JupyterMessage>,
295    ) -> JupyterMessage {
296        // Normally a session ID is per client. A higher level wrapper on this API
297        // should probably create messages based on a `Session` struct that is stateful.
298        // For now, a user can create a message and then set the session ID directly.
299        let session = match parent {
300            Some(parent) => parent.header.session.clone(),
301            None => Uuid::new_v4().to_string(),
302        };
303
304        let content = content.into();
305
306        let header = Header {
307            msg_id: Uuid::new_v4().to_string(),
308            username: "runtimelib".to_string(),
309            session,
310            date: time::utc_now(),
311            msg_type: content.message_type().to_owned(),
312            version: "5.3".to_string(),
313        };
314
315        JupyterMessage {
316            zmq_identities: parent.map_or(Vec::new(), |parent| parent.zmq_identities.clone()),
317            header,
318            parent_header: parent.map(|parent| parent.header.clone()),
319            metadata: json!({}),
320            content,
321            buffers: Vec::new(),
322            channel: None,
323        }
324    }
325
326    pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
327        self.metadata = metadata;
328        self
329    }
330
331    pub fn with_buffers(mut self, buffers: Vec<Bytes>) -> Self {
332        self.buffers = buffers;
333        self
334    }
335
336    pub fn with_parent(mut self, parent: &JupyterMessage) -> Self {
337        self.header.session.clone_from(&parent.header.session);
338        self.parent_header = Some(parent.header.clone());
339        self.zmq_identities.clone_from(&parent.zmq_identities);
340        self
341    }
342
343    pub fn with_zmq_identities(mut self, zmq_identities: Vec<Bytes>) -> Self {
344        self.zmq_identities = zmq_identities;
345        self
346    }
347
348    /// Set the channel for this message.
349    ///
350    /// This is primarily used for WebSocket transports where all channels
351    /// are multiplexed over a single connection and the `channel` field tells
352    /// the server which channel to route the message to.
353    ///
354    /// For ZMQ transports, the channel is implicit in which socket the message
355    /// is sent on, so this field is typically `None`.
356    pub fn with_channel(mut self, channel: Channel) -> Self {
357        self.channel = Some(channel);
358        self
359    }
360
361    pub fn with_session(mut self, session: &str) -> Self {
362        self.header.session = session.to_string();
363        self
364    }
365
366    pub fn message_type(&self) -> &str {
367        self.content.message_type()
368    }
369
370    pub fn from_value(message: Value) -> Result<JupyterMessage, JupyterError> {
371        let message = serde_json::from_value::<UnknownJupyterMessage>(message)?;
372
373        let content =
374            JupyterMessageContent::from_type_and_content(&message.header.msg_type, message.content);
375
376        let content = match content {
377            Ok(content) => content,
378            Err(err) => {
379                return Err(JupyterError::ParseError {
380                    msg_type: message.header.msg_type,
381                    source: err,
382                })
383            }
384        };
385
386        let message = JupyterMessage {
387            zmq_identities: Vec::new(),
388            header: message.header,
389            parent_header: message.parent_header,
390            metadata: message.metadata,
391            content,
392            buffers: message.buffers,
393            channel: None,
394        };
395
396        Ok(message)
397    }
398}
399
400impl<'de> Deserialize<'de> for JupyterMessage {
401    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
402    where
403        D: serde::Deserializer<'de>,
404    {
405        let message = UnknownJupyterMessage::deserialize(deserializer)?;
406
407        let content =
408            JupyterMessageContent::from_type_and_content(&message.header.msg_type, message.content)
409                .map_err(serde::de::Error::custom)?;
410
411        Ok(JupyterMessage {
412            zmq_identities: Vec::new(),
413            header: message.header,
414            parent_header: message.parent_header,
415            metadata: message.metadata,
416            content,
417            buffers: message.buffers,
418            channel: message.channel,
419        })
420    }
421}
422
423impl fmt::Debug for JupyterMessage {
424    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
425        writeln!(
426            f,
427            "\nHeader: {}",
428            serde_json::to_string_pretty(&self.header).unwrap()
429        )?;
430        writeln!(
431            f,
432            "Parent header: {}",
433            if let Some(parent_header) = self.parent_header.as_ref() {
434                serde_json::to_string_pretty(parent_header).unwrap()
435            } else {
436                serde_json::to_string_pretty(&serde_json::Map::new()).unwrap()
437            }
438        )?;
439        writeln!(
440            f,
441            "Metadata: {}",
442            serde_json::to_string_pretty(&self.metadata).unwrap()
443        )?;
444        writeln!(
445            f,
446            "Content: {}\n",
447            serde_json::to_string_pretty(&self.content).unwrap()
448        )?;
449        Ok(())
450    }
451}
452
453#[derive(Serialize, Debug, Clone)]
454#[serde(untagged)]
455pub enum JupyterMessageContent {
456    ClearOutput(ClearOutput),
457    CommClose(CommClose),
458    CommInfoReply(CommInfoReply),
459    CommInfoRequest(CommInfoRequest),
460    CommMsg(CommMsg),
461    CommOpen(CommOpen),
462    CompleteReply(CompleteReply),
463    CompleteRequest(CompleteRequest),
464    DebugReply(DebugReply),
465    DebugRequest(DebugRequest),
466    DisplayData(DisplayData),
467    ErrorOutput(ErrorOutput),
468    ExecuteInput(ExecuteInput),
469    ExecuteReply(ExecuteReply),
470    ExecuteRequest(ExecuteRequest),
471    ExecuteResult(ExecuteResult),
472    HistoryReply(HistoryReply),
473    HistoryRequest(HistoryRequest),
474    InputReply(InputReply),
475    InputRequest(InputRequest),
476    InspectReply(InspectReply),
477    InspectRequest(InspectRequest),
478    InterruptReply(InterruptReply),
479    InterruptRequest(InterruptRequest),
480    IoPubWelcome(IoPubWelcome),
481    IsCompleteReply(IsCompleteReply),
482    IsCompleteRequest(IsCompleteRequest),
483    // This field is much larger than the most frequent ones
484    // so we box it.
485    KernelInfoReply(Box<KernelInfoReply>),
486    KernelInfoRequest(KernelInfoRequest),
487    ShutdownReply(ShutdownReply),
488    ShutdownRequest(ShutdownRequest),
489    Status(Status),
490    StreamContent(StreamContent),
491    UnknownMessage(UnknownMessage),
492    UpdateDisplayData(UpdateDisplayData),
493}
494
495impl JupyterMessageContent {
496    pub fn message_type(&self) -> &str {
497        match self {
498            JupyterMessageContent::ClearOutput(_) => "clear_output",
499            JupyterMessageContent::CommClose(_) => "comm_close",
500            JupyterMessageContent::CommInfoReply(_) => "comm_info_reply",
501            JupyterMessageContent::CommInfoRequest(_) => "comm_info_request",
502            JupyterMessageContent::CommMsg(_) => "comm_msg",
503            JupyterMessageContent::CommOpen(_) => "comm_open",
504            JupyterMessageContent::CompleteReply(_) => "complete_reply",
505            JupyterMessageContent::CompleteRequest(_) => "complete_request",
506            JupyterMessageContent::DebugReply(_) => "debug_reply",
507            JupyterMessageContent::DebugRequest(_) => "debug_request",
508            JupyterMessageContent::DisplayData(_) => "display_data",
509            JupyterMessageContent::ErrorOutput(_) => "error",
510            JupyterMessageContent::ExecuteInput(_) => "execute_input",
511            JupyterMessageContent::ExecuteReply(_) => "execute_reply",
512            JupyterMessageContent::ExecuteRequest(_) => "execute_request",
513            JupyterMessageContent::ExecuteResult(_) => "execute_result",
514            JupyterMessageContent::HistoryReply(_) => "history_reply",
515            JupyterMessageContent::HistoryRequest(_) => "history_request",
516            JupyterMessageContent::InputReply(_) => "input_reply",
517            JupyterMessageContent::InputRequest(_) => "input_request",
518            JupyterMessageContent::InspectReply(_) => "inspect_reply",
519            JupyterMessageContent::InspectRequest(_) => "inspect_request",
520            JupyterMessageContent::InterruptReply(_) => "interrupt_reply",
521            JupyterMessageContent::InterruptRequest(_) => "interrupt_request",
522            JupyterMessageContent::IoPubWelcome(_) => "iopub_welcome",
523            JupyterMessageContent::IsCompleteReply(_) => "is_complete_reply",
524            JupyterMessageContent::IsCompleteRequest(_) => "is_complete_request",
525            JupyterMessageContent::KernelInfoReply(_) => "kernel_info_reply",
526            JupyterMessageContent::KernelInfoRequest(_) => "kernel_info_request",
527            JupyterMessageContent::ShutdownReply(_) => "shutdown_reply",
528            JupyterMessageContent::ShutdownRequest(_) => "shutdown_request",
529            JupyterMessageContent::Status(_) => "status",
530            JupyterMessageContent::StreamContent(_) => "stream",
531            JupyterMessageContent::UnknownMessage(unk) => unk.msg_type.as_str(),
532            JupyterMessageContent::UpdateDisplayData(_) => "update_display_data",
533        }
534    }
535
536    pub fn from_type_and_content(msg_type: &str, content: Value) -> serde_json::Result<Self> {
537        match msg_type {
538            "clear_output" => Ok(JupyterMessageContent::ClearOutput(serde_json::from_value(
539                content,
540            )?)),
541
542            "comm_close" => Ok(JupyterMessageContent::CommClose(serde_json::from_value(
543                content,
544            )?)),
545
546            "comm_info_reply" => Ok(JupyterMessageContent::CommInfoReply(
547                serde_json::from_value(content)?,
548            )),
549            "comm_info_request" => Ok(JupyterMessageContent::CommInfoRequest(
550                serde_json::from_value(content)?,
551            )),
552
553            "comm_msg" => Ok(JupyterMessageContent::CommMsg(serde_json::from_value(
554                content,
555            )?)),
556            "comm_open" => Ok(JupyterMessageContent::CommOpen(serde_json::from_value(
557                content,
558            )?)),
559
560            "complete_reply" => Ok(JupyterMessageContent::CompleteReply(
561                serde_json::from_value(content)?,
562            )),
563            "complete_request" => Ok(JupyterMessageContent::CompleteRequest(
564                serde_json::from_value(content)?,
565            )),
566
567            "debug_reply" => Ok(JupyterMessageContent::DebugReply(serde_json::from_value(
568                content,
569            )?)),
570            "debug_request" => Ok(JupyterMessageContent::DebugRequest(serde_json::from_value(
571                content,
572            )?)),
573
574            "display_data" => Ok(JupyterMessageContent::DisplayData(serde_json::from_value(
575                content,
576            )?)),
577
578            "error" => Ok(JupyterMessageContent::ErrorOutput(serde_json::from_value(
579                content,
580            )?)),
581
582            "execute_input" => Ok(JupyterMessageContent::ExecuteInput(serde_json::from_value(
583                content,
584            )?)),
585
586            "execute_reply" => Ok(JupyterMessageContent::ExecuteReply(serde_json::from_value(
587                content,
588            )?)),
589            "execute_request" => Ok(JupyterMessageContent::ExecuteRequest(
590                serde_json::from_value(content)?,
591            )),
592
593            "execute_result" => Ok(JupyterMessageContent::ExecuteResult(
594                serde_json::from_value(content)?,
595            )),
596
597            "history_reply" => Ok(JupyterMessageContent::HistoryReply(serde_json::from_value(
598                content,
599            )?)),
600            "history_request" => Ok(JupyterMessageContent::HistoryRequest(
601                serde_json::from_value(content)?,
602            )),
603
604            "input_reply" => Ok(JupyterMessageContent::InputReply(serde_json::from_value(
605                content,
606            )?)),
607            "input_request" => Ok(JupyterMessageContent::InputRequest(serde_json::from_value(
608                content,
609            )?)),
610
611            "inspect_reply" => Ok(JupyterMessageContent::InspectReply(serde_json::from_value(
612                content,
613            )?)),
614            "inspect_request" => Ok(JupyterMessageContent::InspectRequest(
615                serde_json::from_value(content)?,
616            )),
617
618            "interrupt_reply" => Ok(JupyterMessageContent::InterruptReply(
619                serde_json::from_value(content)?,
620            )),
621            "interrupt_request" => Ok(JupyterMessageContent::InterruptRequest(
622                serde_json::from_value(content)?,
623            )),
624
625            "iopub_welcome" => Ok(JupyterMessageContent::IoPubWelcome(serde_json::from_value(
626                content,
627            )?)),
628
629            "is_complete_reply" => Ok(JupyterMessageContent::IsCompleteReply(
630                serde_json::from_value(content)?,
631            )),
632            "is_complete_request" => Ok(JupyterMessageContent::IsCompleteRequest(
633                serde_json::from_value(content)?,
634            )),
635
636            "kernel_info_reply" => Ok(JupyterMessageContent::KernelInfoReply(
637                serde_json::from_value(content)?,
638            )),
639            "kernel_info_request" => Ok(JupyterMessageContent::KernelInfoRequest(
640                serde_json::from_value(content)?,
641            )),
642
643            "shutdown_reply" => Ok(JupyterMessageContent::ShutdownReply(
644                serde_json::from_value(content)?,
645            )),
646            "shutdown_request" => Ok(JupyterMessageContent::ShutdownRequest(
647                serde_json::from_value(content)?,
648            )),
649
650            "status" => Ok(JupyterMessageContent::Status(serde_json::from_value(
651                content,
652            )?)),
653
654            "stream" => Ok(JupyterMessageContent::StreamContent(
655                serde_json::from_value(content)?,
656            )),
657
658            "update_display_data" => Ok(JupyterMessageContent::UpdateDisplayData(
659                serde_json::from_value(content)?,
660            )),
661
662            _ => Ok(JupyterMessageContent::UnknownMessage(UnknownMessage {
663                msg_type: msg_type.to_string(),
664                content,
665            })),
666        }
667    }
668}
669
670macro_rules! impl_message_traits {
671    ($($name:ident),*) => {
672        $(
673            impl $name {
674                #[doc = concat!("Create a new `JupyterMessage`, assigning the parent for a `", stringify!($name), "` message.\n")]
675                ///
676                /// This method creates a new `JupyterMessage` with the right content, parent header, and zmq identities, making
677                /// it suitable for sending over ZeroMQ.
678                ///
679                /// # Example
680                /// ```rust
681                /// use jupyter_protocol::messaging::{JupyterMessage, JupyterMessageContent};
682                #[doc = concat!("use jupyter_protocol::", stringify!($name), ";\n")]
683                ///
684                /// let parent_message = JupyterMessage::new(jupyter_protocol::UnknownMessage {
685                ///   msg_type: "example".to_string(),
686                ///   content: serde_json::json!({ "key": "value" }),
687                /// }, None);
688                ///
689                #[doc = concat!("let child_message = ", stringify!($name), "{\n")]
690                ///   ..Default::default()
691                /// }.as_child_of(&parent_message);
692                ///
693                /// // Next you would send the `child_message` over the connection
694                ///
695                /// ```
696                #[must_use]
697                pub fn as_child_of(&self, parent: &JupyterMessage) -> JupyterMessage {
698                    JupyterMessage::new(self.clone(), Some(parent))
699                }
700            }
701
702            impl From<$name> for JupyterMessage {
703                #[doc(hidden)]
704                #[doc = concat!("Create a new `JupyterMessage` for a `", stringify!($name), "`.\n\n")]
705                /// ⚠️ If you use this method with `runtimelib`, you must set the zmq identities yourself. If you
706                /// have a message that "caused" your message to be sent, use that message with `as_child_of` instead.
707                fn from(content: $name) -> Self {
708                    JupyterMessage::new(content, None)
709                }
710            }
711
712            impl From<$name> for JupyterMessageContent {
713                #[doc = concat!("Create a new `JupyterMessageContent` for a `", stringify!($name), "`.\n\n")]
714                fn from(content: $name) -> Self {
715                    JupyterMessageContent::$name(content)
716                }
717            }
718        )*
719    };
720}
721
722impl From<JupyterMessageContent> for JupyterMessage {
723    fn from(content: JupyterMessageContent) -> Self {
724        JupyterMessage::new(content, None)
725    }
726}
727
728impl_message_traits!(
729    ClearOutput,
730    CommClose,
731    CommInfoReply,
732    CommInfoRequest,
733    CommMsg,
734    CommOpen,
735    CompleteReply,
736    CompleteRequest,
737    DebugReply,
738    DebugRequest,
739    DisplayData,
740    ErrorOutput,
741    ExecuteInput,
742    ExecuteReply,
743    ExecuteRequest,
744    ExecuteResult,
745    HistoryReply,
746    // HistoryRequest, // special case due to enum entry
747    InputReply,
748    InputRequest,
749    InspectReply,
750    InspectRequest,
751    InterruptReply,
752    InterruptRequest,
753    IoPubWelcome,
754    IsCompleteReply,
755    IsCompleteRequest,
756    // KernelInfoReply, // special case due to boxing
757    KernelInfoRequest,
758    ShutdownReply,
759    ShutdownRequest,
760    Status,
761    StreamContent,
762    UpdateDisplayData,
763    UnknownMessage
764);
765
766// KernelInfoReply is a special case due to the Boxing requirement
767impl KernelInfoReply {
768    pub fn as_child_of(&self, parent: &JupyterMessage) -> JupyterMessage {
769        JupyterMessage::new(
770            JupyterMessageContent::KernelInfoReply(Box::new(self.clone())),
771            Some(parent),
772        )
773    }
774}
775
776impl From<KernelInfoReply> for JupyterMessage {
777    fn from(content: KernelInfoReply) -> Self {
778        JupyterMessage::new(
779            JupyterMessageContent::KernelInfoReply(Box::new(content)),
780            None,
781        )
782    }
783}
784
785impl From<KernelInfoReply> for JupyterMessageContent {
786    fn from(content: KernelInfoReply) -> Self {
787        JupyterMessageContent::KernelInfoReply(Box::new(content))
788    }
789}
790
791impl HistoryRequest {
792    /// Create a new `JupyterMessage`, assigning the parent for a `HistoryRequest` message.
793    ///
794    /// This method creates a new `JupyterMessage` with the right content, parent header, and zmq identities, making
795    /// it suitable for sending over ZeroMQ.
796    ///
797    /// # Example
798    /// ```rust
799    /// use jupyter_protocol::messaging::{JupyterMessage, JupyterMessageContent};
800    /// use jupyter_protocol::HistoryRequest;
801    ///
802    /// let parent_message = JupyterMessage::new(jupyter_protocol::UnknownMessage {
803    ///   msg_type: "example".to_string(),
804    ///   content: serde_json::json!({ "key": "value" }),
805    /// }, None);
806    ///
807    /// let child_message = HistoryRequest::Range {
808    ///     session: None,
809    ///     start: 0,
810    ///     stop: 10,
811    ///     output: false,
812    ///     raw: false,
813    /// }.as_child_of(&parent_message);
814    ///
815    /// // Next you would send the `child_message` over the connection
816    /// ```
817    #[must_use]
818    pub fn as_child_of(&self, parent: &JupyterMessage) -> JupyterMessage {
819        JupyterMessage::new(self.clone(), Some(parent))
820    }
821}
822
823impl From<HistoryRequest> for JupyterMessage {
824    #[doc(hidden)]
825    /// Create a new `JupyterMessage` for a `HistoryRequest`.
826    /// ⚠️ If you use this method with `runtimelib`, you must set the zmq identities yourself. If you
827    /// have a message that "caused" your message to be sent, use that message with `as_child_of` instead.
828    fn from(content: HistoryRequest) -> Self {
829        JupyterMessage::new(content, None)
830    }
831}
832
833impl From<HistoryRequest> for JupyterMessageContent {
834    /// Create a new `JupyterMessageContent` for a `HistoryRequest`.
835    fn from(content: HistoryRequest) -> Self {
836        JupyterMessageContent::HistoryRequest(content)
837    }
838}
839
840/// Unknown message types are a workaround for generically unknown messages.
841///
842/// ```rust
843/// use jupyter_protocol::messaging::{JupyterMessage, JupyterMessageContent, UnknownMessage};
844/// use serde_json::json;
845///
846/// let msg = UnknownMessage {
847///     msg_type: "example_request".to_string(),
848///     content: json!({ "key": "value" }),
849/// };
850///
851/// let reply_msg = msg.reply(json!({ "status": "ok" }));
852/// ```
853///
854#[derive(Serialize, Deserialize, Debug, Clone)]
855pub struct UnknownMessage {
856    #[serde(skip_serializing, skip_deserializing)]
857    pub msg_type: String,
858    #[serde(flatten)]
859    pub content: Value,
860}
861impl Default for UnknownMessage {
862    fn default() -> Self {
863        Self {
864            msg_type: "unknown".to_string(),
865            content: Value::Null,
866        }
867    }
868}
869
870impl UnknownMessage {
871    // Create a reply message for an unknown message, assuming `content` is known.
872    // Useful for when runtimelib does not support the message type.
873    // Send a PR to add support for the message type!
874    pub fn reply(&self, content: serde_json::Value) -> JupyterMessageContent {
875        JupyterMessageContent::UnknownMessage(UnknownMessage {
876            msg_type: self.msg_type.replace("_request", "_reply"),
877            content,
878        })
879    }
880}
881
882/// All reply messages have a `status` field.
883#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
884#[serde(rename_all = "lowercase")]
885pub enum ReplyStatus {
886    #[default]
887    Ok,
888    Error,
889    Aborted,
890}
891
892#[derive(Serialize, Deserialize, Debug, Clone, Default)]
893pub struct ReplyError {
894    pub ename: String,
895    pub evalue: String,
896    pub traceback: Vec<String>,
897}
898
899/// Clear output of a single cell / output area.
900#[derive(Serialize, Deserialize, Debug, Clone, Default)]
901pub struct ClearOutput {
902    /// Wait to clear the output until new output is available.  Clears the
903    /// existing output immediately before the new output is displayed.
904    /// Useful for creating simple animations with minimal flickering.
905    pub wait: bool,
906}
907
908/// A request for code execution.
909///
910/// See <https://jupyter-client.readthedocs.io/en/latest/messaging.html#execute>
911#[derive(Serialize, Deserialize, Debug, Clone)]
912pub struct ExecuteRequest {
913    pub code: String,
914    pub silent: bool,
915    pub store_history: bool,
916    #[serde(serialize_with = "serialize_user_expressions")]
917    pub user_expressions: Option<HashMap<String, String>>,
918    #[serde(default = "default_allow_stdin")]
919    pub allow_stdin: bool,
920    #[serde(default = "default_stop_on_error")]
921    pub stop_on_error: bool,
922}
923
924/// Serializes the `user_expressions`.
925///
926/// Treats `None` as an empty object to conform to Jupyter's messaging guidelines.
927fn serialize_user_expressions<S>(
928    user_expressions: &Option<HashMap<String, String>>,
929    serializer: S,
930) -> Result<S::Ok, S::Error>
931where
932    S: serde::Serializer,
933{
934    match user_expressions {
935        Some(user_expressions) => user_expressions.serialize(serializer),
936        None => serde_json::Map::new().serialize(serializer),
937    }
938}
939
940fn default_allow_stdin() -> bool {
941    false
942}
943
944fn default_stop_on_error() -> bool {
945    true
946}
947
948impl ExecuteRequest {
949    pub fn new(code: String) -> Self {
950        Self {
951            code,
952            ..Default::default()
953        }
954    }
955}
956
957impl Default for ExecuteRequest {
958    fn default() -> Self {
959        Self {
960            code: "".to_string(),
961            silent: false,
962            store_history: true,
963            user_expressions: None,
964            allow_stdin: false,
965            stop_on_error: true,
966        }
967    }
968}
969
970/// The result of evaluating a single user expression, as returned in an `execute_reply`.
971///
972/// On success, contains the evaluated data as a [`Media`] bundle (same format as `display_data`).
973/// On error, contains the error details.
974///
975/// See <https://jupyter-client.readthedocs.io/en/latest/messaging.html#execution-results>
976#[derive(Serialize, Deserialize, Debug, Clone)]
977#[serde(tag = "status")]
978pub enum ExpressionResult {
979    #[serde(rename = "ok")]
980    Ok {
981        data: Media,
982        #[serde(default)]
983        metadata: serde_json::Map<String, Value>,
984    },
985    #[serde(rename = "error")]
986    Error {
987        ename: String,
988        evalue: String,
989        traceback: Vec<String>,
990    },
991}
992
993/// A reply to an execute request. This is not the output of execution, as this is the reply over
994/// the `shell` socket. Any number of outputs can be emitted as `StreamContent`, `DisplayData`,
995/// `UpdateDisplayData`, `ExecuteResult`, and `ErrorOutput`. This message is used to communicate
996/// the status of the execution request, the execution count, and any user expressions that
997/// were requested.
998///
999/// See <https://jupyter-client.readthedocs.io/en/latest/messaging.html#execution-results>
1000#[derive(Serialize, Deserialize, Debug, Clone)]
1001pub struct ExecuteReply {
1002    #[serde(default)]
1003    pub status: ReplyStatus,
1004    // execution_count may be omitted by kernels (e.g. during abort or widget interactions)
1005    #[serde(default)]
1006    pub execution_count: ExecutionCount,
1007
1008    #[serde(default)]
1009    pub payload: Vec<Payload>,
1010    #[serde(default)]
1011    pub user_expressions: Option<HashMap<String, ExpressionResult>>,
1012
1013    #[serde(flatten, skip_serializing_if = "Option::is_none")]
1014    pub error: Option<Box<ReplyError>>,
1015}
1016impl Default for ExecuteReply {
1017    fn default() -> Self {
1018        Self {
1019            status: ReplyStatus::Ok,
1020            execution_count: ExecutionCount::new(0),
1021            payload: Vec::new(),
1022            user_expressions: None,
1023            error: None,
1024        }
1025    }
1026}
1027
1028/// Payloads are a way to trigger frontend actions from the kernel.
1029/// They are stated as deprecated, however they are in regular use via `?` in IPython
1030///
1031/// See <https://jupyter-client.readthedocs.io/en/latest/messaging.html#payloads-deprecated>
1032#[derive(Serialize, Deserialize, Debug, Clone)]
1033#[serde(rename_all = "snake_case")]
1034#[serde(tag = "source")]
1035pub enum Payload {
1036    Page {
1037        data: Media,
1038        start: usize,
1039    },
1040    SetNextInput {
1041        text: String,
1042        replace: bool,
1043    },
1044    EditMagic {
1045        filename: String,
1046        line_number: usize,
1047    },
1048    AskExit {
1049        // sic
1050        keepkernel: bool,
1051    },
1052}
1053
1054/// A request for information about the kernel.
1055///
1056/// See <https://jupyter-client.readthedocs.io/en/latest/messaging.html#kernel-info>
1057#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
1058pub struct KernelInfoRequest {}
1059
1060/// A reply containing information about the kernel.
1061///
1062/// See <https://jupyter-client.readthedocs.io/en/latest/messaging.html#kernel-info>
1063#[derive(Serialize, Deserialize, Debug, Clone)]
1064pub struct KernelInfoReply {
1065    /// Execution status. Per the Jupyter spec this should always be present,
1066    /// but some kernels omit it. Defaults to `Ok` when missing.
1067    /// Kernels should explicitly set this field.
1068    #[serde(default)]
1069    pub status: ReplyStatus,
1070    pub protocol_version: String,
1071    /// Kernel implementation name (e.g., "ipykernel", "IRkernel").
1072    /// Some kernels may not provide this field.
1073    #[serde(default)]
1074    pub implementation: String,
1075    /// Version of the kernel implementation.
1076    /// Some kernels may not provide this field.
1077    #[serde(default)]
1078    pub implementation_version: String,
1079    pub language_info: LanguageInfo,
1080    #[serde(default)]
1081    pub banner: String,
1082    #[serde(default)]
1083    pub help_links: Vec<HelpLink>,
1084    #[serde(default = "default_debugger")]
1085    pub debugger: bool,
1086    #[serde(flatten, skip_serializing_if = "Option::is_none")]
1087    pub error: Option<Box<ReplyError>>,
1088}
1089
1090fn default_debugger() -> bool {
1091    false
1092}
1093
1094#[derive(Serialize, Deserialize, Debug, Clone)]
1095#[serde(untagged)]
1096pub enum CodeMirrorMode {
1097    Simple(String),
1098    CustomMode { name: String, version: usize },
1099}
1100
1101#[derive(Serialize, Deserialize, Debug, Clone)]
1102pub struct CodeMirrorModeObject {
1103    pub name: String,
1104    pub version: usize,
1105}
1106
1107impl CodeMirrorMode {
1108    pub fn typescript() -> Self {
1109        Self::Simple("typescript".to_string())
1110    }
1111
1112    pub fn python() -> Self {
1113        Self::Simple("python".to_string())
1114    }
1115
1116    pub fn ipython_code_mirror_mode() -> Self {
1117        Self::CustomMode {
1118            name: "ipython".to_string(),
1119            version: 3,
1120        }
1121    }
1122}
1123
1124#[derive(Serialize, Deserialize, Debug, Clone)]
1125pub struct LanguageInfo {
1126    pub name: String,
1127    pub version: String,
1128    #[serde(skip_serializing_if = "Option::is_none")]
1129    pub mimetype: Option<String>,
1130    #[serde(skip_serializing_if = "Option::is_none")]
1131    pub file_extension: Option<String>,
1132    #[serde(skip_serializing_if = "Option::is_none")]
1133    pub pygments_lexer: Option<String>,
1134    #[serde(skip_serializing_if = "Option::is_none")]
1135    pub codemirror_mode: Option<CodeMirrorMode>,
1136    #[serde(skip_serializing_if = "Option::is_none")]
1137    pub nbconvert_exporter: Option<String>,
1138}
1139
1140#[derive(Serialize, Deserialize, Debug, Clone)]
1141pub struct HelpLink {
1142    pub text: String,
1143    pub url: String,
1144}
1145
1146#[derive(Serialize, Deserialize, Debug, Clone)]
1147pub enum Stdio {
1148    #[serde(rename = "stdout")]
1149    Stdout,
1150    #[serde(rename = "stderr")]
1151    Stderr,
1152}
1153
1154/// A `stream` message on the `iopub` channel. These are also known as "stdout" and "stderr".
1155///
1156/// See [Streams](https://jupyter-client.readthedocs.io/en/latest/messaging.html#streams-stdout-stderr-etc).
1157///
1158/// ## Example
1159/// The UI/client sends an `execute_request` message to the kernel.
1160///
1161/// ```rust
1162/// use jupyter_protocol::{ExecuteRequest, JupyterMessage};
1163/// // The UI/client sends an `execute_request` message to the kernel.
1164///
1165/// let execute_request = ExecuteRequest {
1166///     code: "print('Hello, World!')".to_string(),
1167///     silent: false,
1168///     store_history: true,
1169///     user_expressions: None,
1170///     allow_stdin: false,
1171///     stop_on_error: true,
1172/// };
1173///
1174/// let incoming_message: JupyterMessage = execute_request.into();
1175///
1176/// // ...
1177///
1178///
1179/// // On the kernel side, we receive the `execute_request` message.
1180/// //
1181/// // As a side effect of execution, the kernel can send `stream` messages to the UI/client.
1182/// // These are from using `print()`, `console.log()`, or similar. Anything on STDOUT or STDERR.
1183///
1184/// use jupyter_protocol::{StreamContent, Stdio};
1185///
1186/// let message = StreamContent {
1187///   name: Stdio::Stdout,
1188///   text: "Hello, World".to_string()
1189/// // To inform the UI that the kernel is emitting this stdout in response to the execution, we
1190/// // use `as_child_of` to set the parent header.
1191/// }.as_child_of(&incoming_message);
1192///
1193/// // next, send the `message` back over the iopub connection
1194/// ```
1195#[derive(Serialize, Deserialize, Debug, Clone)]
1196pub struct StreamContent {
1197    pub name: Stdio,
1198    pub text: String,
1199}
1200impl Default for StreamContent {
1201    fn default() -> Self {
1202        Self {
1203            name: Stdio::Stdout,
1204            text: String::new(),
1205        }
1206    }
1207}
1208
1209impl StreamContent {
1210    pub fn stdout(text: &str) -> Self {
1211        Self {
1212            name: Stdio::Stdout,
1213            text: text.to_string(),
1214        }
1215    }
1216
1217    pub fn stderr(text: &str) -> Self {
1218        Self {
1219            name: Stdio::Stderr,
1220            text: text.to_string(),
1221        }
1222    }
1223}
1224
1225/// Optional metadata for a display data to allow for updating an output.
1226#[derive(Serialize, Deserialize, Debug, Clone, Default)]
1227pub struct Transient {
1228    #[serde(skip_serializing_if = "Option::is_none")]
1229    pub display_id: Option<String>,
1230}
1231
1232/// A `display_data` message on the `iopub` channel.
1233///
1234/// See [Display Data](https://jupyter-client.readthedocs.io/en/latest/messaging.html#display-data).
1235///
1236/// ## Example
1237///
1238/// The UI/client sends an `execute_request` message to the kernel.
1239///
1240/// ```rust
1241/// use jupyter_protocol::{ExecuteRequest, JupyterMessage};
1242///
1243/// let execute_request: JupyterMessage = ExecuteRequest {
1244///     code: "print('Hello, World!')".to_string(),
1245///     silent: false,
1246///     store_history: true,
1247///     user_expressions: None,
1248///     allow_stdin: false,
1249///     stop_on_error: true,
1250/// }.into();
1251///
1252/// // As a side effect of execution, the kernel can send `display_data` messages to the UI/client.
1253///
1254/// use jupyter_protocol::{DisplayData, Media, MediaType};
1255///
1256/// let raw = r#"{
1257///     "text/plain": "Hello, world!",
1258///     "text/html": "<h1>Hello, world!</h1>"
1259/// }"#;
1260///
1261/// let bundle: Media = serde_json::from_str(raw).unwrap();
1262///
1263/// let message = DisplayData {
1264///    data: bundle,
1265///    metadata: Default::default(),
1266///    transient: None,
1267/// }.as_child_of(&execute_request);
1268/// // Send back the response over the iopub connection
1269///
1270/// ```
1271#[derive(Serialize, Deserialize, Debug, Clone, Default)]
1272pub struct DisplayData {
1273    pub data: Media,
1274    pub metadata: serde_json::Map<String, Value>,
1275    #[serde(default, skip_serializing_if = "Option::is_none")]
1276    pub transient: Option<Transient>,
1277}
1278
1279impl DisplayData {
1280    pub fn new(data: Media) -> Self {
1281        Self {
1282            data,
1283            metadata: Default::default(),
1284            transient: Default::default(),
1285        }
1286    }
1287}
1288
1289impl From<Vec<MediaType>> for DisplayData {
1290    fn from(content: Vec<MediaType>) -> Self {
1291        Self::new(Media::new(content))
1292    }
1293}
1294
1295impl From<MediaType> for DisplayData {
1296    fn from(content: MediaType) -> Self {
1297        Self::new(Media::new(vec![content]))
1298    }
1299}
1300
1301/// An `update_display_data` message on the `iopub` channel.
1302/// See [Update Display Data](https://jupyter-client.readthedocs.io/en/latest/messaging.html#update-display-data).
1303#[derive(Serialize, Deserialize, Debug, Clone, Default)]
1304pub struct UpdateDisplayData {
1305    pub data: Media,
1306    #[serde(default)]
1307    pub metadata: serde_json::Map<String, Value>,
1308    pub transient: Transient,
1309}
1310
1311impl UpdateDisplayData {
1312    pub fn new(data: Media, display_id: &str) -> Self {
1313        Self {
1314            data,
1315            metadata: Default::default(),
1316            transient: Transient {
1317                display_id: Some(display_id.to_string()),
1318            },
1319        }
1320    }
1321}
1322
1323/// An `execute_input` message on the `iopub` channel.
1324/// See [Execute Input](https://jupyter-client.readthedocs.io/en/latest/messaging.html#execute-input).
1325///
1326/// 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.
1327///
1328#[derive(Serialize, Deserialize, Debug, Clone)]
1329pub struct ExecuteInput {
1330    pub code: String,
1331    pub execution_count: ExecutionCount,
1332}
1333impl Default for ExecuteInput {
1334    fn default() -> Self {
1335        Self {
1336            code: String::new(),
1337            execution_count: ExecutionCount::new(0),
1338        }
1339    }
1340}
1341
1342/// An `execute_result` message on the `iopub` channel.
1343/// See [Execute Result](https://jupyter-client.readthedocs.io/en/latest/messaging.html#execute-result).
1344///
1345/// The is the "result", in the REPL sense from execution. As an example, the following Python code:
1346///
1347/// ```python
1348/// >>> 3 + 4
1349/// 7
1350/// ```
1351///
1352/// would have an `execute_result` message with the following content:
1353///
1354/// ```json
1355/// {
1356///     "execution_count": 1,
1357///     "data": {
1358///         "text/plain": "7"
1359///     },
1360///     "metadata": {},
1361///     "transient": {}
1362/// }
1363/// ```
1364///
1365#[derive(Serialize, Deserialize, Debug, Clone)]
1366pub struct ExecuteResult {
1367    pub execution_count: ExecutionCount,
1368    pub data: Media,
1369    #[serde(default)]
1370    pub metadata: serde_json::Map<String, Value>,
1371    pub transient: Option<Transient>,
1372}
1373impl Default for ExecuteResult {
1374    fn default() -> Self {
1375        Self {
1376            execution_count: ExecutionCount::new(0),
1377            data: Media::default(),
1378            metadata: serde_json::Map::new(),
1379            transient: None,
1380        }
1381    }
1382}
1383
1384impl ExecuteResult {
1385    pub fn new(execution_count: ExecutionCount, data: Media) -> Self {
1386        Self {
1387            execution_count,
1388            data,
1389            metadata: Default::default(),
1390            transient: None,
1391        }
1392    }
1393}
1394
1395impl From<(ExecutionCount, Vec<MediaType>)> for ExecuteResult {
1396    fn from((execution_count, content): (ExecutionCount, Vec<MediaType>)) -> Self {
1397        Self::new(execution_count, content.into())
1398    }
1399}
1400
1401impl From<(ExecutionCount, MediaType)> for ExecuteResult {
1402    fn from((execution_count, content): (ExecutionCount, MediaType)) -> Self {
1403        Self::new(execution_count, content.into())
1404    }
1405}
1406
1407/// An `error` message on the `iopub` channel.
1408/// See [Error](https://jupyter-client.readthedocs.io/en/latest/messaging.html#execution-errors).
1409///
1410/// These are errors that occur during execution from user code. Syntax errors, runtime errors, etc.
1411///
1412#[derive(Serialize, Deserialize, Debug, Clone, Default)]
1413pub struct ErrorOutput {
1414    pub ename: String,
1415    pub evalue: String,
1416    pub traceback: Vec<String>,
1417}
1418
1419/// A `comm_open` message on the `iopub` channel.
1420///
1421/// See [Comm Open](https://jupyter-client.readthedocs.io/en/latest/messaging.html#opening-a-comm).
1422///
1423/// Comm messages are one-way communications to update comm state, used for
1424/// synchronizing widget state, or simply requesting actions of a comm’s
1425/// counterpart.
1426///
1427/// Opening a Comm produces a `comm_open` message, to be sent to the other side:
1428///
1429/// ```json
1430/// {
1431///   "comm_id": "u-u-i-d",
1432///   "target_name": "my_comm",
1433///   "data": {}
1434/// }
1435/// ```
1436///
1437/// Every Comm has an ID and a target name. The code handling the message on
1438/// the receiving side is responsible for maintaining a mapping of target_name
1439/// keys to constructors. After a `comm_open` message has been sent, there
1440/// should be a corresponding Comm instance on both sides. The data key is
1441/// always a object with any extra JSON information used in initialization of
1442/// the comm.
1443///
1444/// If the `target_name` key is not found on the receiving side, then it should
1445/// immediately reply with a `comm_close` message to avoid an inconsistent state.
1446#[derive(Serialize, Deserialize, Debug, Clone)]
1447pub struct CommOpen {
1448    pub comm_id: CommId,
1449    pub target_name: String,
1450    pub data: serde_json::Map<String, Value>,
1451    #[serde(skip_serializing_if = "Option::is_none")]
1452    pub target_module: Option<String>,
1453}
1454impl Default for CommOpen {
1455    fn default() -> Self {
1456        Self {
1457            comm_id: CommId("".to_string()),
1458            target_name: String::new(),
1459            data: serde_json::Map::new(),
1460            target_module: None,
1461        }
1462    }
1463}
1464
1465/// A `comm_msg` message on the `iopub` channel.
1466///
1467/// Comm messages are one-way communications to update comm state, used for
1468/// synchronizing widget state, or simply requesting actions of a comm’s
1469/// counterpart.
1470///
1471/// Essentially, each comm pair defines their own message specification
1472/// implemented inside the data object.
1473///
1474/// There are no expected replies.
1475///
1476/// ```json
1477/// {
1478///   "comm_id": "u-u-i-d",
1479///   "data": {}
1480/// }
1481/// ```
1482///
1483#[derive(Serialize, Deserialize, Debug, Clone)]
1484pub struct CommMsg {
1485    pub comm_id: CommId,
1486    pub data: serde_json::Map<String, Value>,
1487}
1488impl Default for CommMsg {
1489    fn default() -> Self {
1490        Self {
1491            comm_id: CommId("".to_string()),
1492            data: serde_json::Map::new(),
1493        }
1494    }
1495}
1496
1497#[derive(Serialize, Deserialize, Debug, Clone, Default)]
1498pub struct CommInfoRequest {
1499    pub target_name: Option<String>,
1500}
1501
1502#[derive(Eq, Hash, PartialEq, Serialize, Deserialize, Debug, Clone)]
1503pub struct CommId(pub String);
1504
1505impl From<CommId> for String {
1506    fn from(comm_id: CommId) -> Self {
1507        comm_id.0
1508    }
1509}
1510
1511impl From<String> for CommId {
1512    fn from(comm_id: String) -> Self {
1513        Self(comm_id)
1514    }
1515}
1516
1517#[derive(Serialize, Deserialize, Debug, Clone)]
1518pub struct CommInfo {
1519    pub target_name: String,
1520}
1521
1522#[derive(Serialize, Deserialize, Debug, Clone)]
1523pub struct CommInfoReply {
1524    #[serde(default)]
1525    pub status: ReplyStatus,
1526    #[serde(default)]
1527    pub comms: HashMap<CommId, CommInfo>,
1528    // pub comms: HashMap<CommId, CommInfo>,
1529    #[serde(flatten, skip_serializing_if = "Option::is_none")]
1530    pub error: Option<Box<ReplyError>>,
1531}
1532impl Default for CommInfoReply {
1533    fn default() -> Self {
1534        Self {
1535            status: ReplyStatus::Ok,
1536            comms: HashMap::new(),
1537            error: None,
1538        }
1539    }
1540}
1541
1542/// A `comm_close` message on the `iopub` channel.
1543///
1544/// Since comms live on both sides, when a comm is destroyed the other side must
1545/// be notified. This is done with a comm_close message.
1546#[derive(Serialize, Deserialize, Debug, Clone)]
1547pub struct CommClose {
1548    pub comm_id: CommId,
1549    pub data: serde_json::Map<String, Value>,
1550}
1551impl Default for CommClose {
1552    fn default() -> Self {
1553        Self {
1554            comm_id: CommId("".to_string()),
1555            data: serde_json::Map::new(),
1556        }
1557    }
1558}
1559
1560#[derive(Serialize, Deserialize, Debug, Clone, Default)]
1561/// Request to shut down the kernel.
1562///
1563/// Upon receiving this message, the kernel will send a reply and then shut itself down.
1564/// If `restart` is True, the kernel will restart itself after shutting down.
1565///
1566/// See <https://jupyter-client.readthedocs.io/en/latest/messaging.html#kernel-shutdown>
1567pub struct ShutdownRequest {
1568    pub restart: bool,
1569}
1570
1571#[derive(Serialize, Deserialize, Debug, Clone, Default)]
1572/// Request to interrupt the kernel.
1573///
1574/// This message is used when the kernel's `interrupt_mode` is set to "message"
1575/// in its kernelspec. It allows the kernel to be interrupted via a message
1576/// instead of an operating system signal.
1577///
1578/// See <https://jupyter-client.readthedocs.io/en/latest/messaging.html#kernel-interrupt>
1579pub struct InterruptRequest {}
1580
1581#[derive(Serialize, Deserialize, Debug, Clone)]
1582/// Reply to an interrupt request.
1583///
1584/// This message is sent by the kernel in response to an `InterruptRequest`.
1585/// It indicates whether the interrupt was successful.
1586///
1587/// See <https://jupyter-client.readthedocs.io/en/latest/messaging.html#kernel-interrupt>
1588pub struct InterruptReply {
1589    pub status: ReplyStatus,
1590
1591    #[serde(flatten, skip_serializing_if = "Option::is_none")]
1592    pub error: Option<Box<ReplyError>>,
1593}
1594
1595impl Default for InterruptReply {
1596    fn default() -> Self {
1597        Self::new()
1598    }
1599}
1600
1601impl InterruptReply {
1602    pub fn new() -> Self {
1603        Self {
1604            status: ReplyStatus::Ok,
1605            error: None,
1606        }
1607    }
1608}
1609
1610#[derive(Serialize, Deserialize, Debug, Clone)]
1611/// Reply to a shutdown request.
1612///
1613/// This message is sent by the kernel in response to a `ShutdownRequest`.
1614/// It confirms that the kernel is shutting down.
1615///
1616/// See <https://jupyter-client.readthedocs.io/en/latest/messaging.html#kernel-shutdown>
1617pub struct ShutdownReply {
1618    pub restart: bool,
1619    pub status: ReplyStatus,
1620
1621    #[serde(flatten, skip_serializing_if = "Option::is_none")]
1622    pub error: Option<Box<ReplyError>>,
1623}
1624impl Default for ShutdownReply {
1625    fn default() -> Self {
1626        Self {
1627            restart: false,
1628            status: ReplyStatus::Ok,
1629            error: None,
1630        }
1631    }
1632}
1633
1634#[derive(Serialize, Deserialize, Debug, Clone)]
1635/// Request for input from the frontend.
1636///
1637/// This message is sent by the kernel when it needs to prompt the user for input.
1638/// It's typically used to implement functions like Python's `input()` or R's `readline()`.
1639///
1640/// See <https://jupyter-client.readthedocs.io/en/latest/messaging.html#messages-on-the-stdin-router-dealer-channel>
1641pub struct InputRequest {
1642    pub prompt: String,
1643    pub password: bool,
1644}
1645impl Default for InputRequest {
1646    fn default() -> Self {
1647        Self {
1648            prompt: "> ".to_string(),
1649            password: false,
1650        }
1651    }
1652}
1653
1654#[derive(Serialize, Deserialize, Debug, Clone)]
1655/// Reply to an input request.
1656///
1657/// This message is sent by the frontend in response to an `InputRequest`.
1658/// It contains the user's input.
1659///
1660/// See <https://jupyter-client.readthedocs.io/en/latest/messaging.html#messages-on-the-stdin-router-dealer-channel>
1661pub struct InputReply {
1662    #[serde(default)]
1663    pub value: String,
1664    #[serde(default)]
1665    pub status: ReplyStatus,
1666    #[serde(flatten, skip_serializing_if = "Option::is_none")]
1667    pub error: Option<Box<ReplyError>>,
1668}
1669impl Default for InputReply {
1670    fn default() -> Self {
1671        Self {
1672            value: String::new(),
1673            status: ReplyStatus::Ok,
1674            error: None,
1675        }
1676    }
1677}
1678
1679/// A `inspect_request` message on the `shell` channel.
1680///
1681/// Code can be inspected to show useful information to the user.
1682/// It is up to the Kernel to decide what information should be displayed, and its formatting.
1683///
1684#[derive(Serialize, Deserialize, Debug, Clone)]
1685pub struct InspectRequest {
1686    /// The code context in which introspection is requested
1687    /// this may be up to an entire multiline cell.
1688    pub code: String,
1689    /// The cursor position within 'code' (in unicode characters) where inspection is requested
1690    pub cursor_pos: usize,
1691    /// The level of detail desired.  In IPython, the default (0) is equivalent to typing
1692    /// 'x?' at the prompt, 1 is equivalent to 'x??'.
1693    /// The difference is up to kernels, but in IPython level 1 includes the source code
1694    /// if available.
1695    pub detail_level: Option<usize>,
1696}
1697impl Default for InspectRequest {
1698    fn default() -> Self {
1699        Self {
1700            code: String::new(),
1701            cursor_pos: 0,
1702            detail_level: Some(0),
1703        }
1704    }
1705}
1706
1707/// Deserialize a field that might be null as its default value
1708fn deserialize_null_as_default<'de, D, T>(deserializer: D) -> Result<T, D::Error>
1709where
1710    D: serde::Deserializer<'de>,
1711    T: Default + serde::Deserialize<'de>,
1712{
1713    let opt = Option::deserialize(deserializer)?;
1714    Ok(opt.unwrap_or_default())
1715}
1716
1717#[derive(Serialize, Deserialize, Debug, Clone)]
1718pub struct InspectReply {
1719    #[serde(default)]
1720    pub found: bool,
1721    /// Some kernels return null for data when inspection is not found
1722    #[serde(default, deserialize_with = "deserialize_null_as_default")]
1723    pub data: Media,
1724    #[serde(default, deserialize_with = "deserialize_null_as_default")]
1725    pub metadata: serde_json::Map<String, Value>,
1726    #[serde(default)]
1727    pub status: ReplyStatus,
1728    #[serde(flatten, skip_serializing_if = "Option::is_none")]
1729    pub error: Option<Box<ReplyError>>,
1730}
1731impl Default for InspectReply {
1732    fn default() -> Self {
1733        Self {
1734            found: false,
1735            data: Media::default(),
1736            metadata: serde_json::Map::new(),
1737            status: ReplyStatus::Ok,
1738            error: None,
1739        }
1740    }
1741}
1742
1743/// A request for code completion suggestions.
1744///
1745/// See <https://jupyter-client.readthedocs.io/en/latest/messaging.html#completion>
1746#[derive(Serialize, Deserialize, Debug, Clone, Default)]
1747pub struct CompleteRequest {
1748    pub code: String,
1749    pub cursor_pos: usize,
1750}
1751
1752/// A reply containing code completion suggestions.
1753///
1754/// See <https://jupyter-client.readthedocs.io/en/latest/messaging.html#completion>
1755#[derive(Serialize, Deserialize, Debug, Clone)]
1756pub struct CompleteReply {
1757    #[serde(default)]
1758    pub matches: Vec<String>,
1759    #[serde(default)]
1760    pub cursor_start: usize,
1761    #[serde(default)]
1762    pub cursor_end: usize,
1763    #[serde(default)]
1764    pub metadata: serde_json::Map<String, Value>,
1765    #[serde(default)]
1766    pub status: ReplyStatus,
1767    #[serde(flatten, skip_serializing_if = "Option::is_none")]
1768    pub error: Option<Box<ReplyError>>,
1769}
1770impl Default for CompleteReply {
1771    fn default() -> Self {
1772        Self {
1773            matches: Vec::new(),
1774            cursor_start: 0,
1775            cursor_end: 0,
1776            metadata: serde_json::Map::new(),
1777            status: ReplyStatus::Ok,
1778            error: None,
1779        }
1780    }
1781}
1782
1783#[derive(Serialize, Deserialize, Debug, Clone)]
1784pub struct DebugRequest {
1785    #[serde(flatten)]
1786    pub content: Value,
1787}
1788impl Default for DebugRequest {
1789    fn default() -> Self {
1790        Self {
1791            content: Value::Null,
1792        }
1793    }
1794}
1795
1796#[derive(Serialize, Deserialize, Debug, Clone)]
1797pub struct DebugReply {
1798    #[serde(flatten)]
1799    pub content: Value,
1800}
1801impl Default for DebugReply {
1802    fn default() -> Self {
1803        Self {
1804            content: Value::Null,
1805        }
1806    }
1807}
1808
1809#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
1810#[serde(rename_all = "snake_case")]
1811pub enum IsCompleteReplyStatus {
1812    /// The code is incomplete, and the frontend should prompt the user for more
1813    /// input.
1814    Incomplete,
1815    /// The code is ready to be executed.
1816    Complete,
1817    /// The code is invalid, yet can be sent for execution to see a syntax error.
1818    Invalid,
1819    /// The kernel is unable to determine status. The frontend should also
1820    /// handle the kernel not replying promptly. It may default to sending the
1821    /// code for execution, or it may implement simple fallback heuristics for
1822    /// whether to execute the code (e.g. execute after a blank line).
1823    Unknown,
1824}
1825
1826#[derive(Serialize, Deserialize, Debug, Clone)]
1827pub struct IsCompleteReply {
1828    /// Unlike other reply messages, the status is unique to this message, using `IsCompleteReplyStatus`
1829    /// instead of `ReplyStatus`.
1830    pub status: IsCompleteReplyStatus,
1831    /// If status is 'incomplete', indent should contain the characters to use
1832    /// to indent the next line. This is only a hint: frontends may ignore it
1833    /// and use their own autoindentation rules. For other statuses, this
1834    /// field does not exist.
1835    #[serde(default)]
1836    pub indent: String,
1837}
1838impl Default for IsCompleteReply {
1839    fn default() -> Self {
1840        Self {
1841            status: IsCompleteReplyStatus::Unknown,
1842            indent: String::new(),
1843        }
1844    }
1845}
1846
1847impl IsCompleteReply {
1848    pub fn new(status: IsCompleteReplyStatus, indent: String) -> Self {
1849        Self { status, indent }
1850    }
1851
1852    pub fn incomplete(indent: String) -> Self {
1853        Self::new(IsCompleteReplyStatus::Incomplete, indent)
1854    }
1855
1856    pub fn complete() -> Self {
1857        Self::new(IsCompleteReplyStatus::Complete, String::new())
1858    }
1859
1860    pub fn invalid() -> Self {
1861        Self::new(IsCompleteReplyStatus::Invalid, String::new())
1862    }
1863
1864    pub fn unknown() -> Self {
1865        Self::new(IsCompleteReplyStatus::Unknown, String::new())
1866    }
1867}
1868
1869#[derive(Serialize, Deserialize, Debug, Clone)]
1870#[serde(tag = "hist_access_type")]
1871pub enum HistoryRequest {
1872    #[serde(rename = "range")]
1873    Range {
1874        session: Option<i32>,
1875        start: i32,
1876        stop: i32,
1877        output: bool,
1878        raw: bool,
1879    },
1880    #[serde(rename = "tail")]
1881    Tail { n: i32, output: bool, raw: bool },
1882    #[serde(rename = "search")]
1883    Search {
1884        pattern: String,
1885        unique: bool,
1886        output: bool,
1887        raw: bool,
1888        n: i32,
1889    },
1890}
1891impl Default for HistoryRequest {
1892    fn default() -> Self {
1893        Self::Range {
1894            session: None,
1895            start: 0,
1896            stop: 0,
1897            output: false,
1898            raw: false,
1899        }
1900    }
1901}
1902
1903#[derive(Serialize, Deserialize, Debug, Clone)]
1904#[serde(untagged)]
1905pub enum HistoryEntry {
1906    // When history_request.output is false
1907    // (session, line_number, input)
1908    Input(usize, usize, String),
1909    // When history_request.output is true
1910    // (session, line_number, (input, output))
1911    InputOutput(usize, usize, (String, String)),
1912}
1913
1914/// A reply containing execution history.
1915///
1916/// See <https://jupyter-client.readthedocs.io/en/latest/messaging.html#history>
1917#[derive(Serialize, Deserialize, Debug, Clone)]
1918pub struct HistoryReply {
1919    pub history: Vec<HistoryEntry>,
1920
1921    pub status: ReplyStatus,
1922    #[serde(flatten, skip_serializing_if = "Option::is_none")]
1923    pub error: Option<Box<ReplyError>>,
1924}
1925impl Default for HistoryReply {
1926    fn default() -> Self {
1927        Self {
1928            history: Vec::new(),
1929            status: ReplyStatus::Ok,
1930            error: None,
1931        }
1932    }
1933}
1934
1935impl HistoryReply {
1936    pub fn new(history: Vec<HistoryEntry>) -> Self {
1937        Self {
1938            history,
1939            status: ReplyStatus::Ok,
1940            error: None,
1941        }
1942    }
1943}
1944
1945/// A request to check if the code is complete and ready for execution.
1946///
1947/// See <https://jupyter-client.readthedocs.io/en/latest/messaging.html#code-completeness>
1948#[derive(Serialize, Deserialize, Debug, Clone, Default)]
1949pub struct IsCompleteRequest {
1950    pub code: String,
1951}
1952
1953#[derive(Debug, Clone, PartialEq)]
1954pub enum ExecutionState {
1955    Unknown,
1956    Starting,
1957    Busy,
1958    Idle,
1959    Restarting,
1960    Terminating,
1961    AutoRestarting,
1962    Dead,
1963    Other(String),
1964}
1965
1966impl ExecutionState {
1967    pub fn as_str(&self) -> &str {
1968        match self {
1969            ExecutionState::Unknown => "unknown",
1970            ExecutionState::Terminating => "terminating",
1971            ExecutionState::AutoRestarting => "autorestarting",
1972            ExecutionState::Dead => "dead",
1973            ExecutionState::Busy => "busy",
1974            ExecutionState::Idle => "idle",
1975            ExecutionState::Starting => "starting",
1976            ExecutionState::Restarting => "restarting",
1977            ExecutionState::Other(s) => s,
1978        }
1979    }
1980}
1981
1982impl serde::Serialize for ExecutionState {
1983    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1984    where
1985        S: serde::Serializer,
1986    {
1987        match self {
1988            ExecutionState::Unknown => serializer.serialize_str("unknown"),
1989            ExecutionState::Terminating => serializer.serialize_str("terminating"),
1990            ExecutionState::AutoRestarting => serializer.serialize_str("autorestarting"),
1991            ExecutionState::Dead => serializer.serialize_str("dead"),
1992            ExecutionState::Busy => serializer.serialize_str("busy"),
1993            ExecutionState::Idle => serializer.serialize_str("idle"),
1994            ExecutionState::Starting => serializer.serialize_str("starting"),
1995            ExecutionState::Restarting => serializer.serialize_str("restarting"),
1996            ExecutionState::Other(s) => serializer.serialize_str(s),
1997        }
1998    }
1999}
2000
2001impl<'de> serde::Deserialize<'de> for ExecutionState {
2002    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2003    where
2004        D: serde::Deserializer<'de>,
2005    {
2006        struct ExecutionStateVisitor;
2007
2008        impl serde::de::Visitor<'_> for ExecutionStateVisitor {
2009            type Value = ExecutionState;
2010
2011            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
2012                formatter.write_str("a string representing an execution state")
2013            }
2014            fn visit_str<E>(self, value: &str) -> Result<ExecutionState, E>
2015            where
2016                E: serde::de::Error,
2017            {
2018                match value {
2019                    "unknown" => Ok(ExecutionState::Unknown),
2020                    "terminating" => Ok(ExecutionState::Terminating),
2021                    "autorestarting" => Ok(ExecutionState::AutoRestarting),
2022                    "dead" => Ok(ExecutionState::Dead),
2023                    "busy" => Ok(ExecutionState::Busy),
2024                    "idle" => Ok(ExecutionState::Idle),
2025                    "starting" => Ok(ExecutionState::Starting),
2026                    "restarting" => Ok(ExecutionState::Restarting),
2027                    other => Ok(ExecutionState::Other(other.to_string())),
2028                }
2029            }
2030        }
2031        deserializer.deserialize_str(ExecutionStateVisitor)
2032    }
2033}
2034
2035/// A message indicating the current status of the kernel.
2036///
2037/// See <https://jupyter-client.readthedocs.io/en/latest/messaging.html#kernel-status>
2038#[derive(Serialize, Deserialize, Debug, Clone)]
2039pub struct Status {
2040    pub execution_state: ExecutionState,
2041}
2042impl Default for Status {
2043    fn default() -> Self {
2044        Self {
2045            execution_state: ExecutionState::Idle,
2046        }
2047    }
2048}
2049
2050impl Status {
2051    pub fn busy() -> Self {
2052        Self {
2053            execution_state: ExecutionState::Busy,
2054        }
2055    }
2056
2057    pub fn idle() -> Self {
2058        Self {
2059            execution_state: ExecutionState::Idle,
2060        }
2061    }
2062
2063    pub fn starting() -> Self {
2064        Self {
2065            execution_state: ExecutionState::Starting,
2066        }
2067    }
2068
2069    pub fn restarting() -> Self {
2070        Self {
2071            execution_state: ExecutionState::Restarting,
2072        }
2073    }
2074
2075    pub fn other(state: impl Into<String>) -> Self {
2076        Self {
2077            execution_state: ExecutionState::Other(state.into()),
2078        }
2079    }
2080}
2081
2082/// An `iopub_welcome` message on the `iopub` channel.
2083///
2084/// Per JEP 65, when IOPub is implemented as XPUB, this message is sent
2085/// to clients upon subscription to indicate the kernel is ready to publish.
2086///
2087/// See [JEP 65](https://github.com/jupyter/enhancement-proposals/blob/master/65-jupyter-xpub/jupyter-xpub.md)
2088#[derive(Serialize, Deserialize, Debug, Clone, Default)]
2089pub struct IoPubWelcome {
2090    /// The subscription topic that was received from the client.
2091    /// Typically an empty string for wildcard subscriptions.
2092    pub subscription: String,
2093}
2094
2095impl IoPubWelcome {
2096    pub fn new(subscription: String) -> Self {
2097        Self { subscription }
2098    }
2099}
2100
2101#[cfg(test)]
2102mod test {
2103    use serde_json::json;
2104
2105    use super::*;
2106
2107    #[test]
2108    fn test_execute_request_serialize() {
2109        let request = ExecuteRequest {
2110            code: "print('Hello, World!')".to_string(),
2111            silent: false,
2112            store_history: true,
2113            user_expressions: Some(HashMap::new()),
2114            allow_stdin: false,
2115            stop_on_error: true,
2116        };
2117        let request_value = serde_json::to_value(request).unwrap();
2118
2119        let expected_request_value = serde_json::json!({
2120            "code": "print('Hello, World!')",
2121            "silent": false,
2122            "store_history": true,
2123            "user_expressions": {},
2124            "allow_stdin": false,
2125            "stop_on_error": true
2126        });
2127
2128        assert_eq!(request_value, expected_request_value);
2129    }
2130
2131    #[test]
2132    fn test_execute_request_user_expressions_serializes_to_empty_dict() {
2133        let request = ExecuteRequest {
2134            code: "print('Hello, World!')".to_string(),
2135            silent: false,
2136            store_history: true,
2137            user_expressions: None,
2138            allow_stdin: false,
2139            stop_on_error: true,
2140        };
2141        let request_value = serde_json::to_value(request).unwrap();
2142
2143        let expected_request_value = serde_json::json!({
2144            "code": "print('Hello, World!')",
2145            "silent": false,
2146            "store_history": true,
2147            "user_expressions": {},
2148            "allow_stdin": false,
2149            "stop_on_error": true
2150        });
2151
2152        assert_eq!(request_value, expected_request_value);
2153    }
2154
2155    #[test]
2156    fn test_into_various() {
2157        let kernel_info_request = KernelInfoRequest {};
2158        let content: JupyterMessageContent = kernel_info_request.clone().into();
2159        let message: JupyterMessage = content.into();
2160        assert!(message.parent_header.is_none());
2161        match message.content {
2162            JupyterMessageContent::KernelInfoRequest(req) => {
2163                assert_eq!(req, kernel_info_request);
2164            }
2165            _ => panic!("Expected KernelInfoRequest"),
2166        }
2167
2168        let kernel_info_request = KernelInfoRequest {};
2169        let message: JupyterMessage = kernel_info_request.clone().into();
2170        assert!(message.parent_header.is_none());
2171        match message.content {
2172            JupyterMessageContent::KernelInfoRequest(req) => {
2173                assert_eq!(req, kernel_info_request);
2174            }
2175            _ => panic!("Expected KernelInfoRequest"),
2176        }
2177    }
2178
2179    #[test]
2180    fn test_default() {
2181        let msg: JupyterMessage = ExecuteRequest {
2182            code: "import this".to_string(),
2183            ..Default::default()
2184        }
2185        .into();
2186
2187        assert_eq!(msg.header.msg_type, "execute_request");
2188        assert_eq!(msg.header.msg_id.len(), 36);
2189
2190        match msg.content {
2191            JupyterMessageContent::ExecuteRequest(req) => {
2192                assert_eq!(req.code, "import this");
2193                assert!(!req.silent);
2194                assert!(req.store_history);
2195                assert_eq!(req.user_expressions, None);
2196                assert!(!req.allow_stdin);
2197                assert!(req.stop_on_error);
2198            }
2199            _ => panic!("Expected ExecuteRequest"),
2200        }
2201    }
2202
2203    #[test]
2204    fn test_deserialize_payload() {
2205        let raw_execute_reply_content = r#"
2206        {
2207            "status": "ok",
2208            "execution_count": 1,
2209            "payload": [{
2210                "source": "page",
2211                "data": {
2212                    "text/html": "<h1>Hello</h1>",
2213                    "text/plain": "Hello"
2214                },
2215                "start": 0
2216            }],
2217            "user_expressions": {}
2218        }
2219        "#;
2220
2221        let execute_reply: ExecuteReply = serde_json::from_str(raw_execute_reply_content).unwrap();
2222
2223        assert_eq!(execute_reply.status, ReplyStatus::Ok);
2224        assert_eq!(execute_reply.execution_count, ExecutionCount::new(1));
2225
2226        let payload = execute_reply.payload.clone();
2227
2228        assert_eq!(payload.len(), 1);
2229        let payload = payload.first().unwrap();
2230
2231        let media = match payload {
2232            Payload::Page { data, .. } => data,
2233            _ => panic!("Expected Page payload type"),
2234        };
2235
2236        let media = serde_json::to_value(media).unwrap();
2237
2238        let expected_media = serde_json::json!({
2239            "text/html": "<h1>Hello</h1>",
2240            "text/plain": "Hello"
2241        });
2242
2243        assert_eq!(media, expected_media);
2244    }
2245
2246    #[test]
2247    pub fn test_display_data_various_data() {
2248        let display_data = DisplayData {
2249            data: serde_json::from_value(json!({
2250                "text/plain": "Hello, World!",
2251                "text/html": "<h1>Hello, World!</h1>",
2252                "application/json": {
2253                    "hello": "world",
2254                    "foo": "bar",
2255                    "ok": [1, 2, 3],
2256                }
2257            }))
2258            .unwrap(),
2259            ..Default::default()
2260        };
2261
2262        let display_data_value = serde_json::to_value(display_data).unwrap();
2263
2264        let expected_display_data_value = serde_json::json!({
2265            "data": {
2266                "text/plain": "Hello, World!",
2267                "text/html": "<h1>Hello, World!</h1>",
2268                "application/json": {
2269                    "hello": "world",
2270                    "foo": "bar",
2271                    "ok": [1, 2, 3]
2272                }
2273            },
2274            "metadata": {}
2275        });
2276
2277        assert_eq!(display_data_value, expected_display_data_value);
2278    }
2279
2280    use std::mem::size_of;
2281
2282    macro_rules! size_of_variant {
2283        ($variant:ty) => {
2284            let size = size_of::<$variant>();
2285            println!("The size of {} is: {} bytes", stringify!($variant), size);
2286
2287            assert!(size <= 96);
2288        };
2289    }
2290
2291    #[test]
2292    fn test_enum_variant_sizes() {
2293        size_of_variant!(ClearOutput);
2294        size_of_variant!(CommClose);
2295        size_of_variant!(CommInfoReply);
2296        size_of_variant!(CommInfoRequest);
2297        size_of_variant!(CommMsg);
2298        size_of_variant!(CommOpen);
2299        size_of_variant!(CompleteReply);
2300        size_of_variant!(CompleteRequest);
2301        size_of_variant!(DebugReply);
2302        size_of_variant!(DebugRequest);
2303        size_of_variant!(DisplayData);
2304        size_of_variant!(ErrorOutput);
2305        size_of_variant!(ExecuteInput);
2306        size_of_variant!(ExecuteReply);
2307        size_of_variant!(ExecuteRequest);
2308        size_of_variant!(ExecuteResult);
2309        size_of_variant!(HistoryReply);
2310        size_of_variant!(HistoryRequest);
2311        size_of_variant!(InputReply);
2312        size_of_variant!(InputRequest);
2313        size_of_variant!(InspectReply);
2314        size_of_variant!(InspectRequest);
2315        size_of_variant!(InterruptReply);
2316        size_of_variant!(InterruptRequest);
2317        size_of_variant!(IsCompleteReply);
2318        size_of_variant!(IsCompleteRequest);
2319        size_of_variant!(Box<KernelInfoReply>);
2320        size_of_variant!(KernelInfoRequest);
2321        size_of_variant!(ShutdownReply);
2322        size_of_variant!(ShutdownRequest);
2323        size_of_variant!(Status);
2324        size_of_variant!(StreamContent);
2325        size_of_variant!(UnknownMessage);
2326        size_of_variant!(UpdateDisplayData);
2327    }
2328
2329    #[test]
2330    fn test_jupyter_message_content_enum_size() {
2331        let size = size_of::<JupyterMessageContent>();
2332        println!("The size of JupyterMessageContent is: {}", size);
2333        assert!(size > 0);
2334        assert!(size <= 104);
2335    }
2336
2337    #[test]
2338    fn test_jupyter_message_parent_header_serializes_to_empty_dict() {
2339        let request = ExecuteRequest {
2340            code: "1 + 1".to_string(),
2341            ..Default::default()
2342        };
2343        let message = JupyterMessage::from(request);
2344
2345        let serialized_message = serde_json::to_value(message).unwrap();
2346
2347        // Test that the `parent_header` field is an empty object.
2348        let parent_header = serialized_message.get("parent_header").unwrap();
2349        assert!(parent_header.is_object());
2350        assert!(parent_header.as_object().unwrap().is_empty());
2351    }
2352
2353    #[test]
2354    fn test_user_expressions_serialization() {
2355        let request = ExecuteRequest {
2356            code: "pass".to_string(),
2357            silent: false,
2358            store_history: true,
2359            user_expressions: Some(HashMap::from([(
2360                String::from("expression"),
2361                String::from("42 + 7"),
2362            )])),
2363            allow_stdin: false,
2364            stop_on_error: true,
2365        };
2366        let request_value = serde_json::to_value(request.clone()).unwrap();
2367
2368        let expected_request_value = serde_json::json!({
2369            "code": "pass",
2370            "silent": false,
2371            "store_history": true,
2372            "user_expressions": {"expression": "42 + 7"},
2373            "allow_stdin": false,
2374            "stop_on_error": true
2375        });
2376
2377        assert_eq!(request_value, expected_request_value);
2378
2379        let deserialized_request: ExecuteRequest = serde_json::from_value(request_value).unwrap();
2380        assert_eq!(
2381            deserialized_request.user_expressions,
2382            request.user_expressions
2383        );
2384    }
2385
2386    #[test]
2387    fn test_execute_reply_user_expressions_deserialization() {
2388        let reply_json = serde_json::json!({
2389            "status": "ok",
2390            "execution_count": 1,
2391            "payload": [],
2392            "user_expressions": {
2393                "answer": {
2394                    "status": "ok",
2395                    "data": {"text/plain": "55"},
2396                    "metadata": {}
2397                },
2398                "bad_expr": {
2399                    "status": "error",
2400                    "ename": "NameError",
2401                    "evalue": "name 'undefined_var' is not defined",
2402                    "traceback": ["Traceback ...", "NameError: name 'undefined_var' is not defined"]
2403                }
2404            }
2405        });
2406
2407        let reply: ExecuteReply = serde_json::from_value(reply_json).unwrap();
2408        assert_eq!(reply.execution_count.value(), 1);
2409
2410        let user_exprs = reply.user_expressions.unwrap();
2411        assert_eq!(user_exprs.len(), 2);
2412
2413        // Check the successful expression
2414        match &user_exprs["answer"] {
2415            ExpressionResult::Ok { data, metadata: _ } => {
2416                let plain = data.content.iter().find_map(|m| match m {
2417                    MediaType::Plain(text) => Some(text.as_str()),
2418                    _ => None,
2419                });
2420                assert_eq!(plain, Some("55"));
2421            }
2422            other => panic!("Expected Ok variant, got {:?}", other),
2423        }
2424
2425        // Check the error expression
2426        match &user_exprs["bad_expr"] {
2427            ExpressionResult::Error {
2428                ename,
2429                evalue,
2430                traceback,
2431            } => {
2432                assert_eq!(ename, "NameError");
2433                assert_eq!(evalue, "name 'undefined_var' is not defined");
2434                assert_eq!(traceback.len(), 2);
2435            }
2436            other => panic!("Expected Error variant, got {:?}", other),
2437        }
2438    }
2439
2440    #[test]
2441    fn test_execute_reply_without_user_expressions() {
2442        let reply_json = serde_json::json!({
2443            "status": "ok",
2444            "execution_count": 1,
2445            "payload": []
2446        });
2447
2448        let reply: ExecuteReply = serde_json::from_value(reply_json).unwrap();
2449        assert!(reply.user_expressions.is_none());
2450    }
2451
2452    #[test]
2453    fn test_execute_reply_without_execution_count() {
2454        let reply_json = serde_json::json!({
2455            "status": "aborted",
2456            "payload": []
2457        });
2458        let reply: ExecuteReply = serde_json::from_value(reply_json).unwrap();
2459        assert_eq!(reply.execution_count, ExecutionCount::new(0));
2460        assert_eq!(reply.status, ReplyStatus::Aborted);
2461    }
2462
2463    #[test]
2464    fn test_jupyter_message_parent_header_deserialize() {
2465        let msg = r#"
2466  {
2467    "buffers": [],
2468    "channel": "shell",
2469    "content": {},
2470    "header": {
2471        "date": "2025-05-14T14:32:23.490Z",
2472        "msg_id": "44bd6b44-78a1-4892-87df-c0861a005d56",
2473        "msg_type": "kernel_info_request",
2474        "session": "b75bddaa-6d69-4340-ba13-81516192370e",
2475        "username": "",
2476        "version": "5.2"
2477    },
2478    "metadata": {},
2479    "parent_header": {
2480        "date": "2025-05-14T14:32:23.490Z",
2481        "msg_id": "2aaf8916-6b83-4f5a-80dd-633e94f5d8e1",
2482        "msg_type": "kernel_info_request",
2483        "session": "e2a3165d-76a8-4fef-850f-712102589660",
2484        "username": "",
2485        "version": "5.2"
2486    }
2487}
2488        "#;
2489
2490        let message: JupyterMessage = serde_json::from_str(msg).unwrap();
2491        assert!(message.parent_header.is_some());
2492        assert_eq!(
2493            message.parent_header.as_ref().unwrap().msg_type,
2494            "kernel_info_request"
2495        );
2496        assert_eq!(
2497            message.parent_header.as_ref().unwrap().msg_id,
2498            "2aaf8916-6b83-4f5a-80dd-633e94f5d8e1"
2499        );
2500        assert_eq!(
2501            message.header.msg_id,
2502            "44bd6b44-78a1-4892-87df-c0861a005d56"
2503        );
2504    }
2505
2506    #[test]
2507    fn test_jupyter_message_empty_parent_header_deserialize() {
2508        let msg = r#"
2509  {
2510    "buffers": [],
2511    "channel": "shell",
2512    "content": {},
2513    "header": {
2514        "date": "2025-05-14T14:32:23.490Z",
2515        "msg_id": "44bd6b44-78a1-4892-87df-c0861a005d56",
2516        "msg_type": "kernel_info_request",
2517        "session": "b75bddaa-6d69-4340-ba13-81516192370e",
2518        "username": "",
2519        "version": "5.2"
2520    },
2521    "metadata": {},
2522    "parent_header": {}
2523}
2524        "#;
2525
2526        let message: JupyterMessage = serde_json::from_str(msg).unwrap();
2527        assert!(message.parent_header.is_none());
2528        assert_eq!(message.header.msg_type, "kernel_info_request");
2529        assert_eq!(
2530            message.header.msg_id,
2531            "44bd6b44-78a1-4892-87df-c0861a005d56"
2532        );
2533    }
2534
2535    #[test]
2536    fn test_execution_state_other_serde() {
2537        let json = r#""busy""#;
2538        let state: ExecutionState = serde_json::from_str(json).unwrap();
2539        assert_eq!(state, ExecutionState::Busy);
2540        let serialized = serde_json::to_string(&state).unwrap();
2541        assert_eq!(serialized, "\"busy\"");
2542
2543        let state = ExecutionState::Idle;
2544        let serialized = serde_json::to_string(&state).unwrap();
2545        assert_eq!(serialized, "\"idle\"");
2546        let state: ExecutionState = serde_json::from_str(&serialized).unwrap();
2547        assert_eq!(state, ExecutionState::Idle);
2548
2549        let json = r#""disconnected""#;
2550        let state: ExecutionState = serde_json::from_str(json).unwrap();
2551        assert_eq!(state, ExecutionState::Other("disconnected".to_string()));
2552        let serialized = serde_json::to_string(&state).unwrap();
2553        assert_eq!(serialized, "\"disconnected\"");
2554    }
2555
2556    #[test]
2557    fn test_iopub_welcome_message() {
2558        // Test creating IoPubWelcome message
2559        let welcome = IoPubWelcome::new("".to_string());
2560        assert_eq!(welcome.subscription, "");
2561
2562        // Test serialization
2563        let welcome_value = serde_json::to_value(&welcome).unwrap();
2564        let expected_value = serde_json::json!({
2565            "subscription": ""
2566        });
2567        assert_eq!(welcome_value, expected_value);
2568
2569        // Test deserialization
2570        let json_str = r#"{"subscription": ""}"#;
2571        let deserialized: IoPubWelcome = serde_json::from_str(json_str).unwrap();
2572        assert_eq!(deserialized.subscription, "");
2573
2574        // Test with non-empty subscription
2575        let welcome_with_topic = IoPubWelcome::new("kernel.output".to_string());
2576        assert_eq!(welcome_with_topic.subscription, "kernel.output");
2577
2578        // Test conversion to JupyterMessage
2579        let message: JupyterMessage = welcome.clone().into();
2580        assert_eq!(message.header.msg_type, "iopub_welcome");
2581        match message.content {
2582            JupyterMessageContent::IoPubWelcome(w) => {
2583                assert_eq!(w.subscription, "");
2584            }
2585            _ => panic!("Expected IoPubWelcome"),
2586        }
2587
2588        // Test from_type_and_content
2589        let content_value = serde_json::json!({"subscription": "test_topic"});
2590        let content =
2591            JupyterMessageContent::from_type_and_content("iopub_welcome", content_value).unwrap();
2592        match content {
2593            JupyterMessageContent::IoPubWelcome(w) => {
2594                assert_eq!(w.subscription, "test_topic");
2595            }
2596            _ => panic!("Expected IoPubWelcome"),
2597        }
2598    }
2599
2600    #[test]
2601    fn test_deserialize_jupyter_message() {
2602        let original_message = JupyterMessage::new(
2603            ExecuteRequest::new("print('Hello world!')".to_string()),
2604            None,
2605        );
2606        let serialized = serde_json::to_string(&original_message).unwrap();
2607        let deserialized: JupyterMessage = serde_json::from_str(&serialized).unwrap();
2608        assert_eq!(original_message.header.msg_id, deserialized.header.msg_id);
2609        assert_eq!(
2610            original_message.header.msg_type,
2611            deserialized.header.msg_type
2612        );
2613        match deserialized.content {
2614            JupyterMessageContent::ExecuteRequest(req) => {
2615                assert_eq!(req.code, "print('Hello world!')");
2616            }
2617            _ => panic!("Expected ExecuteRequest"),
2618        }
2619    }
2620
2621    #[test]
2622    fn test_header_missing_date_defaults_to_epoch() {
2623        // Some kernels (e.g., Almond) don't include the date field
2624        let header_json = json!({
2625            "msg_id": "test-msg-id",
2626            "username": "testuser",
2627            "session": "test-session",
2628            "msg_type": "execute_request",
2629            "version": "5.3"
2630        });
2631
2632        let header: Header = serde_json::from_value(header_json).unwrap();
2633        assert_eq!(header.msg_id, "test-msg-id");
2634        // Date should default to UNIX_EPOCH when missing
2635        assert_eq!(header.date, DateTime::<Utc>::from_timestamp(0, 0).unwrap());
2636    }
2637
2638    #[test]
2639    fn test_header_with_date_parses_correctly() {
2640        let header_json = json!({
2641            "msg_id": "test-msg-id",
2642            "username": "testuser",
2643            "session": "test-session",
2644            "date": "2025-05-14T14:32:23.490Z",
2645            "msg_type": "execute_request",
2646            "version": "5.3"
2647        });
2648
2649        let header: Header = serde_json::from_value(header_json).unwrap();
2650        assert_ne!(header.date, DateTime::<Utc>::from_timestamp(0, 0).unwrap());
2651    }
2652
2653    #[test]
2654    fn test_kernel_info_reply_missing_status_defaults_to_ok() {
2655        // Some kernels don't include the status field in kernel_info_reply
2656        let reply_json = json!({
2657            "protocol_version": "5.3",
2658            "implementation": "test_kernel",
2659            "implementation_version": "1.0.0",
2660            "language_info": {
2661                "name": "python",
2662                "version": "3.9.0"
2663            },
2664            "banner": "Test Kernel",
2665            "help_links": [],
2666            "debugger": false
2667        });
2668
2669        let reply: KernelInfoReply = serde_json::from_value(reply_json).unwrap();
2670        // Status should default to Ok when missing
2671        assert_eq!(reply.status, ReplyStatus::Ok);
2672    }
2673
2674    #[test]
2675    fn test_kernel_info_reply_with_status_parses_correctly() {
2676        let reply_json = json!({
2677            "status": "error",
2678            "protocol_version": "5.3",
2679            "implementation": "test_kernel",
2680            "implementation_version": "1.0.0",
2681            "language_info": {
2682                "name": "python",
2683                "version": "3.9.0"
2684            },
2685            "banner": "Test Kernel",
2686            "help_links": [],
2687            "debugger": false
2688        });
2689
2690        let reply: KernelInfoReply = serde_json::from_value(reply_json).unwrap();
2691        assert_eq!(reply.status, ReplyStatus::Error);
2692    }
2693}