jupyter_protocol/
messaging.rs

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