Skip to main content

dingtalk_stream/messages/
card_callback.rs

1//! 卡片回调消息,对齐 Python card_callback.py
2
3use serde::{Deserialize, Serialize};
4
5/// 卡片回调路由主题
6pub const CARD_CALLBACK_ROUTER_TOPIC: &str = "/v1.0/card/instances/callback";
7
8/// 卡片回调消息
9#[derive(Debug, Clone, Default, Serialize, Deserialize)]
10pub struct CardCallbackMessage {
11    /// 扩展信息
12    #[serde(default)]
13    pub extension: serde_json::Value,
14    /// 企业 ID
15    #[serde(rename = "corpId", default)]
16    pub corp_id: String,
17    /// 用户 ID
18    #[serde(rename = "userId", default)]
19    pub user_id: String,
20    /// 卡片实例 ID
21    #[serde(rename = "outTrackId", default)]
22    pub card_instance_id: String,
23    /// 回调内容
24    #[serde(default)]
25    pub content: serde_json::Value,
26    /// 回调类型
27    #[serde(rename = "type", default)]
28    pub callback_type: String,
29    /// 空间类型
30    #[serde(rename = "spaceType", default)]
31    pub space_type: String,
32    /// 用户 ID 类型
33    #[serde(rename = "userIdType", default)]
34    pub user_id_type: i32,
35    /// 空间 ID
36    #[serde(rename = "spaceId", default)]
37    pub space_id: String,
38}
39
40impl CardCallbackMessage {
41    /// 从 JSON 字符串解析(处理 extension 和 content 的二次解析)
42    pub fn from_json_str(data: &str) -> crate::Result<Self> {
43        let raw: serde_json::Value = serde_json::from_str(data)?;
44        Self::from_value(&raw)
45    }
46
47    /// 从 JSON Value 解析(处理 extension 和 content 的字符串→对象转换)
48    pub fn from_value(value: &serde_json::Value) -> crate::Result<Self> {
49        let mut msg: Self = serde_json::from_value(value.clone())?;
50
51        // Python 中 extension 和 content 是 json.loads(value) 的结果
52        if let Some(ext_str) = value.get("extension").and_then(|v| v.as_str()) {
53            if let Ok(parsed) = serde_json::from_str(ext_str) {
54                msg.extension = parsed;
55            }
56        }
57        if let Some(content_str) = value.get("content").and_then(|v| v.as_str()) {
58            if let Ok(parsed) = serde_json::from_str(content_str) {
59                msg.content = parsed;
60            }
61        }
62
63        Ok(msg)
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn test_card_callback_message_from_value() {
73        let json = serde_json::json!({
74            "extension": "{\"key\":\"value\"}",
75            "corpId": "corp_001",
76            "userId": "user_001",
77            "outTrackId": "card_001",
78            "content": "{\"action\":\"click\"}",
79            "type": "actionCallback",
80            "spaceType": "IM_GROUP",
81            "userIdType": 1,
82            "spaceId": "space_001"
83        });
84        let msg = CardCallbackMessage::from_value(&json).unwrap();
85        assert_eq!(msg.corp_id, "corp_001");
86        assert_eq!(msg.user_id, "user_001");
87        assert_eq!(msg.card_instance_id, "card_001");
88        assert_eq!(msg.callback_type, "actionCallback");
89        assert_eq!(msg.space_type, "IM_GROUP");
90        assert_eq!(msg.user_id_type, 1);
91        assert_eq!(msg.extension["key"], "value");
92        assert_eq!(msg.content["action"], "click");
93    }
94
95    #[test]
96    fn test_card_callback_router_topic() {
97        assert_eq!(CARD_CALLBACK_ROUTER_TOPIC, "/v1.0/card/instances/callback");
98    }
99}