1use crate::core::validation::{ValidateBuilder, ValidationResult};
6use serde_json::Value;
7
8pub 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, "post" | "interactive" => 30_720, _ => {
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
46pub 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 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 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
106pub 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
137pub fn validate_uuid(uuid: &str) -> ValidationResult {
145 if uuid.is_empty() {
146 return ValidationResult::Invalid("UUID cannot be empty".to_string());
147 }
148
149 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 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 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
185pub fn validate_file_upload(file_name: &str, file_size: u64, file_type: &str) -> ValidationResult {
200 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 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 if file_size > 100 * 1024 * 1024 {
219 return ValidationResult::Invalid("File too large. Maximum 100MB allowed".to_string());
221 }
222
223 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
249pub 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 if let ValidationResult::Invalid(msg) = validate_uuid(message_id) {
268 return ValidationResult::Invalid(format!("Invalid message ID: {}", msg));
269 }
270
271 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
279pub 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 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 if read_timestamp < 1_577_836_800 {
309 return ValidationResult::Invalid("Read timestamp is too early".to_string());
311 }
312
313 ValidationResult::Valid
314}
315
316pub 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 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 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
360pub 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 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 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
429pub 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 if template_content.len() > 50_000 {
448 return ValidationResult::Invalid(
450 "Template content too long. Maximum 50KB allowed".to_string(),
451 );
452 }
453
454 if let ValidationResult::Invalid(msg) = validate_uuid(template_id) {
456 return ValidationResult::Invalid(format!("Invalid template ID: {}", msg));
457 }
458
459 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
467pub 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 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 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
513pub trait ValidateImBuilder {
515 fn validate_message_content(&self, content: &str, message_type: &str) -> ValidationResult {
517 validate_message_content(content, message_type)
518 }
519
520 fn validate_receiver_id(&self, receiver_id: &str, id_type: &str) -> ValidationResult {
522 validate_receiver_id(receiver_id, id_type)
523 }
524
525 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 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
545impl<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 assert!(matches!(
557 validate_message_content("Hello", "text"),
558 ValidationResult::Valid
559 ));
560
561 assert!(matches!(
563 validate_message_content("", "text"),
564 ValidationResult::Invalid(_)
565 ));
566
567 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 assert!(matches!(
579 validate_receiver_id("ou_1234567890123456789012345", "open_id"),
580 ValidationResult::Valid
581 ));
582
583 assert!(matches!(
585 validate_receiver_id("abc123", "open_id"),
586 ValidationResult::Invalid(_)
587 ));
588
589 assert!(matches!(
591 validate_receiver_id("ou_123", "open_id"),
592 ValidationResult::Invalid(_)
593 ));
594 }
595
596 #[test]
597 fn test_validate_uuid() {
598 assert!(matches!(
600 validate_uuid("550e8400-e29b-41d4-a716-446655440000"),
601 ValidationResult::Valid
602 ));
603
604 assert!(matches!(
606 validate_uuid("invalid-uuid"),
607 ValidationResult::Invalid(_)
608 ));
609 }
610
611 #[test]
612 fn test_validate_file_upload() {
613 assert!(matches!(
615 validate_file_upload("test.jpg", 1024, "image"),
616 ValidationResult::Valid
617 ));
618
619 assert!(matches!(
621 validate_file_upload("", 1024, "image"),
622 ValidationResult::Invalid(_)
623 ));
624
625 assert!(matches!(
627 validate_file_upload("large.jpg", 200 * 1024 * 1024, "image"),
628 ValidationResult::Invalid(_)
629 ));
630
631 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 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 assert!(matches!(
652 validate_message_receivers(&[], 10),
653 ValidationResult::Invalid(_)
654 ));
655
656 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}