#![allow(clippy::module_name_repetitions)]
#![allow(clippy::collection_is_never_read)]
use serde::{Deserialize, Serialize};
use time::OffsetDateTime;
#[repr(u8)]
#[derive(
Debug,
Clone,
PartialEq,
Eq,
Hash,
Serialize,
Deserialize,
strum::Display,
strum::AsRefStr,
strum::VariantNames,
)]
#[serde(untagged)]
pub enum Message {
CancelInvocation {
#[serde(rename = "type")]
num: VariantNumber<5>,
#[serde(flatten)]
data: CancelInvocation,
},
Close {
#[serde(rename = "type")]
num: VariantNumber<7>,
},
Completion {
#[serde(rename = "type")]
num: VariantNumber<3>,
#[serde(flatten)]
data: serde_json::Value,
},
Invocation {
#[serde(rename = "type")]
num: VariantNumber<1>,
#[serde(flatten)]
data: Invocation,
},
Ping {
#[serde(rename = "type")]
num: VariantNumber<6>,
},
StreamInvocation {
#[serde(rename = "type")]
num: VariantNumber<4>,
#[serde(flatten)]
data: serde_json::Value,
},
StreamItem {
#[serde(rename = "type")]
num: VariantNumber<2>,
#[serde(flatten)]
data: serde_json::Value,
},
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct VariantNumber<const V: u8>;
impl<const V: u8> Serialize for VariantNumber<V> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_u8(V)
}
}
impl<'de, const V: u8> Deserialize<'de> for VariantNumber<V> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = u8::deserialize(deserializer)?;
if value == V {
Ok(Self)
} else {
Err(serde::de::Error::custom("Parsing variant number failed"))
}
}
}
#[cfg(feature = "borsh")]
impl<const V: u8> borsh::BorshSerialize for VariantNumber<V> {
fn serialize<W: std::io::Write>(
&self, writer: &mut W,
) -> std::io::Result<()> {
let num: u8 = V;
borsh::BorshSerialize::serialize(&num, writer)
}
}
#[cfg(feature = "borsh")]
impl<const V: u8> borsh::BorshDeserialize for VariantNumber<V> {
fn deserialize_reader<R: std::io::Read>(
reader: &mut R,
) -> std::io::Result<Self> {
let num: u8 = borsh::BorshDeserialize::deserialize_reader(reader)?;
if num == V {
Ok(Self)
} else {
Err(borsh::io::Error::other(std::io::Error::other(
"Wrong number: ".to_owned() + &num.to_string(),
)))
}
}
}
#[cfg(test)]
#[test]
fn message_serde() {
let src = "{\"type\":6}";
let ping: Message = serde_json::from_str(src).unwrap();
assert_eq!(ping, Message::Ping { num: VariantNumber });
let str = serde_json::to_string(&ping).unwrap();
assert_eq!(src, str);
}
#[cfg(all(test, feature = "borsh"))]
#[test]
fn message_borsh() {
}
#[cfg_attr(
feature = "borsh",
derive(borsh::BorshSerialize, borsh::BorshDeserialize)
)]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct CancelInvocation {
pub invocation_id: String,
}
#[serde_with::serde_as]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Invocation {
#[serde(flatten)]
pub data: InvocationData,
#[serde_as(deserialize_as = "serde_with::DefaultOnNull")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub invocation_id: Option<String>,
}
#[repr(u8)]
#[derive(
Debug,
Clone,
PartialEq,
Eq,
Hash,
Serialize,
Deserialize,
strum::Display,
strum::AsRefStr,
strum::VariantNames,
)]
#[serde(tag = "target", content = "arguments")]
pub enum InvocationData {
Debug((String,)),
ReceiveSessionUpdate((Box<crate::model::SessionInfo>,)),
RemoveSession((crate::id::Session, OffsetDateTime)),
#[serde(untagged)]
Unknown(serde_json::Value),
}
#[cfg(test)]
#[test]
fn invocation_data_serde() {
let src = "{\"target\":\"Debug\",\"arguments\":[\"Test 123\"]}";
let debug: InvocationData = serde_json::from_str(src).unwrap();
assert_eq!(debug, InvocationData::Debug(("Test 123".to_owned(),)));
let str = serde_json::to_string(&debug).unwrap();
assert_eq!(src, str);
let session_info = crate::model::SessionInfo {
name: String::default(),
description: String::default(),
world: None,
tags: vec![],
id: crate::id::Session::try_from("S-example").unwrap(),
normalized_id: String::default(),
host_id: None,
host_user_session_id: None,
host_machine_id: String::default(),
host_username: String::default(),
compatibility_hash: String::default(),
system_compatibility_hash: String::default(),
data_model_assemblies: vec![],
universe_id: String::default(),
app_version: String::default(),
is_headless_host: Default::default(),
urls: vec![],
users: vec![],
thumbnail_url: None,
joined_users: Default::default(),
active_users: Default::default(),
total_joined_users: Default::default(),
total_active_users: Default::default(),
max_users: Default::default(),
is_mobile_friendly: Default::default(),
session_begin_time: OffsetDateTime::now_utc(),
last_update_time: OffsetDateTime::now_utc(),
access_level: crate::model::SessionAccessLevel::Anyone,
has_ended: Default::default(),
hide_from_listing: Default::default(),
broadcast_key: Default::default(),
is_valid: Default::default(),
parent_session_ids: vec![],
nested_session_ids: vec![],
};
let msg = InvocationData::ReceiveSessionUpdate((Box::new(session_info),));
let as_json = serde_json::to_string(&msg).unwrap();
let parsed = serde_json::from_str(&as_json).unwrap();
assert_eq!(msg, parsed);
let msg = Message::Invocation {
num: VariantNumber,
data: Invocation { invocation_id: None, data: msg },
};
let as_json = dbg!(serde_json::to_string(&msg).unwrap());
let parsed = serde_json::from_str(&as_json).unwrap();
assert_eq!(msg, parsed);
}