dingtalk_stream/messages/
frames.rs1use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6#[derive(Debug, Clone, Default, Serialize, Deserialize)]
8pub struct Headers {
9 #[serde(rename = "appId", skip_serializing_if = "Option::is_none")]
11 pub app_id: Option<String>,
12 #[serde(rename = "connectionId", skip_serializing_if = "Option::is_none")]
14 pub connection_id: Option<String>,
15 #[serde(rename = "contentType", skip_serializing_if = "Option::is_none")]
17 pub content_type: Option<String>,
18 #[serde(rename = "messageId", skip_serializing_if = "Option::is_none")]
20 pub message_id: Option<String>,
21 #[serde(rename = "time", skip_serializing_if = "Option::is_none")]
23 pub time: Option<String>,
24 #[serde(rename = "topic", skip_serializing_if = "Option::is_none")]
26 pub topic: Option<String>,
27 #[serde(rename = "eventBornTime", skip_serializing_if = "Option::is_none")]
30 pub event_born_time: Option<i64>,
31 #[serde(rename = "eventCorpId", skip_serializing_if = "Option::is_none")]
33 pub event_corp_id: Option<String>,
34 #[serde(rename = "eventId", skip_serializing_if = "Option::is_none")]
36 pub event_id: Option<String>,
37 #[serde(rename = "eventType", skip_serializing_if = "Option::is_none")]
39 pub event_type: Option<String>,
40 #[serde(rename = "eventUnifiedAppId", skip_serializing_if = "Option::is_none")]
42 pub event_unified_app_id: Option<String>,
43 #[serde(flatten)]
45 pub extensions: HashMap<String, serde_json::Value>,
46}
47
48impl Headers {
49 pub const CONTENT_TYPE_APPLICATION_JSON: &'static str = "application/json";
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
55#[serde(tag = "type")]
56pub enum StreamMessage {
57 #[serde(rename = "EVENT")]
59 Event(MessageBody),
60 #[serde(rename = "CALLBACK")]
62 Callback(MessageBody),
63 #[serde(rename = "SYSTEM")]
65 System(MessageBody),
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct MessageBody {
71 #[serde(rename = "specVersion", default)]
73 pub spec_version: String,
74 pub headers: Headers,
76 #[serde(default)]
78 pub data: String,
79 #[serde(flatten)]
81 pub extensions: HashMap<String, serde_json::Value>,
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct AckMessage {
87 pub code: u16,
89 pub headers: Headers,
91 #[serde(default)]
93 pub message: String,
94 #[serde(default)]
96 pub data: String,
97}
98
99impl AckMessage {
100 pub const STATUS_OK: u16 = 200;
102 pub const STATUS_BAD_REQUEST: u16 = 400;
104 pub const STATUS_NOT_IMPLEMENT: u16 = 404;
106 pub const STATUS_SYSTEM_EXCEPTION: u16 = 500;
108
109 pub fn ok(message_id: Option<String>, data: serde_json::Value) -> Self {
111 Self {
112 code: Self::STATUS_OK,
113 headers: Headers {
114 message_id,
115 content_type: Some(Headers::CONTENT_TYPE_APPLICATION_JSON.to_owned()),
116 ..Default::default()
117 },
118 message: "OK".to_owned(),
119 data: serde_json::to_string(&data).unwrap_or_default(),
120 }
121 }
122
123 pub fn not_implemented(message_id: Option<String>) -> Self {
125 Self {
126 code: Self::STATUS_NOT_IMPLEMENT,
127 headers: Headers {
128 message_id,
129 content_type: Some(Headers::CONTENT_TYPE_APPLICATION_JSON.to_owned()),
130 ..Default::default()
131 },
132 message: "not implement".to_owned(),
133 data: String::new(),
134 }
135 }
136}
137
138pub struct SystemMessage;
140
141impl SystemMessage {
142 pub const TOPIC_DISCONNECT: &'static str = "disconnect";
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 #[test]
151 fn test_stream_message_deserialize_event() {
152 let json = r#"{
153 "specVersion": "1.0",
154 "type": "EVENT",
155 "headers": {
156 "appId": "test_app",
157 "messageId": "msg_001",
158 "topic": "/v1.0/im/bot/messages/get",
159 "eventId": "evt_001",
160 "eventType": "chat_update_title",
161 "eventBornTime": 1690106592000,
162 "eventCorpId": "corp_001"
163 },
164 "data": "{\"key\":\"value\"}"
165 }"#;
166 let msg: StreamMessage = serde_json::from_str(json).unwrap();
167 match &msg {
168 StreamMessage::Event(body) => {
169 assert_eq!(body.spec_version, "1.0");
170 assert_eq!(body.headers.app_id.as_deref(), Some("test_app"));
171 assert_eq!(body.headers.event_id.as_deref(), Some("evt_001"));
172 assert_eq!(
173 body.headers.event_type.as_deref(),
174 Some("chat_update_title")
175 );
176 assert_eq!(body.headers.event_born_time, Some(1_690_106_592_000));
177 assert_eq!(body.data, r#"{"key":"value"}"#);
178 }
179 _ => panic!("expected Event"),
180 }
181 }
182
183 #[test]
184 fn test_stream_message_deserialize_callback() {
185 let json = r#"{
186 "specVersion": "1.0",
187 "type": "CALLBACK",
188 "headers": {
189 "messageId": "msg_002",
190 "topic": "/v1.0/im/bot/messages/get"
191 },
192 "data": "{\"text\":\"hello\"}"
193 }"#;
194 let msg: StreamMessage = serde_json::from_str(json).unwrap();
195 assert!(matches!(msg, StreamMessage::Callback(_)));
196 }
197
198 #[test]
199 fn test_stream_message_deserialize_system() {
200 let json = r#"{
201 "specVersion": "1.0",
202 "type": "SYSTEM",
203 "headers": {
204 "topic": "disconnect"
205 },
206 "data": ""
207 }"#;
208 let msg: StreamMessage = serde_json::from_str(json).unwrap();
209 match &msg {
210 StreamMessage::System(body) => {
211 assert_eq!(body.headers.topic.as_deref(), Some("disconnect"));
212 }
213 _ => panic!("expected System"),
214 }
215 }
216
217 #[test]
218 fn test_ack_message_serialize() {
219 let ack = AckMessage::ok(
220 Some("msg_001".to_owned()),
221 serde_json::json!({"response": "ok"}),
222 );
223 let json = serde_json::to_value(&ack).unwrap();
224 assert_eq!(json["code"], 200);
225 assert_eq!(json["headers"]["messageId"], "msg_001");
226 assert_eq!(json["headers"]["contentType"], "application/json");
227 assert_eq!(json["message"], "OK");
228 }
229
230 #[test]
231 fn test_ack_not_implemented() {
232 let ack = AckMessage::not_implemented(Some("msg_002".to_owned()));
233 assert_eq!(ack.code, AckMessage::STATUS_NOT_IMPLEMENT);
234 assert_eq!(ack.message, "not implement");
235 }
236
237 #[test]
238 fn test_headers_extensions() {
239 let json = r#"{
240 "appId": "test",
241 "customField": "custom_value"
242 }"#;
243 let headers: Headers = serde_json::from_str(json).unwrap();
244 assert_eq!(headers.app_id.as_deref(), Some("test"));
245 assert_eq!(
246 headers.extensions.get("customField"),
247 Some(&serde_json::Value::String("custom_value".to_owned()))
248 );
249 }
250}