open_lark/core/validation/
im.rs

1//! IM(即时消息)验证模块
2//!
3//! 提供消息相关功能的验证服务,包括消息内容、接收者、文件等验证。
4
5use crate::core::validation::{ValidateBuilder, ValidationResult};
6use serde_json::Value;
7
8/// 验证消息内容长度
9///
10/// # 参数
11/// - `content`: 消息内容
12/// - `message_type`: 消息类型(text, post, interactive)
13///
14/// # 返回
15/// - ValidationResult: 验证结果
16///
17/// # 限制
18/// - 文本消息: 最大 150KB (153,600 字符)
19/// - 富文本消息: 最大 30KB (30,720 字符)
20/// - 互动消息: 最大 30KB (30,720 字符)
21pub fn validate_message_content(content: &str, message_type: &str) -> ValidationResult {
22    if content.is_empty() {
23        return ValidationResult::Invalid("Message content cannot be empty".to_string());
24    }
25
26    let max_length = match message_type {
27        "text" => 153_600,                // 150KB
28        "post" | "interactive" => 30_720, // 30KB
29        _ => {
30            return ValidationResult::Invalid(format!("Unsupported message type: {}", message_type))
31        }
32    };
33
34    if content.len() > max_length {
35        return ValidationResult::Invalid(format!(
36            "Message content too long. Maximum {} characters allowed for {} messages, got {}",
37            max_length,
38            message_type,
39            content.len()
40        ));
41    }
42
43    ValidationResult::Valid
44}
45
46/// 验证接收者ID格式
47///
48/// # 参数
49/// - `receiver_id`: 接收者ID
50/// - `id_type`: ID类型(open_id, user_id, union_id, chat_id)
51///
52/// # 返回
53/// - ValidationResult: 验证结果
54pub fn validate_receiver_id(receiver_id: &str, id_type: &str) -> ValidationResult {
55    if receiver_id.is_empty() {
56        return ValidationResult::Invalid("Receiver ID cannot be empty".to_string());
57    }
58
59    // 验证不同类型的ID格式
60    match id_type {
61        "open_id" => {
62            if !receiver_id.starts_with("ou_") {
63                return ValidationResult::Invalid("Open ID must start with 'ou_'".to_string());
64            }
65            if receiver_id.len() != 28 {
66                return ValidationResult::Invalid("Open ID must be 28 characters long".to_string());
67            }
68        }
69        "user_id" => {
70            if !receiver_id.starts_with("u_") {
71                return ValidationResult::Invalid("User ID must start with 'u_'".to_string());
72            }
73        }
74        "union_id" => {
75            if !receiver_id.starts_with("on_") {
76                return ValidationResult::Invalid("Union ID must start with 'on_'".to_string());
77            }
78            if receiver_id.len() != 28 {
79                return ValidationResult::Invalid(
80                    "Union ID must be 28 characters long".to_string(),
81                );
82            }
83        }
84        "chat_id" => {
85            if !receiver_id.starts_with("oc_") {
86                return ValidationResult::Invalid("Chat ID must start with 'oc_'".to_string());
87            }
88            if receiver_id.len() != 28 {
89                return ValidationResult::Invalid("Chat ID must be 28 characters long".to_string());
90            }
91        }
92        _ => return ValidationResult::Invalid(format!("Unsupported ID type: {}", id_type)),
93    }
94
95    // 验证ID字符(只允许字母、数字和下划线)
96    if !receiver_id.chars().all(|c| c.is_alphanumeric() || c == '_') {
97        return ValidationResult::Invalid(
98            "ID contains invalid characters. Only alphanumeric and underscore are allowed"
99                .to_string(),
100        );
101    }
102
103    ValidationResult::Valid
104}
105
106/// 验证消息类型
107///
108/// # 参数
109/// - `message_type`: 消息类型
110///
111/// # 返回
112/// - ValidationResult: 验证结果
113pub fn validate_message_type(message_type: &str) -> ValidationResult {
114    let valid_types = [
115        "text",
116        "post",
117        "image",
118        "file",
119        "audio",
120        "media",
121        "sticker",
122        "interactive",
123        "share_chat",
124    ];
125
126    if !valid_types.contains(&message_type) {
127        return ValidationResult::Invalid(format!(
128            "Invalid message type '{}'. Valid types are: {}",
129            message_type,
130            valid_types.join(", ")
131        ));
132    }
133
134    ValidationResult::Valid
135}
136
137/// 验证UUID格式
138///
139/// # 参数
140/// - `uuid`: UUID字符串
141///
142/// # 返回
143/// - ValidationResult: 验证结果
144pub fn validate_uuid(uuid: &str) -> ValidationResult {
145    if uuid.is_empty() {
146        return ValidationResult::Invalid("UUID cannot be empty".to_string());
147    }
148
149    // 简单的UUID格式验证(实际可能需要更复杂的验证)
150    if uuid.len() != 36 {
151        return ValidationResult::Invalid("UUID must be 36 characters long".to_string());
152    }
153
154    let parts: Vec<&str> = uuid.split('-').collect();
155    if parts.len() != 5 {
156        return ValidationResult::Invalid(
157            "UUID must have 5 parts separated by hyphens".to_string(),
158        );
159    }
160
161    // 验证各部分长度
162    let expected_lengths = [8, 4, 4, 4, 12];
163    for (i, (part, expected_len)) in parts.iter().zip(expected_lengths.iter()).enumerate() {
164        if part.len() != *expected_len {
165            return ValidationResult::Invalid(format!(
166                "UUID part {} must be {} characters long, got {}",
167                i + 1,
168                expected_len,
169                part.len()
170            ));
171        }
172
173        // 验证字符
174        if !part.chars().all(|c| c.is_ascii_hexdigit()) {
175            return ValidationResult::Invalid(format!(
176                "UUID part {} contains invalid characters. Only hexadecimal digits are allowed",
177                i + 1
178            ));
179        }
180    }
181
182    ValidationResult::Valid
183}
184
185/// 验证文件上传信息
186///
187/// # 参数
188/// - `file_name`: 文件名
189/// - `file_size`: 文件大小(字节)
190/// - `file_type`: 文件类型
191///
192/// # 返回
193/// - ValidationResult: 验证结果
194///
195/// # 限制
196/// - 文件名: 最大 255 字符
197/// - 文件大小: 最大 100MB (104,857,600 字节)
198/// - 文件类型: 根据不同消息类型有不同限制
199pub fn validate_file_upload(file_name: &str, file_size: u64, file_type: &str) -> ValidationResult {
200    // 验证文件名
201    if file_name.is_empty() {
202        return ValidationResult::Invalid("File name cannot be empty".to_string());
203    }
204
205    if file_name.len() > 255 {
206        return ValidationResult::Invalid(
207            "File name too long. Maximum 255 characters allowed".to_string(),
208        );
209    }
210
211    // 验证文件名字符(不允许特殊字符)
212    let invalid_chars = ['/', '\\', ':', '*', '?', '"', '<', '>', '|'];
213    if file_name.chars().any(|c| invalid_chars.contains(&c)) {
214        return ValidationResult::Invalid("File name contains invalid characters".to_string());
215    }
216
217    // 验证文件大小
218    if file_size > 100 * 1024 * 1024 {
219        // 100MB
220        return ValidationResult::Invalid("File too large. Maximum 100MB allowed".to_string());
221    }
222
223    // 验证文件类型
224    let allowed_types: &[&str] = match file_type {
225        "image" => &["jpg", "jpeg", "png", "gif", "bmp", "webp"],
226        "audio" => &["mp3", "wav", "amr", "aac", "ogg"],
227        "video" => &["mp4", "mov", "avi", "mkv", "flv"],
228        "file" => &[
229            "pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt", "zip", "rar",
230        ],
231        _ => return ValidationResult::Invalid(format!("Unsupported file type: {}", file_type)),
232    };
233
234    let file_ext = file_name
235        .split('.')
236        .next_back()
237        .unwrap_or("")
238        .to_lowercase();
239    if !allowed_types.contains(&file_ext.as_str()) {
240        return ValidationResult::Invalid(format!(
241            "File type '.{}' is not allowed for {} files",
242            file_ext, file_type
243        ));
244    }
245
246    ValidationResult::Valid
247}
248
249/// 验证消息撤回请求
250///
251/// # 参数
252/// - `message_id`: 消息ID
253/// - `chat_id`: 聊天ID
254///
255/// # 返回
256/// - ValidationResult: 验证结果
257pub fn validate_message_recall(message_id: &str, chat_id: &str) -> ValidationResult {
258    if message_id.is_empty() {
259        return ValidationResult::Invalid("Message ID cannot be empty".to_string());
260    }
261
262    if chat_id.is_empty() {
263        return ValidationResult::Invalid("Chat ID cannot be empty".to_string());
264    }
265
266    // 验证消息ID格式(通常是UUID)
267    if let ValidationResult::Invalid(msg) = validate_uuid(message_id) {
268        return ValidationResult::Invalid(format!("Invalid message ID: {}", msg));
269    }
270
271    // 验证聊天ID
272    if let ValidationResult::Invalid(msg) = validate_receiver_id(chat_id, "chat_id") {
273        return ValidationResult::Invalid(format!("Invalid chat ID: {}", msg));
274    }
275
276    ValidationResult::Valid
277}
278
279/// 验证消息读取状态更新
280///
281/// # 参数
282/// - `message_id`: 消息ID
283/// - `user_id`: 用户ID
284/// - `read_timestamp`: 读取时间戳
285///
286/// # 返回
287/// - ValidationResult: 验证结果
288pub fn validate_message_read_status(
289    message_id: &str,
290    user_id: &str,
291    read_timestamp: i64,
292) -> ValidationResult {
293    if message_id.is_empty() {
294        return ValidationResult::Invalid("Message ID cannot be empty".to_string());
295    }
296
297    if user_id.is_empty() {
298        return ValidationResult::Invalid("User ID cannot be empty".to_string());
299    }
300
301    // 验证时间戳(不能是未来时间)
302    let current_time = chrono::Utc::now().timestamp();
303    if read_timestamp > current_time {
304        return ValidationResult::Invalid("Read timestamp cannot be in the future".to_string());
305    }
306
307    // 验证时间戳不能太早(比如2020年之前)
308    if read_timestamp < 1_577_836_800 {
309        // 2020-01-01
310        return ValidationResult::Invalid("Read timestamp is too early".to_string());
311    }
312
313    ValidationResult::Valid
314}
315
316/// 验证消息转发请求
317///
318/// # 参数
319/// - `source_message_id`: 源消息ID
320/// - `target_chat_id`: 目标聊天ID
321/// - `forward_type`: 转发类型
322///
323/// # 返回
324/// - ValidationResult: 验证结果
325pub fn validate_message_forward(
326    source_message_id: &str,
327    target_chat_id: &str,
328    forward_type: &str,
329) -> ValidationResult {
330    if source_message_id.is_empty() {
331        return ValidationResult::Invalid("Source message ID cannot be empty".to_string());
332    }
333
334    if target_chat_id.is_empty() {
335        return ValidationResult::Invalid("Target chat ID cannot be empty".to_string());
336    }
337
338    // 验证转发类型
339    let valid_forward_types = ["normal", "forward_as_quote"];
340    if !valid_forward_types.contains(&forward_type) {
341        return ValidationResult::Invalid(format!(
342            "Invalid forward type '{}'. Valid types are: {}",
343            forward_type,
344            valid_forward_types.join(", ")
345        ));
346    }
347
348    // 验证ID格式
349    if let ValidationResult::Invalid(msg) = validate_uuid(source_message_id) {
350        return ValidationResult::Invalid(format!("Invalid source message ID: {}", msg));
351    }
352
353    if let ValidationResult::Invalid(msg) = validate_receiver_id(target_chat_id, "chat_id") {
354        return ValidationResult::Invalid(format!("Invalid target chat ID: {}", msg));
355    }
356
357    ValidationResult::Valid
358}
359
360/// 验证消息接收者列表
361///
362/// # 参数
363/// - `receivers`: 接收者列表
364/// - `max_receivers`: 最大接收者数量
365///
366/// # 返回
367/// - ValidationResult: 验证结果
368pub fn validate_message_receivers(receivers: &[Value], max_receivers: usize) -> ValidationResult {
369    if receivers.is_empty() {
370        return ValidationResult::Invalid("At least one receiver is required".to_string());
371    }
372
373    if receivers.len() > max_receivers {
374        return ValidationResult::Invalid(format!(
375            "Too many receivers. Maximum {} allowed, got {}",
376            max_receivers,
377            receivers.len()
378        ));
379    }
380
381    for (i, receiver) in receivers.iter().enumerate() {
382        if let Some(obj) = receiver.as_object() {
383            // 验证必需字段
384            if !obj.contains_key("user_id")
385                && !obj.contains_key("union_id")
386                && !obj.contains_key("open_id")
387            {
388                return ValidationResult::Invalid(format!(
389                    "Receiver at index {} must have either user_id, union_id, or open_id",
390                    i
391                ));
392            }
393
394            // 验证ID格式
395            if let Some(user_id) = obj.get("user_id").and_then(|v| v.as_str()) {
396                if let ValidationResult::Invalid(msg) = validate_receiver_id(user_id, "user_id") {
397                    return ValidationResult::Invalid(format!(
398                        "Invalid user_id at index {}: {}",
399                        i, msg
400                    ));
401                }
402            }
403
404            if let Some(union_id) = obj.get("union_id").and_then(|v| v.as_str()) {
405                if let ValidationResult::Invalid(msg) = validate_receiver_id(union_id, "union_id") {
406                    return ValidationResult::Invalid(format!(
407                        "Invalid union_id at index {}: {}",
408                        i, msg
409                    ));
410                }
411            }
412
413            if let Some(open_id) = obj.get("open_id").and_then(|v| v.as_str()) {
414                if let ValidationResult::Invalid(msg) = validate_receiver_id(open_id, "open_id") {
415                    return ValidationResult::Invalid(format!(
416                        "Invalid open_id at index {}: {}",
417                        i, msg
418                    ));
419                }
420            }
421        } else {
422            return ValidationResult::Invalid(format!("Receiver at index {} must be an object", i));
423        }
424    }
425
426    ValidationResult::Valid
427}
428
429/// 验证消息模板内容
430///
431/// # 参数
432/// - `template_content`: 模板内容
433/// - `template_id`: 模板ID
434///
435/// # 返回
436/// - ValidationResult: 验证结果
437pub fn validate_message_template(template_content: &str, template_id: &str) -> ValidationResult {
438    if template_content.is_empty() {
439        return ValidationResult::Invalid("Template content cannot be empty".to_string());
440    }
441
442    if template_id.is_empty() {
443        return ValidationResult::Invalid("Template ID cannot be empty".to_string());
444    }
445
446    // 验证模板内容长度
447    if template_content.len() > 50_000 {
448        // 50KB
449        return ValidationResult::Invalid(
450            "Template content too long. Maximum 50KB allowed".to_string(),
451        );
452    }
453
454    // 验证模板ID格式(通常是UUID)
455    if let ValidationResult::Invalid(msg) = validate_uuid(template_id) {
456        return ValidationResult::Invalid(format!("Invalid template ID: {}", msg));
457    }
458
459    // 验证模板内容格式(JSON格式)
460    if serde_json::from_str::<Value>(template_content).is_err() {
461        return ValidationResult::Invalid("Template content must be valid JSON".to_string());
462    }
463
464    ValidationResult::Valid
465}
466
467/// 验证消息表情回复
468///
469/// # 参数
470/// - `message_id`: 消息ID
471/// - `emoji_type`: 表情类型
472/// - `emoji_key`: 表情key
473///
474/// # 返回
475/// - ValidationResult: 验证结果
476pub fn validate_message_reaction(
477    message_id: &str,
478    emoji_type: &str,
479    emoji_key: &str,
480) -> ValidationResult {
481    if message_id.is_empty() {
482        return ValidationResult::Invalid("Message ID cannot be empty".to_string());
483    }
484
485    if emoji_type.is_empty() {
486        return ValidationResult::Invalid("Emoji type cannot be empty".to_string());
487    }
488
489    if emoji_key.is_empty() {
490        return ValidationResult::Invalid("Emoji key cannot be empty".to_string());
491    }
492
493    // 验证表情类型
494    let valid_emoji_types = ["emoji", "custom"];
495    if !valid_emoji_types.contains(&emoji_type) {
496        return ValidationResult::Invalid(format!(
497            "Invalid emoji type '{}'. Valid types are: {}",
498            emoji_type,
499            valid_emoji_types.join(", ")
500        ));
501    }
502
503    // 验证表情key长度
504    if emoji_key.len() > 100 {
505        return ValidationResult::Invalid(
506            "Emoji key too long. Maximum 100 characters allowed".to_string(),
507        );
508    }
509
510    ValidationResult::Valid
511}
512
513/// Builder trait for IM validation
514pub trait ValidateImBuilder {
515    /// 验证消息内容
516    fn validate_message_content(&self, content: &str, message_type: &str) -> ValidationResult {
517        validate_message_content(content, message_type)
518    }
519
520    /// 验证接收者ID
521    fn validate_receiver_id(&self, receiver_id: &str, id_type: &str) -> ValidationResult {
522        validate_receiver_id(receiver_id, id_type)
523    }
524
525    /// 验证文件上传
526    fn validate_file_upload(
527        &self,
528        file_name: &str,
529        file_size: u64,
530        file_type: &str,
531    ) -> ValidationResult {
532        validate_file_upload(file_name, file_size, file_type)
533    }
534
535    /// 验证消息接收者列表
536    fn validate_message_receivers(
537        &self,
538        receivers: &[Value],
539        max_receivers: usize,
540    ) -> ValidationResult {
541        validate_message_receivers(receivers, max_receivers)
542    }
543}
544
545// 为所有实现了 ValidateBuilder 的类型自动实现 ValidateImBuilder
546impl<T: ValidateBuilder> ValidateImBuilder for T {}
547
548#[cfg(test)]
549mod tests {
550    use super::*;
551    use serde_json::json;
552
553    #[test]
554    fn test_validate_message_content() {
555        // 有效内容
556        assert!(matches!(
557            validate_message_content("Hello", "text"),
558            ValidationResult::Valid
559        ));
560
561        // 空内容
562        assert!(matches!(
563            validate_message_content("", "text"),
564            ValidationResult::Invalid(_)
565        ));
566
567        // 过长内容
568        let long_content = "a".repeat(153_601);
569        assert!(matches!(
570            validate_message_content(&long_content, "text"),
571            ValidationResult::Invalid(_)
572        ));
573    }
574
575    #[test]
576    fn test_validate_receiver_id() {
577        // 有效ID
578        assert!(matches!(
579            validate_receiver_id("ou_1234567890123456789012345", "open_id"),
580            ValidationResult::Valid
581        ));
582
583        // 无效前缀
584        assert!(matches!(
585            validate_receiver_id("abc123", "open_id"),
586            ValidationResult::Invalid(_)
587        ));
588
589        // 无效长度
590        assert!(matches!(
591            validate_receiver_id("ou_123", "open_id"),
592            ValidationResult::Invalid(_)
593        ));
594    }
595
596    #[test]
597    fn test_validate_uuid() {
598        // 有效UUID
599        assert!(matches!(
600            validate_uuid("550e8400-e29b-41d4-a716-446655440000"),
601            ValidationResult::Valid
602        ));
603
604        // 无效格式
605        assert!(matches!(
606            validate_uuid("invalid-uuid"),
607            ValidationResult::Invalid(_)
608        ));
609    }
610
611    #[test]
612    fn test_validate_file_upload() {
613        // 有效文件
614        assert!(matches!(
615            validate_file_upload("test.jpg", 1024, "image"),
616            ValidationResult::Valid
617        ));
618
619        // 无效文件名
620        assert!(matches!(
621            validate_file_upload("", 1024, "image"),
622            ValidationResult::Invalid(_)
623        ));
624
625        // 文件过大
626        assert!(matches!(
627            validate_file_upload("large.jpg", 200 * 1024 * 1024, "image"),
628            ValidationResult::Invalid(_)
629        ));
630
631        // 无效文件类型
632        assert!(matches!(
633            validate_file_upload("test.exe", 1024, "image"),
634            ValidationResult::Invalid(_)
635        ));
636    }
637
638    #[test]
639    fn test_validate_message_receivers() {
640        // 有效接收者
641        let receivers = vec![
642            json!({"user_id": "u_1234567890"}),
643            json!({"open_id": "ou_1234567890123456789012345"}),
644        ];
645        assert!(matches!(
646            validate_message_receivers(&receivers, 10),
647            ValidationResult::Valid
648        ));
649
650        // 空列表
651        assert!(matches!(
652            validate_message_receivers(&[], 10),
653            ValidationResult::Invalid(_)
654        ));
655
656        // 超过限制
657        let mut many_receivers = Vec::new();
658        for i in 0..20 {
659            many_receivers.push(json!({"user_id": format!("u_{}", i)}));
660        }
661        assert!(matches!(
662            validate_message_receivers(&many_receivers, 10),
663            ValidationResult::Invalid(_)
664        ));
665    }
666}