openlark_webhook/robot/v1/
send.rs1use crate::common::error::{Result, WebhookError};
2use crate::common::validation;
3use crate::models::{FileContent, ImageContent, PostContent, TextContent};
4use serde_json::json;
5
6#[cfg(feature = "signature")]
7use crate::common::signature;
8
9#[cfg(feature = "card")]
10use crate::models::InteractiveContent;
11
12#[derive(Debug, Clone)]
14pub struct SendWebhookMessageRequest {
15 webhook_url: String,
16 msg_type: String,
17 content: serde_json::Value,
18 #[cfg(feature = "signature")]
19 secret: Option<String>,
20}
21
22impl SendWebhookMessageRequest {
23 pub fn new(webhook_url: String) -> Self {
25 Self {
26 webhook_url,
27 msg_type: "text".to_string(),
28 content: json!({}),
29 #[cfg(feature = "signature")]
30 secret: None,
31 }
32 }
33
34 #[cfg(feature = "signature")]
36 pub fn with_secret(mut self, secret: String) -> Self {
37 self.secret = Some(secret);
38 self
39 }
40
41 pub fn text(mut self, text: String) -> Self {
43 self.msg_type = "text".to_string();
44 self.content = serde_json::to_value(TextContent::new(text)).unwrap_or_else(|_| json!({}));
45 self
46 }
47
48 pub fn post(mut self, post: String) -> Self {
50 self.msg_type = "post".to_string();
51 self.content = serde_json::to_value(PostContent::new(post)).unwrap_or_else(|_| json!({}));
52 self
53 }
54
55 pub fn image(mut self, image_key: String) -> Self {
57 self.msg_type = "image".to_string();
58 self.content =
59 serde_json::to_value(ImageContent::new(image_key)).unwrap_or_else(|_| json!({}));
60 self
61 }
62
63 pub fn file(mut self, file_key: String) -> Self {
65 self.msg_type = "file".to_string();
66 self.content =
67 serde_json::to_value(FileContent::new(file_key)).unwrap_or_else(|_| json!({}));
68 self
69 }
70
71 #[cfg(feature = "card")]
75 pub fn card(mut self, card: serde_json::Value) -> Self {
76 self.msg_type = "interactive".to_string();
77 self.content =
78 serde_json::to_value(InteractiveContent::new(card)).unwrap_or_else(|_| json!({}));
79 self
80 }
81
82 pub async fn execute(self) -> Result<SendWebhookMessageResponse> {
84 validation::validate_webhook_url(&self.webhook_url)
85 .map_err(|e| WebhookError::Http(e.to_string()))?;
86
87 let payload = json!(
88 {
89 "msg_type": self.msg_type,
90 "content": self.content,
91 });
92
93 #[cfg(feature = "signature")]
94 let request_builder = {
95 let mut rb = reqwest::Client::new()
96 .post(&self.webhook_url)
97 .json(&payload);
98 if let Some(secret) = &self.secret {
99 let timestamp = signature::current_timestamp();
100 let sign = signature::sign(timestamp, secret);
101 rb = rb
102 .header("X-Lark-Signature", sign)
103 .header("X-Lark-Timestamp", timestamp.to_string());
104 }
105 rb
106 };
107
108 #[cfg(not(feature = "signature"))]
109 let request_builder = reqwest::Client::new()
110 .post(&self.webhook_url)
111 .json(&payload);
112
113 let response = request_builder
114 .send()
115 .await
116 .map_err(|e| WebhookError::Http(e.to_string()))?;
117
118 let status = response.status();
119 if !status.is_success() {
120 return Err(WebhookError::Http(format!("HTTP error: {status}")));
121 }
122
123 let body = response
124 .text()
125 .await
126 .map_err(|e| WebhookError::Http(e.to_string()))?;
127
128 let result: SendWebhookMessageResponse = serde_json::from_str(&body)?;
129 Ok(result)
130 }
131}
132
133#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
135pub struct SendWebhookMessageResponse {
136 pub code: i32,
138 pub msg: String,
140}
141
142#[cfg(test)]
143#[allow(unused_imports)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn test_send_webhook_message_request_text() {
149 let req = SendWebhookMessageRequest::new("https://example.com/webhook".to_string())
150 .text("Hello, World!".to_string());
151
152 assert_eq!(req.msg_type, "text");
153 assert_eq!(req.webhook_url, "https://example.com/webhook");
154 }
155
156 #[test]
157 fn test_send_webhook_message_request_post() {
158 let req = SendWebhookMessageRequest::new("https://example.com/webhook".to_string())
159 .post(r#"{"title":"Test"}"#.to_string());
160
161 assert_eq!(req.msg_type, "post");
162 }
163
164 #[test]
165 fn test_send_webhook_message_request_image() {
166 let req = SendWebhookMessageRequest::new("https://example.com/webhook".to_string())
167 .image("img_abc123".to_string());
168
169 assert_eq!(req.msg_type, "image");
170 }
171
172 #[test]
173 fn test_send_webhook_message_request_file() {
174 let req = SendWebhookMessageRequest::new("https://example.com/webhook".to_string())
175 .file("file_xyz789".to_string());
176
177 assert_eq!(req.msg_type, "file");
178 }
179
180 #[cfg(feature = "card")]
181 #[test]
182 fn test_send_webhook_message_request_card() {
183 let card = serde_json::json!({
184 "type": "template",
185 "data": {
186 "template_id": "test_template"
187 }
188 });
189 let req =
190 SendWebhookMessageRequest::new("https://example.com/webhook".to_string()).card(card);
191
192 assert_eq!(req.msg_type, "interactive");
193 }
194
195 #[test]
196 fn test_send_webhook_message_response_serialization() {
197 let json = r#"{"code":0,"msg":"ok"}"#;
198 let response: SendWebhookMessageResponse =
199 serde_json::from_str(json).expect("JSON 反序列化失败");
200 assert_eq!(response.code, 0);
201 assert_eq!(response.msg, "ok");
202 }
203
204 #[cfg(feature = "signature")]
205 #[test]
206 fn test_send_webhook_message_request_with_secret() {
207 let req = SendWebhookMessageRequest::new("https://example.com/webhook".to_string())
208 .text("Hello".to_string())
209 .with_secret("my-secret".to_string());
210
211 assert!(req.secret.is_some());
212 assert_eq!(req.secret.unwrap(), "my-secret");
213 }
214}