openlark_webhook/robot/v1/
client.rs1use crate::common::error::{Result, WebhookError};
2use crate::common::validation;
3use crate::robot::v1::send::SendWebhookMessageResponse;
4use serde_json::json;
5
6#[cfg(feature = "signature")]
7use crate::common::signature;
8
9#[derive(Debug, Clone)]
11pub struct WebhookClient {
12 client: reqwest::Client,
13 #[cfg(feature = "signature")]
14 secret: Option<String>,
15}
16
17impl WebhookClient {
18 pub fn new() -> Self {
20 Self {
21 client: reqwest::Client::new(),
22 #[cfg(feature = "signature")]
23 secret: None,
24 }
25 }
26
27 pub fn with_client(client: reqwest::Client) -> Self {
42 Self {
43 client,
44 #[cfg(feature = "signature")]
45 secret: None,
46 }
47 }
48
49 #[cfg(feature = "signature")]
51 pub fn with_secret(mut self, secret: String) -> Self {
52 self.secret = Some(secret);
53 self
54 }
55
56 pub async fn send(
60 &self,
61 webhook_url: &str,
62 payload: serde_json::Value,
63 ) -> Result<SendWebhookMessageResponse> {
64 validation::validate_webhook_url(webhook_url)
65 .map_err(|e| WebhookError::Http(e.to_string()))?;
66
67 #[cfg(feature = "signature")]
68 let request_builder = {
69 let mut rb = self.client.post(webhook_url).json(&payload);
70 if let Some(secret) = &self.secret {
71 let timestamp = signature::current_timestamp();
72 let sign = signature::sign(timestamp, secret);
73 rb = rb
74 .header("X-Lark-Signature", sign)
75 .header("X-Lark-Timestamp", timestamp.to_string());
76 }
77 rb
78 };
79
80 #[cfg(not(feature = "signature"))]
81 let request_builder = self.client.post(webhook_url).json(&payload);
82
83 let response = request_builder
84 .send()
85 .await
86 .map_err(|e| WebhookError::Http(e.to_string()))?;
87
88 let status = response.status();
89 if !status.is_success() {
90 return Err(WebhookError::Http(format!("HTTP error: {status}")));
91 }
92
93 let body = response
94 .text()
95 .await
96 .map_err(|e| WebhookError::Http(e.to_string()))?;
97
98 let result: SendWebhookMessageResponse = serde_json::from_str(&body)?;
99 Ok(result)
100 }
101
102 pub async fn send_text(
104 &self,
105 webhook_url: &str,
106 text: String,
107 ) -> Result<SendWebhookMessageResponse> {
108 let payload = json!({
109 "msg_type": "text",
110 "content": {
111 "text": text
112 }
113 });
114 self.send(webhook_url, payload).await
115 }
116
117 pub async fn send_post(
119 &self,
120 webhook_url: &str,
121 post: String,
122 ) -> Result<SendWebhookMessageResponse> {
123 let payload = json!({
124 "msg_type": "post",
125 "content": {
126 "post": post
127 }
128 });
129 self.send(webhook_url, payload).await
130 }
131
132 pub async fn send_image(
134 &self,
135 webhook_url: &str,
136 image_key: String,
137 ) -> Result<SendWebhookMessageResponse> {
138 let payload = json!({
139 "msg_type": "image",
140 "content": {
141 "image_key": image_key
142 }
143 });
144 self.send(webhook_url, payload).await
145 }
146
147 pub async fn send_file(
149 &self,
150 webhook_url: &str,
151 file_key: String,
152 ) -> Result<SendWebhookMessageResponse> {
153 let payload = json!({
154 "msg_type": "file",
155 "content": {
156 "file_key": file_key
157 }
158 });
159 self.send(webhook_url, payload).await
160 }
161
162 #[cfg(feature = "card")]
166 pub async fn send_card(
167 &self,
168 webhook_url: &str,
169 card: serde_json::Value,
170 ) -> Result<SendWebhookMessageResponse> {
171 let payload = json!({
172 "msg_type": "interactive",
173 "content": {
174 "card": card
175 }
176 });
177 self.send(webhook_url, payload).await
178 }
179}
180
181impl Default for WebhookClient {
182 fn default() -> Self {
183 Self::new()
184 }
185}
186
187#[cfg(test)]
188#[allow(unused_imports)]
189mod tests {
190 use super::*;
191
192 #[test]
193 fn test_webhook_client_creation() {
194 let _client = WebhookClient::new();
195 let _default_client = WebhookClient::default();
196 }
197
198 #[tokio::test]
199 async fn test_send_text_message_construction() {
200 let client = WebhookClient::new();
201 let _client_ref = &client;
204 }
205
206 #[cfg(feature = "signature")]
207 #[test]
208 fn test_webhook_client_with_secret() {
209 let client = WebhookClient::new().with_secret("my-secret".to_string());
210 let _ = client;
211 }
212}