use crate::common::error::{Result, WebhookError};
use crate::common::validation;
use crate::models::{FileContent, ImageContent, PostContent, TextContent};
use serde_json::json;
#[cfg(feature = "signature")]
use crate::common::signature;
#[cfg(feature = "card")]
use crate::models::InteractiveContent;
#[derive(Debug, Clone)]
pub struct SendWebhookMessageRequest {
webhook_url: String,
msg_type: String,
content: serde_json::Value,
#[cfg(feature = "signature")]
secret: Option<String>,
}
impl SendWebhookMessageRequest {
pub fn new(webhook_url: String) -> Self {
Self {
webhook_url,
msg_type: "text".to_string(),
content: json!({}),
#[cfg(feature = "signature")]
secret: None,
}
}
#[cfg(feature = "signature")]
pub fn with_secret(mut self, secret: String) -> Self {
self.secret = Some(secret);
self
}
pub fn text(mut self, text: String) -> Self {
self.msg_type = "text".to_string();
self.content = serde_json::to_value(TextContent::new(text)).unwrap_or_else(|_| json!({}));
self
}
pub fn post(mut self, post: String) -> Self {
self.msg_type = "post".to_string();
self.content = serde_json::to_value(PostContent::new(post)).unwrap_or_else(|_| json!({}));
self
}
pub fn image(mut self, image_key: String) -> Self {
self.msg_type = "image".to_string();
self.content =
serde_json::to_value(ImageContent::new(image_key)).unwrap_or_else(|_| json!({}));
self
}
pub fn file(mut self, file_key: String) -> Self {
self.msg_type = "file".to_string();
self.content =
serde_json::to_value(FileContent::new(file_key)).unwrap_or_else(|_| json!({}));
self
}
#[cfg(feature = "card")]
pub fn card(mut self, card: serde_json::Value) -> Self {
self.msg_type = "interactive".to_string();
self.content =
serde_json::to_value(InteractiveContent::new(card)).unwrap_or_else(|_| json!({}));
self
}
pub async fn execute(self) -> Result<SendWebhookMessageResponse> {
validation::validate_webhook_url(&self.webhook_url)
.map_err(|e| WebhookError::Http(e.to_string()))?;
let payload = json!(
{
"msg_type": self.msg_type,
"content": self.content,
});
#[cfg(feature = "signature")]
let request_builder = {
let mut rb = reqwest::Client::new()
.post(&self.webhook_url)
.json(&payload);
if let Some(secret) = &self.secret {
let timestamp = signature::current_timestamp();
let sign = signature::sign(timestamp, secret);
rb = rb
.header("X-Lark-Signature", sign)
.header("X-Lark-Timestamp", timestamp.to_string());
}
rb
};
#[cfg(not(feature = "signature"))]
let request_builder = reqwest::Client::new()
.post(&self.webhook_url)
.json(&payload);
let response = request_builder
.send()
.await
.map_err(|e| WebhookError::Http(e.to_string()))?;
let status = response.status();
if !status.is_success() {
return Err(WebhookError::Http(format!("HTTP error: {status}")));
}
let body = response
.text()
.await
.map_err(|e| WebhookError::Http(e.to_string()))?;
let result: SendWebhookMessageResponse = serde_json::from_str(&body)?;
Ok(result)
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct SendWebhookMessageResponse {
pub code: i32,
pub msg: String,
}
#[cfg(test)]
#[allow(unused_imports)]
mod tests {
use super::*;
#[test]
fn test_send_webhook_message_request_text() {
let req = SendWebhookMessageRequest::new("https://example.com/webhook".to_string())
.text("Hello, World!".to_string());
assert_eq!(req.msg_type, "text");
assert_eq!(req.webhook_url, "https://example.com/webhook");
}
#[test]
fn test_send_webhook_message_request_post() {
let req = SendWebhookMessageRequest::new("https://example.com/webhook".to_string())
.post(r#"{"title":"Test"}"#.to_string());
assert_eq!(req.msg_type, "post");
}
#[test]
fn test_send_webhook_message_request_image() {
let req = SendWebhookMessageRequest::new("https://example.com/webhook".to_string())
.image("img_abc123".to_string());
assert_eq!(req.msg_type, "image");
}
#[test]
fn test_send_webhook_message_request_file() {
let req = SendWebhookMessageRequest::new("https://example.com/webhook".to_string())
.file("file_xyz789".to_string());
assert_eq!(req.msg_type, "file");
}
#[cfg(feature = "card")]
#[test]
fn test_send_webhook_message_request_card() {
let card = serde_json::json!({
"type": "template",
"data": {
"template_id": "test_template"
}
});
let req =
SendWebhookMessageRequest::new("https://example.com/webhook".to_string()).card(card);
assert_eq!(req.msg_type, "interactive");
}
#[test]
fn test_send_webhook_message_response_serialization() {
let json = r#"{"code":0,"msg":"ok"}"#;
let response: SendWebhookMessageResponse =
serde_json::from_str(json).expect("JSON 反序列化失败");
assert_eq!(response.code, 0);
assert_eq!(response.msg, "ok");
}
#[cfg(feature = "signature")]
#[test]
fn test_send_webhook_message_request_with_secret() {
let req = SendWebhookMessageRequest::new("https://example.com/webhook".to_string())
.text("Hello".to_string())
.with_secret("my-secret".to_string());
assert!(req.secret.is_some());
assert_eq!(req.secret.unwrap(), "my-secret");
}
}