use serde::{Deserialize, Serialize};
#[non_exhaustive]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum MessagePayload {
Text {
content: String,
},
Structured {
data: serde_json::Value,
},
Binary {
mime_type: String,
#[serde(with = "base64_bytes")]
data: Vec<u8>,
},
}
mod base64_bytes {
use serde::{Deserialize, Deserializer, Serializer};
pub fn serialize<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_bytes(bytes)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
where
D: Deserializer<'de>,
{
let bytes: Vec<u8> = Vec::deserialize(deserializer)?;
Ok(bytes)
}
}
impl MessagePayload {
pub fn text(content: impl Into<String>) -> Self {
Self::Text {
content: content.into(),
}
}
pub fn structured(data: serde_json::Value) -> Self {
Self::Structured { data }
}
pub fn binary(mime_type: impl Into<String>, data: Vec<u8>) -> Self {
Self::Binary {
mime_type: mime_type.into(),
data,
}
}
pub fn is_text(&self) -> bool {
matches!(self, Self::Text { .. })
}
pub fn is_binary(&self) -> bool {
matches!(self, Self::Binary { .. })
}
pub fn as_text(&self) -> Option<&str> {
match self {
Self::Text { content } => Some(content),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn text_payload_creation() {
let payload = MessagePayload::text("hello");
assert!(payload.is_text());
assert!(!payload.is_binary());
assert_eq!(payload.as_text(), Some("hello"));
}
#[test]
fn structured_payload_creation() {
let data = serde_json::json!({"key": "value"});
let payload = MessagePayload::structured(data.clone());
assert!(!payload.is_text());
assert!(!payload.is_binary());
assert_eq!(payload.as_text(), None);
}
#[test]
fn binary_payload_creation() {
let payload = MessagePayload::binary("audio/wav", vec![0u8; 16]);
assert!(payload.is_binary());
assert!(!payload.is_text());
assert_eq!(payload.as_text(), None);
}
#[test]
fn text_payload_serde_roundtrip() {
let payload = MessagePayload::text("hello world");
let json = serde_json::to_string(&payload).unwrap();
let restored: MessagePayload = serde_json::from_str(&json).unwrap();
assert_eq!(restored.as_text(), Some("hello world"));
}
#[test]
fn structured_payload_serde_roundtrip() {
let data = serde_json::json!({"key": "value", "count": 42});
let payload = MessagePayload::structured(data.clone());
let json = serde_json::to_string(&payload).unwrap();
let restored: MessagePayload = serde_json::from_str(&json).unwrap();
match restored {
MessagePayload::Structured { data: d } => {
assert_eq!(d["key"], "value");
assert_eq!(d["count"], 42);
}
_ => panic!("expected Structured variant"),
}
}
#[test]
fn binary_payload_serde_roundtrip() {
let original_data = vec![0u8, 1, 2, 3, 255, 128];
let payload = MessagePayload::binary("audio/wav", original_data.clone());
let json = serde_json::to_string(&payload).unwrap();
let restored: MessagePayload = serde_json::from_str(&json).unwrap();
match restored {
MessagePayload::Binary { mime_type, data } => {
assert_eq!(mime_type, "audio/wav");
assert_eq!(data, original_data);
}
_ => panic!("expected Binary variant"),
}
}
#[test]
fn message_payload_is_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<MessagePayload>();
}
}