use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Headers {
#[serde(rename = "appId", skip_serializing_if = "Option::is_none")]
pub app_id: Option<String>,
#[serde(rename = "connectionId", skip_serializing_if = "Option::is_none")]
pub connection_id: Option<String>,
#[serde(rename = "contentType", skip_serializing_if = "Option::is_none")]
pub content_type: Option<String>,
#[serde(rename = "messageId", skip_serializing_if = "Option::is_none")]
pub message_id: Option<String>,
#[serde(rename = "time", skip_serializing_if = "Option::is_none")]
pub time: Option<String>,
#[serde(rename = "topic", skip_serializing_if = "Option::is_none")]
pub topic: Option<String>,
#[serde(rename = "eventBornTime", skip_serializing_if = "Option::is_none")]
pub event_born_time: Option<i64>,
#[serde(rename = "eventCorpId", skip_serializing_if = "Option::is_none")]
pub event_corp_id: Option<String>,
#[serde(rename = "eventId", skip_serializing_if = "Option::is_none")]
pub event_id: Option<String>,
#[serde(rename = "eventType", skip_serializing_if = "Option::is_none")]
pub event_type: Option<String>,
#[serde(rename = "eventUnifiedAppId", skip_serializing_if = "Option::is_none")]
pub event_unified_app_id: Option<String>,
#[serde(flatten)]
pub extensions: HashMap<String, serde_json::Value>,
}
impl Headers {
pub const CONTENT_TYPE_APPLICATION_JSON: &'static str = "application/json";
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum StreamMessage {
#[serde(rename = "EVENT")]
Event(MessageBody),
#[serde(rename = "CALLBACK")]
Callback(MessageBody),
#[serde(rename = "SYSTEM")]
System(MessageBody),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MessageBody {
#[serde(rename = "specVersion", default)]
pub spec_version: String,
pub headers: Headers,
#[serde(default)]
pub data: String,
#[serde(flatten)]
pub extensions: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AckMessage {
pub code: u16,
pub headers: Headers,
#[serde(default)]
pub message: String,
#[serde(default)]
pub data: String,
}
impl AckMessage {
pub const STATUS_OK: u16 = 200;
pub const STATUS_BAD_REQUEST: u16 = 400;
pub const STATUS_NOT_IMPLEMENT: u16 = 404;
pub const STATUS_SYSTEM_EXCEPTION: u16 = 500;
pub fn ok(message_id: Option<String>, data: serde_json::Value) -> Self {
Self {
code: Self::STATUS_OK,
headers: Headers {
message_id,
content_type: Some(Headers::CONTENT_TYPE_APPLICATION_JSON.to_owned()),
..Default::default()
},
message: "OK".to_owned(),
data: serde_json::to_string(&data).unwrap_or_default(),
}
}
pub fn not_implemented(message_id: Option<String>) -> Self {
Self {
code: Self::STATUS_NOT_IMPLEMENT,
headers: Headers {
message_id,
content_type: Some(Headers::CONTENT_TYPE_APPLICATION_JSON.to_owned()),
..Default::default()
},
message: "not implement".to_owned(),
data: String::new(),
}
}
}
pub struct SystemMessage;
impl SystemMessage {
pub const TOPIC_DISCONNECT: &'static str = "disconnect";
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_stream_message_deserialize_event() {
let json = r#"{
"specVersion": "1.0",
"type": "EVENT",
"headers": {
"appId": "test_app",
"messageId": "msg_001",
"topic": "/v1.0/im/bot/messages/get",
"eventId": "evt_001",
"eventType": "chat_update_title",
"eventBornTime": 1690106592000,
"eventCorpId": "corp_001"
},
"data": "{\"key\":\"value\"}"
}"#;
let msg: StreamMessage = serde_json::from_str(json).unwrap();
match &msg {
StreamMessage::Event(body) => {
assert_eq!(body.spec_version, "1.0");
assert_eq!(body.headers.app_id.as_deref(), Some("test_app"));
assert_eq!(body.headers.event_id.as_deref(), Some("evt_001"));
assert_eq!(
body.headers.event_type.as_deref(),
Some("chat_update_title")
);
assert_eq!(body.headers.event_born_time, Some(1_690_106_592_000));
assert_eq!(body.data, r#"{"key":"value"}"#);
}
_ => panic!("expected Event"),
}
}
#[test]
fn test_stream_message_deserialize_callback() {
let json = r#"{
"specVersion": "1.0",
"type": "CALLBACK",
"headers": {
"messageId": "msg_002",
"topic": "/v1.0/im/bot/messages/get"
},
"data": "{\"text\":\"hello\"}"
}"#;
let msg: StreamMessage = serde_json::from_str(json).unwrap();
assert!(matches!(msg, StreamMessage::Callback(_)));
}
#[test]
fn test_stream_message_deserialize_system() {
let json = r#"{
"specVersion": "1.0",
"type": "SYSTEM",
"headers": {
"topic": "disconnect"
},
"data": ""
}"#;
let msg: StreamMessage = serde_json::from_str(json).unwrap();
match &msg {
StreamMessage::System(body) => {
assert_eq!(body.headers.topic.as_deref(), Some("disconnect"));
}
_ => panic!("expected System"),
}
}
#[test]
fn test_ack_message_serialize() {
let ack = AckMessage::ok(
Some("msg_001".to_owned()),
serde_json::json!({"response": "ok"}),
);
let json = serde_json::to_value(&ack).unwrap();
assert_eq!(json["code"], 200);
assert_eq!(json["headers"]["messageId"], "msg_001");
assert_eq!(json["headers"]["contentType"], "application/json");
assert_eq!(json["message"], "OK");
}
#[test]
fn test_ack_not_implemented() {
let ack = AckMessage::not_implemented(Some("msg_002".to_owned()));
assert_eq!(ack.code, AckMessage::STATUS_NOT_IMPLEMENT);
assert_eq!(ack.message, "not implement");
}
#[test]
fn test_headers_extensions() {
let json = r#"{
"appId": "test",
"customField": "custom_value"
}"#;
let headers: Headers = serde_json::from_str(json).unwrap();
assert_eq!(headers.app_id.as_deref(), Some("test"));
assert_eq!(
headers.extensions.get("customField"),
Some(&serde_json::Value::String("custom_value".to_owned()))
);
}
}