use bytes::Bytes;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u8)]
pub enum MessageType {
Ping = 0,
Pong = 1,
Subscribe = 2,
Unsubscribe = 3,
Publish = 4,
Data = 5,
TileUpdate = 6,
FeatureUpdate = 7,
ChangeStream = 8,
Error = 9,
Ack = 10,
JoinRoom = 11,
LeaveRoom = 12,
Broadcast = 13,
SystemEvent = 14,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Payload {
Empty,
Text(String),
Binary(Vec<u8>),
Json(serde_json::Value),
TileData(TilePayload),
FeatureData(FeaturePayload),
ChangeEvent(ChangePayload),
Subscribe(SubscribePayload),
Room(RoomPayload),
Error(ErrorPayload),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Message {
pub id: Uuid,
pub msg_type: MessageType,
pub timestamp: i64,
pub payload: Payload,
pub correlation_id: Option<Uuid>,
}
impl Message {
pub fn new(msg_type: MessageType, payload: Payload) -> Self {
Self {
id: Uuid::new_v4(),
msg_type,
timestamp: chrono::Utc::now().timestamp_millis(),
payload,
correlation_id: None,
}
}
pub fn ping() -> Self {
Self::new(MessageType::Ping, Payload::Empty)
}
pub fn pong() -> Self {
Self::new(MessageType::Pong, Payload::Empty)
}
pub fn subscribe(topic: String, filter: Option<serde_json::Value>) -> Self {
Self::new(
MessageType::Subscribe,
Payload::Subscribe(SubscribePayload { topic, filter }),
)
}
pub fn unsubscribe(topic: String) -> Self {
Self::new(
MessageType::Unsubscribe,
Payload::Subscribe(SubscribePayload {
topic,
filter: None,
}),
)
}
pub fn data(data: Bytes) -> Self {
Self::new(MessageType::Data, Payload::Binary(data.to_vec()))
}
pub fn error(code: u32, message: String) -> Self {
Self::new(
MessageType::Error,
Payload::Error(ErrorPayload { code, message }),
)
}
pub fn join_room(room: String) -> Self {
Self::new(MessageType::JoinRoom, Payload::Room(RoomPayload { room }))
}
pub fn leave_room(room: String) -> Self {
Self::new(MessageType::LeaveRoom, Payload::Room(RoomPayload { room }))
}
pub fn message_type(&self) -> MessageType {
self.msg_type
}
pub fn with_correlation_id(mut self, id: Uuid) -> Self {
self.correlation_id = Some(id);
self
}
pub fn is_response_to(&self, message_id: &Uuid) -> bool {
self.correlation_id.as_ref() == Some(message_id)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TilePayload {
pub z: u8,
pub x: u32,
pub y: u32,
pub data: Vec<u8>,
pub format: String,
pub delta: Option<Vec<u8>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeaturePayload {
pub id: String,
pub layer: String,
pub feature: serde_json::Value,
pub change_type: ChangeType,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ChangeType {
Created,
Updated,
Deleted,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChangePayload {
pub change_id: u64,
pub collection: String,
pub change_type: ChangeType,
pub document_id: String,
pub data: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubscribePayload {
pub topic: String,
pub filter: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RoomPayload {
pub room: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ErrorPayload {
pub code: u32,
pub message: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_message_creation() {
let msg = Message::ping();
assert_eq!(msg.msg_type, MessageType::Ping);
assert!(matches!(msg.payload, Payload::Empty));
}
#[test]
fn test_message_correlation() {
let request = Message::ping();
let response = Message::pong().with_correlation_id(request.id);
assert!(response.is_response_to(&request.id));
}
#[test]
fn test_subscribe_message() {
let msg = Message::subscribe("tiles".to_string(), None);
assert_eq!(msg.msg_type, MessageType::Subscribe);
assert!(
matches!(msg.payload, Payload::Subscribe(_)),
"Expected Subscribe payload"
);
if let Payload::Subscribe(sub) = &msg.payload {
assert_eq!(sub.topic, "tiles");
}
}
#[test]
fn test_tile_payload() {
let payload = TilePayload {
z: 10,
x: 512,
y: 384,
data: vec![1, 2, 3, 4],
format: "png".to_string(),
delta: None,
};
assert_eq!(payload.z, 10);
assert_eq!(payload.x, 512);
assert_eq!(payload.y, 384);
}
#[test]
fn test_feature_payload() {
let feature = serde_json::json!({
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [0.0, 0.0]
},
"properties": {}
});
let payload = FeaturePayload {
id: "feature1".to_string(),
layer: "layer1".to_string(),
feature,
change_type: ChangeType::Created,
};
assert_eq!(payload.id, "feature1");
assert_eq!(payload.change_type, ChangeType::Created);
}
}