use std::borrow::Cow;
use std::fmt::{Debug, Display};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use crate::client::Id;
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct Message<P> {
pub join_reference: Option<String>,
pub message_reference: Option<String>,
pub topic_name: String,
pub event_name: String,
pub payload: P,
}
impl<P> Message<P> {
pub(crate) fn info(&self) -> String {
format!(
"[{:?}, {:?}, {:?}, {:?}, <payload>]",
self.join_reference, self.message_reference, self.topic_name, self.event_name
)
}
}
impl<'a, P> From<ChannelMsg<'a, P>> for Message<P> {
fn from(value: ChannelMsg<'a, P>) -> Self {
Self {
join_reference: value.join_reference.map(Cow::into),
message_reference: value.message_reference.map(Cow::into),
topic_name: value.topic_name.into(),
event_name: value.event_name.into(),
payload: value.payload,
}
}
}
impl Message<serde_json::Value> {
pub fn deserialize_payload<P>(self) -> Result<Message<P>, serde_json::error::Error>
where
P: DeserializeOwned,
{
let payload = serde_json::from_value(self.payload)?;
Ok(Message {
join_reference: self.join_reference,
message_reference: self.message_reference,
topic_name: self.topic_name,
event_name: self.event_name,
payload,
})
}
}
impl<P> Display for Message<P>
where
P: Serialize + Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "[")?;
ser_or_debug(&self.join_reference, f)?;
write!(f, ", ")?;
ser_or_debug(&self.message_reference, f)?;
write!(f, ", ")?;
ser_or_debug(&self.topic_name, f)?;
write!(f, ", ")?;
ser_or_debug(&self.event_name, f)?;
write!(f, ", ")?;
ser_or_debug(&self.payload, f)?;
write!(f, "]")
}
}
fn ser_or_debug<T>(v: &T, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
where
T: Serialize + Debug,
{
if let Ok(s) = serde_json::to_string(v) {
write!(f, "{s}")
} else {
write!(f, "{v:?}")
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct ChannelMsg<'a, P> {
pub(crate) join_reference: Option<Cow<'a, str>>,
pub(crate) message_reference: Option<Cow<'a, str>>,
pub(crate) topic_name: Cow<'a, str>,
pub(crate) event_name: Cow<'a, str>,
pub(crate) payload: P,
}
impl<'a, P> ChannelMsg<'a, P> {
pub(crate) fn new(
join_reference: Option<Id>,
message_reference: Option<Id>,
topic_name: &'a str,
event_name: &'a str,
payload: P,
) -> Self {
Self {
join_reference: join_reference.map(|id| Cow::Owned(id.to_string())),
message_reference: message_reference.map(|id| Cow::Owned(id.to_string())),
topic_name: Cow::Borrowed(topic_name),
event_name: Cow::Borrowed(event_name),
payload,
}
}
pub(crate) fn into_err(self) -> Message<()> {
Message {
join_reference: self.join_reference.map(Cow::into),
message_reference: self.message_reference.map(Cow::into),
topic_name: self.topic_name.into(),
event_name: self.event_name.into(),
payload: (),
}
}
}
impl<P> Serialize for ChannelMsg<'_, P>
where
P: Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let Self {
join_reference,
message_reference,
topic_name,
event_name,
payload,
} = self;
(
join_reference,
message_reference,
topic_name,
event_name,
payload,
)
.serialize(serializer)
}
}
impl<'de, 'a, P> Deserialize<'de> for ChannelMsg<'a, P>
where
P: Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let (join_reference, message_reference, topic_name, event_name, payload) =
Deserialize::deserialize(deserializer)?;
Ok(Self {
join_reference,
message_reference,
topic_name,
event_name,
payload,
})
}
}
impl<P> Display for ChannelMsg<'_, P>
where
P: Serialize + Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Ok(s) = serde_json::to_string(self) else {
return write!(
f,
"[{:?}, {:?}, {:?}, {:?}, {:?}]",
self.join_reference,
self.message_reference,
self.topic_name,
self.event_name,
self.payload,
);
};
write!(f, "{s}")
}
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use crate::Map;
use super::*;
#[test]
fn deserialize_payload_to_struct() {
let join = Message {
join_reference: Some("0".to_string()),
message_reference: Some("0".to_string()),
topic_name: "phoenix".to_string(),
event_name: "phx_reply".to_string(),
payload: serde_json::Value::Object(serde_json::Map::from_iter([
(
"status".to_string(),
serde_json::Value::String("ok".to_string()),
),
(
"response".to_string(),
serde_json::Value::Object(serde_json::Map::default()),
),
])),
};
#[derive(Debug, Deserialize, PartialEq, Eq)]
struct Payload {
status: String,
response: Map,
}
let exp = Message {
join_reference: join.join_reference.clone(),
message_reference: join.message_reference.clone(),
topic_name: join.topic_name.clone(),
event_name: join.event_name.clone(),
payload: Payload {
status: "ok".to_string(),
response: Map::default(),
},
};
let message: Message<Payload> = join.deserialize_payload().unwrap();
assert_eq!(message, exp);
}
#[test]
fn serialize_deserialize_join() {
let join = r#"["0","0","miami:weather","phx_join",{"some":"param"}]"#;
let message: ChannelMsg<Map> = serde_json::from_str(join).unwrap();
let exp = ChannelMsg::new(
Some(0),
Some(0),
"miami:weather",
"phx_join",
Map::from_iter([("some".to_string(), "param".to_string())]),
);
assert_eq!(message, exp);
let json = serde_json::to_string(&message).unwrap();
assert_eq!(json, join);
}
#[test]
fn serialize_deserialize_leave() {
let join = r#"[null,"1","miami:weather","phx_leave",{}]"#;
let message: ChannelMsg<Map> = serde_json::from_str(join).unwrap();
let exp = ChannelMsg::new(None, Some(1), "miami:weather", "phx_leave", Map::default());
assert_eq!(message, exp);
let json = serde_json::to_string(&message).unwrap();
assert_eq!(json, join);
}
#[test]
fn serialize_deserialize_heartbit() {
let join = r#"[null,"2","phoenix","heartbeat",{}]"#;
let message: ChannelMsg<Map> = serde_json::from_str(join).unwrap();
let exp = ChannelMsg::new(None, Some(2), "phoenix", "heartbeat", Map::default());
assert_eq!(message, exp);
let json = serde_json::to_string(&message).unwrap();
assert_eq!(json, join);
}
#[test]
fn serialize_deserialize_send_example() {
let join = r#"[null,"3","miami:weather","report_emergency",{"category":"sharknado"}]"#;
let message: ChannelMsg<Map> = serde_json::from_str(join).unwrap();
let exp = ChannelMsg::new(
None,
Some(3),
"miami:weather",
"report_emergency",
Map::from_iter([("category".to_string(), "sharknado".to_string())]),
);
assert_eq!(message, exp);
let json = serde_json::to_string(&message).unwrap();
assert_eq!(json, join);
}
#[test]
fn serialize_deserialize_no_message_reference() {
let join =
r#"[null,null,"rooms:test:dashboard_oSgokqqBReiKRg_c1nYqMQ_9899","watch_added",{}]"#;
let message: ChannelMsg<Map> = serde_json::from_str(join).unwrap();
let exp = ChannelMsg::new(
None,
None,
"rooms:test:dashboard_oSgokqqBReiKRg_c1nYqMQ_9899",
"watch_added",
Map::default(),
);
assert_eq!(message, exp);
let json = serde_json::to_string(&message).unwrap();
assert_eq!(json, join);
}
}