use crate::{time, JupyterError};
pub use crate::{
media::{Media, MediaType},
ExecutionCount,
};
use bytes::Bytes;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::{collections::HashMap, fmt};
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Channel {
Shell,
Control,
Stdin,
IOPub,
Heartbeat,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
struct UnknownJupyterMessage {
pub header: Header,
#[serde(
serialize_with = "serialize_parent_header",
deserialize_with = "deserialize_parent_header"
)]
pub parent_header: Option<Header>,
pub metadata: Value,
pub content: Value,
#[serde(skip_serializing, skip_deserializing)]
pub buffers: Vec<Bytes>,
pub channel: Option<Channel>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Header {
pub msg_id: String,
pub username: String,
pub session: String,
pub date: DateTime<Utc>,
pub msg_type: String,
pub version: String,
}
fn serialize_parent_header<S>(
parent_header: &Option<Header>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match parent_header {
Some(parent_header) => parent_header.serialize(serializer),
None => serde_json::Map::new().serialize(serializer),
}
}
fn deserialize_parent_header<'de, D>(deserializer: D) -> Result<Option<Header>, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::Error;
let value = Value::deserialize(deserializer)?;
if value.is_null() {
Ok(None)
} else if let Some(obj) = value.as_object() {
if obj.is_empty() {
Ok(None)
} else {
serde_json::from_value(Value::Object(obj.clone()))
.map(Some)
.map_err(D::Error::custom)
}
} else {
serde_json::from_value(value)
.map(Some)
.map_err(D::Error::custom)
}
}
#[derive(Serialize, Clone)]
pub struct JupyterMessage {
#[serde(skip_serializing, skip_deserializing)]
pub zmq_identities: Vec<Bytes>,
pub header: Header,
#[serde(
serialize_with = "serialize_parent_header",
deserialize_with = "deserialize_parent_header"
)]
pub parent_header: Option<Header>,
pub metadata: Value,
pub content: JupyterMessageContent,
#[serde(skip_serializing, skip_deserializing)]
pub buffers: Vec<Bytes>,
pub channel: Option<Channel>,
}
impl JupyterMessage {
pub fn new(
content: impl Into<JupyterMessageContent>,
parent: Option<&JupyterMessage>,
) -> JupyterMessage {
let session = match parent {
Some(parent) => parent.header.session.clone(),
None => Uuid::new_v4().to_string(),
};
let content = content.into();
let header = Header {
msg_id: Uuid::new_v4().to_string(),
username: "runtimelib".to_string(),
session,
date: time::utc_now(),
msg_type: content.message_type().to_owned(),
version: "5.3".to_string(),
};
JupyterMessage {
zmq_identities: parent.map_or(Vec::new(), |parent| parent.zmq_identities.clone()),
header,
parent_header: parent.map(|parent| parent.header.clone()),
metadata: json!({}),
content,
buffers: Vec::new(),
channel: None,
}
}
pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
self.metadata = metadata;
self
}
pub fn with_buffers(mut self, buffers: Vec<Bytes>) -> Self {
self.buffers = buffers;
self
}
pub fn with_parent(mut self, parent: &JupyterMessage) -> Self {
self.header.session.clone_from(&parent.header.session);
self.parent_header = Some(parent.header.clone());
self.zmq_identities.clone_from(&parent.zmq_identities);
self
}
pub fn with_zmq_identities(mut self, zmq_identities: Vec<Bytes>) -> Self {
self.zmq_identities = zmq_identities;
self
}
pub fn with_session(mut self, session: &str) -> Self {
self.header.session = session.to_string();
self
}
pub fn message_type(&self) -> &str {
self.content.message_type()
}
pub fn from_value(message: Value) -> Result<JupyterMessage, JupyterError> {
let message = serde_json::from_value::<UnknownJupyterMessage>(message)?;
let content =
JupyterMessageContent::from_type_and_content(&message.header.msg_type, message.content);
let content = match content {
Ok(content) => content,
Err(err) => {
return Err(JupyterError::ParseError {
msg_type: message.header.msg_type,
source: err,
})
}
};
let message = JupyterMessage {
zmq_identities: Vec::new(),
header: message.header,
parent_header: message.parent_header,
metadata: message.metadata,
content,
buffers: message.buffers,
channel: None,
};
Ok(message)
}
}
impl<'de> Deserialize<'de> for JupyterMessage {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let message = UnknownJupyterMessage::deserialize(deserializer)?;
let content =
JupyterMessageContent::from_type_and_content(&message.header.msg_type, message.content)
.map_err(serde::de::Error::custom)?;
Ok(JupyterMessage {
zmq_identities: Vec::new(),
header: message.header,
parent_header: message.parent_header,
metadata: message.metadata,
content,
buffers: message.buffers,
channel: message.channel,
})
}
}
impl fmt::Debug for JupyterMessage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(
f,
"\nHeader: {}",
serde_json::to_string_pretty(&self.header).unwrap()
)?;
writeln!(
f,
"Parent header: {}",
if let Some(parent_header) = self.parent_header.as_ref() {
serde_json::to_string_pretty(parent_header).unwrap()
} else {
serde_json::to_string_pretty(&serde_json::Map::new()).unwrap()
}
)?;
writeln!(
f,
"Metadata: {}",
serde_json::to_string_pretty(&self.metadata).unwrap()
)?;
writeln!(
f,
"Content: {}\n",
serde_json::to_string_pretty(&self.content).unwrap()
)?;
Ok(())
}
}
#[derive(Serialize, Debug, Clone)]
#[serde(untagged)]
pub enum JupyterMessageContent {
ClearOutput(ClearOutput),
CommClose(CommClose),
CommInfoReply(CommInfoReply),
CommInfoRequest(CommInfoRequest),
CommMsg(CommMsg),
CommOpen(CommOpen),
CompleteReply(CompleteReply),
CompleteRequest(CompleteRequest),
DebugReply(DebugReply),
DebugRequest(DebugRequest),
DisplayData(DisplayData),
ErrorOutput(ErrorOutput),
ExecuteInput(ExecuteInput),
ExecuteReply(ExecuteReply),
ExecuteRequest(ExecuteRequest),
ExecuteResult(ExecuteResult),
HistoryReply(HistoryReply),
HistoryRequest(HistoryRequest),
InputReply(InputReply),
InputRequest(InputRequest),
InspectReply(InspectReply),
InspectRequest(InspectRequest),
InterruptReply(InterruptReply),
InterruptRequest(InterruptRequest),
IoPubWelcome(IoPubWelcome),
IsCompleteReply(IsCompleteReply),
IsCompleteRequest(IsCompleteRequest),
KernelInfoReply(Box<KernelInfoReply>),
KernelInfoRequest(KernelInfoRequest),
ShutdownReply(ShutdownReply),
ShutdownRequest(ShutdownRequest),
Status(Status),
StreamContent(StreamContent),
UnknownMessage(UnknownMessage),
UpdateDisplayData(UpdateDisplayData),
}
impl JupyterMessageContent {
pub fn message_type(&self) -> &str {
match self {
JupyterMessageContent::ClearOutput(_) => "clear_output",
JupyterMessageContent::CommClose(_) => "comm_close",
JupyterMessageContent::CommInfoReply(_) => "comm_info_reply",
JupyterMessageContent::CommInfoRequest(_) => "comm_info_request",
JupyterMessageContent::CommMsg(_) => "comm_msg",
JupyterMessageContent::CommOpen(_) => "comm_open",
JupyterMessageContent::CompleteReply(_) => "complete_reply",
JupyterMessageContent::CompleteRequest(_) => "complete_request",
JupyterMessageContent::DebugReply(_) => "debug_reply",
JupyterMessageContent::DebugRequest(_) => "debug_request",
JupyterMessageContent::DisplayData(_) => "display_data",
JupyterMessageContent::ErrorOutput(_) => "error",
JupyterMessageContent::ExecuteInput(_) => "execute_input",
JupyterMessageContent::ExecuteReply(_) => "execute_reply",
JupyterMessageContent::ExecuteRequest(_) => "execute_request",
JupyterMessageContent::ExecuteResult(_) => "execute_result",
JupyterMessageContent::HistoryReply(_) => "history_reply",
JupyterMessageContent::HistoryRequest(_) => "history_request",
JupyterMessageContent::InputReply(_) => "input_reply",
JupyterMessageContent::InputRequest(_) => "input_request",
JupyterMessageContent::InspectReply(_) => "inspect_reply",
JupyterMessageContent::InspectRequest(_) => "inspect_request",
JupyterMessageContent::InterruptReply(_) => "interrupt_reply",
JupyterMessageContent::InterruptRequest(_) => "interrupt_request",
JupyterMessageContent::IoPubWelcome(_) => "iopub_welcome",
JupyterMessageContent::IsCompleteReply(_) => "is_complete_reply",
JupyterMessageContent::IsCompleteRequest(_) => "is_complete_request",
JupyterMessageContent::KernelInfoReply(_) => "kernel_info_reply",
JupyterMessageContent::KernelInfoRequest(_) => "kernel_info_request",
JupyterMessageContent::ShutdownReply(_) => "shutdown_reply",
JupyterMessageContent::ShutdownRequest(_) => "shutdown_request",
JupyterMessageContent::Status(_) => "status",
JupyterMessageContent::StreamContent(_) => "stream",
JupyterMessageContent::UnknownMessage(unk) => unk.msg_type.as_str(),
JupyterMessageContent::UpdateDisplayData(_) => "update_display_data",
}
}
pub fn from_type_and_content(msg_type: &str, content: Value) -> serde_json::Result<Self> {
match msg_type {
"clear_output" => Ok(JupyterMessageContent::ClearOutput(serde_json::from_value(
content,
)?)),
"comm_close" => Ok(JupyterMessageContent::CommClose(serde_json::from_value(
content,
)?)),
"comm_info_reply" => Ok(JupyterMessageContent::CommInfoReply(
serde_json::from_value(content)?,
)),
"comm_info_request" => Ok(JupyterMessageContent::CommInfoRequest(
serde_json::from_value(content)?,
)),
"comm_msg" => Ok(JupyterMessageContent::CommMsg(serde_json::from_value(
content,
)?)),
"comm_open" => Ok(JupyterMessageContent::CommOpen(serde_json::from_value(
content,
)?)),
"complete_reply" => Ok(JupyterMessageContent::CompleteReply(
serde_json::from_value(content)?,
)),
"complete_request" => Ok(JupyterMessageContent::CompleteRequest(
serde_json::from_value(content)?,
)),
"debug_reply" => Ok(JupyterMessageContent::DebugReply(serde_json::from_value(
content,
)?)),
"debug_request" => Ok(JupyterMessageContent::DebugRequest(serde_json::from_value(
content,
)?)),
"display_data" => Ok(JupyterMessageContent::DisplayData(serde_json::from_value(
content,
)?)),
"error" => Ok(JupyterMessageContent::ErrorOutput(serde_json::from_value(
content,
)?)),
"execute_input" => Ok(JupyterMessageContent::ExecuteInput(serde_json::from_value(
content,
)?)),
"execute_reply" => Ok(JupyterMessageContent::ExecuteReply(serde_json::from_value(
content,
)?)),
"execute_request" => Ok(JupyterMessageContent::ExecuteRequest(
serde_json::from_value(content)?,
)),
"execute_result" => Ok(JupyterMessageContent::ExecuteResult(
serde_json::from_value(content)?,
)),
"history_reply" => Ok(JupyterMessageContent::HistoryReply(serde_json::from_value(
content,
)?)),
"history_request" => Ok(JupyterMessageContent::HistoryRequest(
serde_json::from_value(content)?,
)),
"input_reply" => Ok(JupyterMessageContent::InputReply(serde_json::from_value(
content,
)?)),
"input_request" => Ok(JupyterMessageContent::InputRequest(serde_json::from_value(
content,
)?)),
"inspect_reply" => Ok(JupyterMessageContent::InspectReply(serde_json::from_value(
content,
)?)),
"inspect_request" => Ok(JupyterMessageContent::InspectRequest(
serde_json::from_value(content)?,
)),
"interrupt_reply" => Ok(JupyterMessageContent::InterruptReply(
serde_json::from_value(content)?,
)),
"interrupt_request" => Ok(JupyterMessageContent::InterruptRequest(
serde_json::from_value(content)?,
)),
"iopub_welcome" => Ok(JupyterMessageContent::IoPubWelcome(serde_json::from_value(
content,
)?)),
"is_complete_reply" => Ok(JupyterMessageContent::IsCompleteReply(
serde_json::from_value(content)?,
)),
"is_complete_request" => Ok(JupyterMessageContent::IsCompleteRequest(
serde_json::from_value(content)?,
)),
"kernel_info_reply" => Ok(JupyterMessageContent::KernelInfoReply(
serde_json::from_value(content)?,
)),
"kernel_info_request" => Ok(JupyterMessageContent::KernelInfoRequest(
serde_json::from_value(content)?,
)),
"shutdown_reply" => Ok(JupyterMessageContent::ShutdownReply(
serde_json::from_value(content)?,
)),
"shutdown_request" => Ok(JupyterMessageContent::ShutdownRequest(
serde_json::from_value(content)?,
)),
"status" => Ok(JupyterMessageContent::Status(serde_json::from_value(
content,
)?)),
"stream" => Ok(JupyterMessageContent::StreamContent(
serde_json::from_value(content)?,
)),
"update_display_data" => Ok(JupyterMessageContent::UpdateDisplayData(
serde_json::from_value(content)?,
)),
_ => Ok(JupyterMessageContent::UnknownMessage(UnknownMessage {
msg_type: msg_type.to_string(),
content,
})),
}
}
}
macro_rules! impl_message_traits {
($($name:ident),*) => {
$(
impl $name {
#[doc = concat!("Create a new `JupyterMessage`, assigning the parent for a `", stringify!($name), "` message.\n")]
#[doc = concat!("use jupyter_protocol::", stringify!($name), ";\n")]
#[doc = concat!("let child_message = ", stringify!($name), "{\n")]
#[must_use]
pub fn as_child_of(&self, parent: &JupyterMessage) -> JupyterMessage {
JupyterMessage::new(self.clone(), Some(parent))
}
}
impl From<$name> for JupyterMessage {
#[doc(hidden)]
#[doc = concat!("Create a new `JupyterMessage` for a `", stringify!($name), "`.\n\n")]
fn from(content: $name) -> Self {
JupyterMessage::new(content, None)
}
}
impl From<$name> for JupyterMessageContent {
#[doc = concat!("Create a new `JupyterMessageContent` for a `", stringify!($name), "`.\n\n")]
fn from(content: $name) -> Self {
JupyterMessageContent::$name(content)
}
}
)*
};
}
impl From<JupyterMessageContent> for JupyterMessage {
fn from(content: JupyterMessageContent) -> Self {
JupyterMessage::new(content, None)
}
}
impl_message_traits!(
ClearOutput,
CommClose,
CommInfoReply,
CommInfoRequest,
CommMsg,
CommOpen,
CompleteReply,
CompleteRequest,
DebugReply,
DebugRequest,
DisplayData,
ErrorOutput,
ExecuteInput,
ExecuteReply,
ExecuteRequest,
ExecuteResult,
HistoryReply,
InputReply,
InputRequest,
InspectReply,
InspectRequest,
InterruptReply,
InterruptRequest,
IoPubWelcome,
IsCompleteReply,
IsCompleteRequest,
KernelInfoRequest,
ShutdownReply,
ShutdownRequest,
Status,
StreamContent,
UpdateDisplayData,
UnknownMessage
);
impl KernelInfoReply {
pub fn as_child_of(&self, parent: &JupyterMessage) -> JupyterMessage {
JupyterMessage::new(
JupyterMessageContent::KernelInfoReply(Box::new(self.clone())),
Some(parent),
)
}
}
impl From<KernelInfoReply> for JupyterMessage {
fn from(content: KernelInfoReply) -> Self {
JupyterMessage::new(
JupyterMessageContent::KernelInfoReply(Box::new(content)),
None,
)
}
}
impl From<KernelInfoReply> for JupyterMessageContent {
fn from(content: KernelInfoReply) -> Self {
JupyterMessageContent::KernelInfoReply(Box::new(content))
}
}
impl HistoryRequest {
#[must_use]
pub fn as_child_of(&self, parent: &JupyterMessage) -> JupyterMessage {
JupyterMessage::new(self.clone(), Some(parent))
}
}
impl From<HistoryRequest> for JupyterMessage {
#[doc(hidden)]
fn from(content: HistoryRequest) -> Self {
JupyterMessage::new(content, None)
}
}
impl From<HistoryRequest> for JupyterMessageContent {
fn from(content: HistoryRequest) -> Self {
JupyterMessageContent::HistoryRequest(content)
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct UnknownMessage {
#[serde(skip_serializing, skip_deserializing)]
pub msg_type: String,
#[serde(flatten)]
pub content: Value,
}
impl Default for UnknownMessage {
fn default() -> Self {
Self {
msg_type: "unknown".to_string(),
content: Value::Null,
}
}
}
impl UnknownMessage {
pub fn reply(&self, content: serde_json::Value) -> JupyterMessageContent {
JupyterMessageContent::UnknownMessage(UnknownMessage {
msg_type: self.msg_type.replace("_request", "_reply"),
content,
})
}
}
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum ReplyStatus {
#[default]
Ok,
Error,
Aborted,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct ReplyError {
pub ename: String,
pub evalue: String,
pub traceback: Vec<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct ClearOutput {
pub wait: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ExecuteRequest {
pub code: String,
pub silent: bool,
pub store_history: bool,
#[serde(serialize_with = "serialize_user_expressions")]
pub user_expressions: Option<HashMap<String, String>>,
#[serde(default = "default_allow_stdin")]
pub allow_stdin: bool,
#[serde(default = "default_stop_on_error")]
pub stop_on_error: bool,
}
fn serialize_user_expressions<S>(
user_expressions: &Option<HashMap<String, String>>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match user_expressions {
Some(user_expressions) => user_expressions.serialize(serializer),
None => serde_json::Map::new().serialize(serializer),
}
}
fn default_allow_stdin() -> bool {
false
}
fn default_stop_on_error() -> bool {
true
}
impl ExecuteRequest {
pub fn new(code: String) -> Self {
Self {
code,
..Default::default()
}
}
}
impl Default for ExecuteRequest {
fn default() -> Self {
Self {
code: "".to_string(),
silent: false,
store_history: true,
user_expressions: None,
allow_stdin: false,
stop_on_error: true,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(tag = "status")]
pub enum ExpressionResult {
#[serde(rename = "ok")]
Ok {
data: Media,
#[serde(default)]
metadata: serde_json::Map<String, Value>,
},
#[serde(rename = "error")]
Error {
ename: String,
evalue: String,
traceback: Vec<String>,
},
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ExecuteReply {
#[serde(default)]
pub status: ReplyStatus,
pub execution_count: ExecutionCount,
#[serde(default)]
pub payload: Vec<Payload>,
#[serde(default)]
pub user_expressions: Option<HashMap<String, ExpressionResult>>,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub error: Option<Box<ReplyError>>,
}
impl Default for ExecuteReply {
fn default() -> Self {
Self {
status: ReplyStatus::Ok,
execution_count: ExecutionCount::new(0),
payload: Vec::new(),
user_expressions: None,
error: None,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "snake_case")]
#[serde(tag = "source")]
pub enum Payload {
Page {
data: Media,
start: usize,
},
SetNextInput {
text: String,
replace: bool,
},
EditMagic {
filename: String,
line_number: usize,
},
AskExit {
keepkernel: bool,
},
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
pub struct KernelInfoRequest {}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct KernelInfoReply {
pub status: ReplyStatus,
pub protocol_version: String,
pub implementation: String,
pub implementation_version: String,
pub language_info: LanguageInfo,
pub banner: String,
pub help_links: Vec<HelpLink>,
#[serde(default = "default_debugger")]
pub debugger: bool,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub error: Option<Box<ReplyError>>,
}
fn default_debugger() -> bool {
false
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum CodeMirrorMode {
Simple(String),
CustomMode { name: String, version: usize },
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CodeMirrorModeObject {
pub name: String,
pub version: usize,
}
impl CodeMirrorMode {
pub fn typescript() -> Self {
Self::Simple("typescript".to_string())
}
pub fn python() -> Self {
Self::Simple("python".to_string())
}
pub fn ipython_code_mirror_mode() -> Self {
Self::CustomMode {
name: "ipython".to_string(),
version: 3,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct LanguageInfo {
pub name: String,
pub version: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub mimetype: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub file_extension: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub pygments_lexer: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub codemirror_mode: Option<CodeMirrorMode>,
#[serde(skip_serializing_if = "Option::is_none")]
pub nbconvert_exporter: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct HelpLink {
pub text: String,
pub url: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum Stdio {
#[serde(rename = "stdout")]
Stdout,
#[serde(rename = "stderr")]
Stderr,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct StreamContent {
pub name: Stdio,
pub text: String,
}
impl Default for StreamContent {
fn default() -> Self {
Self {
name: Stdio::Stdout,
text: String::new(),
}
}
}
impl StreamContent {
pub fn stdout(text: &str) -> Self {
Self {
name: Stdio::Stdout,
text: text.to_string(),
}
}
pub fn stderr(text: &str) -> Self {
Self {
name: Stdio::Stderr,
text: text.to_string(),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct Transient {
#[serde(skip_serializing_if = "Option::is_none")]
pub display_id: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct DisplayData {
pub data: Media,
pub metadata: serde_json::Map<String, Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub transient: Option<Transient>,
}
impl DisplayData {
pub fn new(data: Media) -> Self {
Self {
data,
metadata: Default::default(),
transient: Default::default(),
}
}
}
impl From<Vec<MediaType>> for DisplayData {
fn from(content: Vec<MediaType>) -> Self {
Self::new(Media::new(content))
}
}
impl From<MediaType> for DisplayData {
fn from(content: MediaType) -> Self {
Self::new(Media::new(vec![content]))
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct UpdateDisplayData {
pub data: Media,
#[serde(default)]
pub metadata: serde_json::Map<String, Value>,
pub transient: Transient,
}
impl UpdateDisplayData {
pub fn new(data: Media, display_id: &str) -> Self {
Self {
data,
metadata: Default::default(),
transient: Transient {
display_id: Some(display_id.to_string()),
},
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ExecuteInput {
pub code: String,
pub execution_count: ExecutionCount,
}
impl Default for ExecuteInput {
fn default() -> Self {
Self {
code: String::new(),
execution_count: ExecutionCount::new(0),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ExecuteResult {
pub execution_count: ExecutionCount,
pub data: Media,
#[serde(default)]
pub metadata: serde_json::Map<String, Value>,
pub transient: Option<Transient>,
}
impl Default for ExecuteResult {
fn default() -> Self {
Self {
execution_count: ExecutionCount::new(0),
data: Media::default(),
metadata: serde_json::Map::new(),
transient: None,
}
}
}
impl ExecuteResult {
pub fn new(execution_count: ExecutionCount, data: Media) -> Self {
Self {
execution_count,
data,
metadata: Default::default(),
transient: None,
}
}
}
impl From<(ExecutionCount, Vec<MediaType>)> for ExecuteResult {
fn from((execution_count, content): (ExecutionCount, Vec<MediaType>)) -> Self {
Self::new(execution_count, content.into())
}
}
impl From<(ExecutionCount, MediaType)> for ExecuteResult {
fn from((execution_count, content): (ExecutionCount, MediaType)) -> Self {
Self::new(execution_count, content.into())
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct ErrorOutput {
pub ename: String,
pub evalue: String,
pub traceback: Vec<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CommOpen {
pub comm_id: CommId,
pub target_name: String,
pub data: serde_json::Map<String, Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub target_module: Option<String>,
}
impl Default for CommOpen {
fn default() -> Self {
Self {
comm_id: CommId("".to_string()),
target_name: String::new(),
data: serde_json::Map::new(),
target_module: None,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CommMsg {
pub comm_id: CommId,
pub data: serde_json::Map<String, Value>,
}
impl Default for CommMsg {
fn default() -> Self {
Self {
comm_id: CommId("".to_string()),
data: serde_json::Map::new(),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct CommInfoRequest {
pub target_name: Option<String>,
}
#[derive(Eq, Hash, PartialEq, Serialize, Deserialize, Debug, Clone)]
pub struct CommId(pub String);
impl From<CommId> for String {
fn from(comm_id: CommId) -> Self {
comm_id.0
}
}
impl From<String> for CommId {
fn from(comm_id: String) -> Self {
Self(comm_id)
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CommInfo {
pub target_name: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CommInfoReply {
#[serde(default)]
pub status: ReplyStatus,
pub comms: HashMap<CommId, CommInfo>,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub error: Option<Box<ReplyError>>,
}
impl Default for CommInfoReply {
fn default() -> Self {
Self {
status: ReplyStatus::Ok,
comms: HashMap::new(),
error: None,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CommClose {
pub comm_id: CommId,
pub data: serde_json::Map<String, Value>,
}
impl Default for CommClose {
fn default() -> Self {
Self {
comm_id: CommId("".to_string()),
data: serde_json::Map::new(),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct ShutdownRequest {
pub restart: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct InterruptRequest {}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct InterruptReply {
pub status: ReplyStatus,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub error: Option<Box<ReplyError>>,
}
impl Default for InterruptReply {
fn default() -> Self {
Self::new()
}
}
impl InterruptReply {
pub fn new() -> Self {
Self {
status: ReplyStatus::Ok,
error: None,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ShutdownReply {
pub restart: bool,
pub status: ReplyStatus,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub error: Option<Box<ReplyError>>,
}
impl Default for ShutdownReply {
fn default() -> Self {
Self {
restart: false,
status: ReplyStatus::Ok,
error: None,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct InputRequest {
pub prompt: String,
pub password: bool,
}
impl Default for InputRequest {
fn default() -> Self {
Self {
prompt: "> ".to_string(),
password: false,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct InputReply {
pub value: String,
pub status: ReplyStatus,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub error: Option<Box<ReplyError>>,
}
impl Default for InputReply {
fn default() -> Self {
Self {
value: String::new(),
status: ReplyStatus::Ok,
error: None,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct InspectRequest {
pub code: String,
pub cursor_pos: usize,
pub detail_level: Option<usize>,
}
impl Default for InspectRequest {
fn default() -> Self {
Self {
code: String::new(),
cursor_pos: 0,
detail_level: Some(0),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct InspectReply {
pub found: bool,
pub data: Media,
pub metadata: serde_json::Map<String, Value>,
#[serde(default)]
pub status: ReplyStatus,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub error: Option<Box<ReplyError>>,
}
impl Default for InspectReply {
fn default() -> Self {
Self {
found: false,
data: Media::default(),
metadata: serde_json::Map::new(),
status: ReplyStatus::Ok,
error: None,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct CompleteRequest {
pub code: String,
pub cursor_pos: usize,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CompleteReply {
pub matches: Vec<String>,
pub cursor_start: usize,
pub cursor_end: usize,
pub metadata: serde_json::Map<String, Value>,
#[serde(default)]
pub status: ReplyStatus,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub error: Option<Box<ReplyError>>,
}
impl Default for CompleteReply {
fn default() -> Self {
Self {
matches: Vec::new(),
cursor_start: 0,
cursor_end: 0,
metadata: serde_json::Map::new(),
status: ReplyStatus::Ok,
error: None,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct DebugRequest {
#[serde(flatten)]
pub content: Value,
}
impl Default for DebugRequest {
fn default() -> Self {
Self {
content: Value::Null,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct DebugReply {
#[serde(flatten)]
pub content: Value,
}
impl Default for DebugReply {
fn default() -> Self {
Self {
content: Value::Null,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum IsCompleteReplyStatus {
Incomplete,
Complete,
Invalid,
Unknown,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct IsCompleteReply {
pub status: IsCompleteReplyStatus,
pub indent: String,
}
impl Default for IsCompleteReply {
fn default() -> Self {
Self {
status: IsCompleteReplyStatus::Unknown,
indent: String::new(),
}
}
}
impl IsCompleteReply {
pub fn new(status: IsCompleteReplyStatus, indent: String) -> Self {
Self { status, indent }
}
pub fn incomplete(indent: String) -> Self {
Self::new(IsCompleteReplyStatus::Incomplete, indent)
}
pub fn complete() -> Self {
Self::new(IsCompleteReplyStatus::Complete, String::new())
}
pub fn invalid() -> Self {
Self::new(IsCompleteReplyStatus::Invalid, String::new())
}
pub fn unknown() -> Self {
Self::new(IsCompleteReplyStatus::Unknown, String::new())
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(tag = "hist_access_type")]
pub enum HistoryRequest {
#[serde(rename = "range")]
Range {
session: Option<i32>,
start: i32,
stop: i32,
output: bool,
raw: bool,
},
#[serde(rename = "tail")]
Tail { n: i32, output: bool, raw: bool },
#[serde(rename = "search")]
Search {
pattern: String,
unique: bool,
output: bool,
raw: bool,
n: i32,
},
}
impl Default for HistoryRequest {
fn default() -> Self {
Self::Range {
session: None,
start: 0,
stop: 0,
output: false,
raw: false,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum HistoryEntry {
Input(usize, usize, String),
InputOutput(usize, usize, (String, String)),
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct HistoryReply {
pub history: Vec<HistoryEntry>,
pub status: ReplyStatus,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub error: Option<Box<ReplyError>>,
}
impl Default for HistoryReply {
fn default() -> Self {
Self {
history: Vec::new(),
status: ReplyStatus::Ok,
error: None,
}
}
}
impl HistoryReply {
pub fn new(history: Vec<HistoryEntry>) -> Self {
Self {
history,
status: ReplyStatus::Ok,
error: None,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct IsCompleteRequest {
pub code: String,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ExecutionState {
Unknown,
Starting,
Busy,
Idle,
Restarting,
Terminating,
AutoRestarting,
Dead,
Other(String),
}
impl ExecutionState {
pub fn as_str(&self) -> &str {
match self {
ExecutionState::Unknown => "unknown",
ExecutionState::Terminating => "terminating",
ExecutionState::AutoRestarting => "autorestarting",
ExecutionState::Dead => "dead",
ExecutionState::Busy => "busy",
ExecutionState::Idle => "idle",
ExecutionState::Starting => "starting",
ExecutionState::Restarting => "restarting",
ExecutionState::Other(s) => s,
}
}
}
impl serde::Serialize for ExecutionState {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
ExecutionState::Unknown => serializer.serialize_str("unknown"),
ExecutionState::Terminating => serializer.serialize_str("terminating"),
ExecutionState::AutoRestarting => serializer.serialize_str("autorestarting"),
ExecutionState::Dead => serializer.serialize_str("dead"),
ExecutionState::Busy => serializer.serialize_str("busy"),
ExecutionState::Idle => serializer.serialize_str("idle"),
ExecutionState::Starting => serializer.serialize_str("starting"),
ExecutionState::Restarting => serializer.serialize_str("restarting"),
ExecutionState::Other(s) => serializer.serialize_str(s),
}
}
}
impl<'de> serde::Deserialize<'de> for ExecutionState {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct ExecutionStateVisitor;
impl serde::de::Visitor<'_> for ExecutionStateVisitor {
type Value = ExecutionState;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a string representing an execution state")
}
fn visit_str<E>(self, value: &str) -> Result<ExecutionState, E>
where
E: serde::de::Error,
{
match value {
"unknown" => Ok(ExecutionState::Unknown),
"terminating" => Ok(ExecutionState::Terminating),
"autorestarting" => Ok(ExecutionState::AutoRestarting),
"dead" => Ok(ExecutionState::Dead),
"busy" => Ok(ExecutionState::Busy),
"idle" => Ok(ExecutionState::Idle),
"starting" => Ok(ExecutionState::Starting),
"restarting" => Ok(ExecutionState::Restarting),
other => Ok(ExecutionState::Other(other.to_string())),
}
}
}
deserializer.deserialize_str(ExecutionStateVisitor)
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Status {
pub execution_state: ExecutionState,
}
impl Default for Status {
fn default() -> Self {
Self {
execution_state: ExecutionState::Idle,
}
}
}
impl Status {
pub fn busy() -> Self {
Self {
execution_state: ExecutionState::Busy,
}
}
pub fn idle() -> Self {
Self {
execution_state: ExecutionState::Idle,
}
}
pub fn starting() -> Self {
Self {
execution_state: ExecutionState::Starting,
}
}
pub fn restarting() -> Self {
Self {
execution_state: ExecutionState::Restarting,
}
}
pub fn other(state: impl Into<String>) -> Self {
Self {
execution_state: ExecutionState::Other(state.into()),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct IoPubWelcome {
pub subscription: String,
}
impl IoPubWelcome {
pub fn new(subscription: String) -> Self {
Self { subscription }
}
}
#[cfg(test)]
mod test {
use serde_json::json;
use super::*;
#[test]
fn test_execute_request_serialize() {
let request = ExecuteRequest {
code: "print('Hello, World!')".to_string(),
silent: false,
store_history: true,
user_expressions: Some(HashMap::new()),
allow_stdin: false,
stop_on_error: true,
};
let request_value = serde_json::to_value(request).unwrap();
let expected_request_value = serde_json::json!({
"code": "print('Hello, World!')",
"silent": false,
"store_history": true,
"user_expressions": {},
"allow_stdin": false,
"stop_on_error": true
});
assert_eq!(request_value, expected_request_value);
}
#[test]
fn test_execute_request_user_expressions_serializes_to_empty_dict() {
let request = ExecuteRequest {
code: "print('Hello, World!')".to_string(),
silent: false,
store_history: true,
user_expressions: None,
allow_stdin: false,
stop_on_error: true,
};
let request_value = serde_json::to_value(request).unwrap();
let expected_request_value = serde_json::json!({
"code": "print('Hello, World!')",
"silent": false,
"store_history": true,
"user_expressions": {},
"allow_stdin": false,
"stop_on_error": true
});
assert_eq!(request_value, expected_request_value);
}
#[test]
fn test_into_various() {
let kernel_info_request = KernelInfoRequest {};
let content: JupyterMessageContent = kernel_info_request.clone().into();
let message: JupyterMessage = content.into();
assert!(message.parent_header.is_none());
match message.content {
JupyterMessageContent::KernelInfoRequest(req) => {
assert_eq!(req, kernel_info_request);
}
_ => panic!("Expected KernelInfoRequest"),
}
let kernel_info_request = KernelInfoRequest {};
let message: JupyterMessage = kernel_info_request.clone().into();
assert!(message.parent_header.is_none());
match message.content {
JupyterMessageContent::KernelInfoRequest(req) => {
assert_eq!(req, kernel_info_request);
}
_ => panic!("Expected KernelInfoRequest"),
}
}
#[test]
fn test_default() {
let msg: JupyterMessage = ExecuteRequest {
code: "import this".to_string(),
..Default::default()
}
.into();
assert_eq!(msg.header.msg_type, "execute_request");
assert_eq!(msg.header.msg_id.len(), 36);
match msg.content {
JupyterMessageContent::ExecuteRequest(req) => {
assert_eq!(req.code, "import this");
assert!(!req.silent);
assert!(req.store_history);
assert_eq!(req.user_expressions, None);
assert!(!req.allow_stdin);
assert!(req.stop_on_error);
}
_ => panic!("Expected ExecuteRequest"),
}
}
#[test]
fn test_deserialize_payload() {
let raw_execute_reply_content = r#"
{
"status": "ok",
"execution_count": 1,
"payload": [{
"source": "page",
"data": {
"text/html": "<h1>Hello</h1>",
"text/plain": "Hello"
},
"start": 0
}],
"user_expressions": {}
}
"#;
let execute_reply: ExecuteReply = serde_json::from_str(raw_execute_reply_content).unwrap();
assert_eq!(execute_reply.status, ReplyStatus::Ok);
assert_eq!(execute_reply.execution_count, ExecutionCount::new(1));
let payload = execute_reply.payload.clone();
assert_eq!(payload.len(), 1);
let payload = payload.first().unwrap();
let media = match payload {
Payload::Page { data, .. } => data,
_ => panic!("Expected Page payload type"),
};
let media = serde_json::to_value(media).unwrap();
let expected_media = serde_json::json!({
"text/html": "<h1>Hello</h1>",
"text/plain": "Hello"
});
assert_eq!(media, expected_media);
}
#[test]
pub fn test_display_data_various_data() {
let display_data = DisplayData {
data: serde_json::from_value(json!({
"text/plain": "Hello, World!",
"text/html": "<h1>Hello, World!</h1>",
"application/json": {
"hello": "world",
"foo": "bar",
"ok": [1, 2, 3],
}
}))
.unwrap(),
..Default::default()
};
let display_data_value = serde_json::to_value(display_data).unwrap();
let expected_display_data_value = serde_json::json!({
"data": {
"text/plain": "Hello, World!",
"text/html": "<h1>Hello, World!</h1>",
"application/json": {
"hello": "world",
"foo": "bar",
"ok": [1, 2, 3]
}
},
"metadata": {}
});
assert_eq!(display_data_value, expected_display_data_value);
}
use std::mem::size_of;
macro_rules! size_of_variant {
($variant:ty) => {
let size = size_of::<$variant>();
println!("The size of {} is: {} bytes", stringify!($variant), size);
assert!(size <= 96);
};
}
#[test]
fn test_enum_variant_sizes() {
size_of_variant!(ClearOutput);
size_of_variant!(CommClose);
size_of_variant!(CommInfoReply);
size_of_variant!(CommInfoRequest);
size_of_variant!(CommMsg);
size_of_variant!(CommOpen);
size_of_variant!(CompleteReply);
size_of_variant!(CompleteRequest);
size_of_variant!(DebugReply);
size_of_variant!(DebugRequest);
size_of_variant!(DisplayData);
size_of_variant!(ErrorOutput);
size_of_variant!(ExecuteInput);
size_of_variant!(ExecuteReply);
size_of_variant!(ExecuteRequest);
size_of_variant!(ExecuteResult);
size_of_variant!(HistoryReply);
size_of_variant!(HistoryRequest);
size_of_variant!(InputReply);
size_of_variant!(InputRequest);
size_of_variant!(InspectReply);
size_of_variant!(InspectRequest);
size_of_variant!(InterruptReply);
size_of_variant!(InterruptRequest);
size_of_variant!(IsCompleteReply);
size_of_variant!(IsCompleteRequest);
size_of_variant!(Box<KernelInfoReply>);
size_of_variant!(KernelInfoRequest);
size_of_variant!(ShutdownReply);
size_of_variant!(ShutdownRequest);
size_of_variant!(Status);
size_of_variant!(StreamContent);
size_of_variant!(UnknownMessage);
size_of_variant!(UpdateDisplayData);
}
#[test]
fn test_jupyter_message_content_enum_size() {
let size = size_of::<JupyterMessageContent>();
println!("The size of JupyterMessageContent is: {}", size);
assert!(size > 0);
assert!(size <= 104);
}
#[test]
fn test_jupyter_message_parent_header_serializes_to_empty_dict() {
let request = ExecuteRequest {
code: "1 + 1".to_string(),
..Default::default()
};
let message = JupyterMessage::from(request);
let serialized_message = serde_json::to_value(message).unwrap();
let parent_header = serialized_message.get("parent_header").unwrap();
assert!(parent_header.is_object());
assert!(parent_header.as_object().unwrap().is_empty());
}
#[test]
fn test_user_expressions_serialization() {
let request = ExecuteRequest {
code: "pass".to_string(),
silent: false,
store_history: true,
user_expressions: Some(HashMap::from([(
String::from("expression"),
String::from("42 + 7"),
)])),
allow_stdin: false,
stop_on_error: true,
};
let request_value = serde_json::to_value(request.clone()).unwrap();
let expected_request_value = serde_json::json!({
"code": "pass",
"silent": false,
"store_history": true,
"user_expressions": {"expression": "42 + 7"},
"allow_stdin": false,
"stop_on_error": true
});
assert_eq!(request_value, expected_request_value);
let deserialized_request: ExecuteRequest = serde_json::from_value(request_value).unwrap();
assert_eq!(
deserialized_request.user_expressions,
request.user_expressions
);
}
#[test]
fn test_execute_reply_user_expressions_deserialization() {
let reply_json = serde_json::json!({
"status": "ok",
"execution_count": 1,
"payload": [],
"user_expressions": {
"answer": {
"status": "ok",
"data": {"text/plain": "55"},
"metadata": {}
},
"bad_expr": {
"status": "error",
"ename": "NameError",
"evalue": "name 'undefined_var' is not defined",
"traceback": ["Traceback ...", "NameError: name 'undefined_var' is not defined"]
}
}
});
let reply: ExecuteReply = serde_json::from_value(reply_json).unwrap();
assert_eq!(reply.execution_count.value(), 1);
let user_exprs = reply.user_expressions.unwrap();
assert_eq!(user_exprs.len(), 2);
match &user_exprs["answer"] {
ExpressionResult::Ok { data, metadata: _ } => {
let plain = data.content.iter().find_map(|m| match m {
MediaType::Plain(text) => Some(text.as_str()),
_ => None,
});
assert_eq!(plain, Some("55"));
}
other => panic!("Expected Ok variant, got {:?}", other),
}
match &user_exprs["bad_expr"] {
ExpressionResult::Error {
ename,
evalue,
traceback,
} => {
assert_eq!(ename, "NameError");
assert_eq!(evalue, "name 'undefined_var' is not defined");
assert_eq!(traceback.len(), 2);
}
other => panic!("Expected Error variant, got {:?}", other),
}
}
#[test]
fn test_execute_reply_without_user_expressions() {
let reply_json = serde_json::json!({
"status": "ok",
"execution_count": 1,
"payload": []
});
let reply: ExecuteReply = serde_json::from_value(reply_json).unwrap();
assert!(reply.user_expressions.is_none());
}
#[test]
fn test_jupyter_message_parent_header_deserialize() {
let msg = r#"
{
"buffers": [],
"channel": "shell",
"content": {},
"header": {
"date": "2025-05-14T14:32:23.490Z",
"msg_id": "44bd6b44-78a1-4892-87df-c0861a005d56",
"msg_type": "kernel_info_request",
"session": "b75bddaa-6d69-4340-ba13-81516192370e",
"username": "",
"version": "5.2"
},
"metadata": {},
"parent_header": {
"date": "2025-05-14T14:32:23.490Z",
"msg_id": "2aaf8916-6b83-4f5a-80dd-633e94f5d8e1",
"msg_type": "kernel_info_request",
"session": "e2a3165d-76a8-4fef-850f-712102589660",
"username": "",
"version": "5.2"
}
}
"#;
let message: JupyterMessage = serde_json::from_str(msg).unwrap();
assert!(message.parent_header.is_some());
assert_eq!(
message.parent_header.as_ref().unwrap().msg_type,
"kernel_info_request"
);
assert_eq!(
message.parent_header.as_ref().unwrap().msg_id,
"2aaf8916-6b83-4f5a-80dd-633e94f5d8e1"
);
assert_eq!(
message.header.msg_id,
"44bd6b44-78a1-4892-87df-c0861a005d56"
);
}
#[test]
fn test_jupyter_message_empty_parent_header_deserialize() {
let msg = r#"
{
"buffers": [],
"channel": "shell",
"content": {},
"header": {
"date": "2025-05-14T14:32:23.490Z",
"msg_id": "44bd6b44-78a1-4892-87df-c0861a005d56",
"msg_type": "kernel_info_request",
"session": "b75bddaa-6d69-4340-ba13-81516192370e",
"username": "",
"version": "5.2"
},
"metadata": {},
"parent_header": {}
}
"#;
let message: JupyterMessage = serde_json::from_str(msg).unwrap();
assert!(message.parent_header.is_none());
assert_eq!(message.header.msg_type, "kernel_info_request");
assert_eq!(
message.header.msg_id,
"44bd6b44-78a1-4892-87df-c0861a005d56"
);
}
#[test]
fn test_execution_state_other_serde() {
let json = r#""busy""#;
let state: ExecutionState = serde_json::from_str(json).unwrap();
assert_eq!(state, ExecutionState::Busy);
let serialized = serde_json::to_string(&state).unwrap();
assert_eq!(serialized, "\"busy\"");
let state = ExecutionState::Idle;
let serialized = serde_json::to_string(&state).unwrap();
assert_eq!(serialized, "\"idle\"");
let state: ExecutionState = serde_json::from_str(&serialized).unwrap();
assert_eq!(state, ExecutionState::Idle);
let json = r#""disconnected""#;
let state: ExecutionState = serde_json::from_str(json).unwrap();
assert_eq!(state, ExecutionState::Other("disconnected".to_string()));
let serialized = serde_json::to_string(&state).unwrap();
assert_eq!(serialized, "\"disconnected\"");
}
#[test]
fn test_iopub_welcome_message() {
let welcome = IoPubWelcome::new("".to_string());
assert_eq!(welcome.subscription, "");
let welcome_value = serde_json::to_value(&welcome).unwrap();
let expected_value = serde_json::json!({
"subscription": ""
});
assert_eq!(welcome_value, expected_value);
let json_str = r#"{"subscription": ""}"#;
let deserialized: IoPubWelcome = serde_json::from_str(json_str).unwrap();
assert_eq!(deserialized.subscription, "");
let welcome_with_topic = IoPubWelcome::new("kernel.output".to_string());
assert_eq!(welcome_with_topic.subscription, "kernel.output");
let message: JupyterMessage = welcome.clone().into();
assert_eq!(message.header.msg_type, "iopub_welcome");
match message.content {
JupyterMessageContent::IoPubWelcome(w) => {
assert_eq!(w.subscription, "");
}
_ => panic!("Expected IoPubWelcome"),
}
let content_value = serde_json::json!({"subscription": "test_topic"});
let content =
JupyterMessageContent::from_type_and_content("iopub_welcome", content_value).unwrap();
match content {
JupyterMessageContent::IoPubWelcome(w) => {
assert_eq!(w.subscription, "test_topic");
}
_ => panic!("Expected IoPubWelcome"),
}
}
#[test]
fn test_deserialize_jupyter_message() {
let original_message = JupyterMessage::new(
ExecuteRequest::new("print('Hello world!')".to_string()),
None,
);
let serialized = serde_json::to_string(&original_message).unwrap();
let deserialized: JupyterMessage = serde_json::from_str(&serialized).unwrap();
assert_eq!(original_message.header.msg_id, deserialized.header.msg_id);
assert_eq!(
original_message.header.msg_type,
deserialized.header.msg_type
);
match deserialized.content {
JupyterMessageContent::ExecuteRequest(req) => {
assert_eq!(req.code, "print('Hello world!')");
}
_ => panic!("Expected ExecuteRequest"),
}
}
}