use crate::core::validation::{ValidateBuilder, ValidationResult};
pub fn validate_drive_file_type(file_type: &str) -> ValidationResult {
const VALID_FILE_TYPES: &[&str] = &[
"doc", "docx", "sheet", "bitable", "wiki", "file", "mindnote", "minutes", "slides", "pdf",
"txt",
];
if file_type.trim().is_empty() {
return ValidationResult::Invalid("文件类型不能为空".to_string());
}
if !VALID_FILE_TYPES.contains(&file_type) {
return ValidationResult::Invalid(format!(
"不支持的文件类型: {}。支持的类型: {}",
file_type,
VALID_FILE_TYPES.join(", ")
));
}
ValidationResult::Valid
}
pub fn validate_folder_name(name: &str) -> ValidationResult {
if name.trim().is_empty() {
return ValidationResult::Invalid("文件夹名称不能为空".to_string());
}
const MAX_FOLDER_NAME_LENGTH: usize = 255;
if name.len() > MAX_FOLDER_NAME_LENGTH {
return ValidationResult::Invalid(format!(
"文件夹名称过长 ({} > {})",
name.len(),
MAX_FOLDER_NAME_LENGTH
));
}
let invalid_chars = ['/', '\\', ':', '*', '?', '"', '<', '>', '|'];
for c in name.chars() {
if invalid_chars.contains(&c) {
return ValidationResult::Invalid(format!("文件夹名称包含非法字符: '{}'", c));
}
}
let reserved_names = ["CON", "PRN", "AUX", "NUL"];
if reserved_names.contains(&name.to_uppercase().as_str()) {
return ValidationResult::Invalid(format!("'{}' 是系统保留名称,不能使用", name));
}
ValidationResult::Valid
}
pub fn validate_file_upload_params(
file_name: &str,
file_size: u64,
parent_token: &str,
file_type: &str,
) -> ValidationResult {
if file_name.trim().is_empty() {
return ValidationResult::Invalid("文件名不能为空".to_string());
}
const MAX_FILENAME_LENGTH: usize = 255;
if file_name.len() > MAX_FILENAME_LENGTH {
return ValidationResult::Invalid(format!(
"文件名过长 ({} > {})",
file_name.len(),
MAX_FILENAME_LENGTH
));
}
let invalid_chars = ['/', '\\', ':', '*', '?', '"', '<', '>', '|'];
if file_name.chars().any(|c| invalid_chars.contains(&c)) {
return ValidationResult::Invalid("文件名包含非法字符".to_string());
}
if file_size == 0 {
return ValidationResult::Invalid("文件大小必须大于0".to_string());
}
let max_size = match file_type {
"image" => 20 * 1024 * 1024, "video" => 100 * 1024 * 1024, "audio" => 50 * 1024 * 1024, _ => 100 * 1024 * 1024, };
if file_size > max_size {
return ValidationResult::Invalid(format!(
"文件大小超过限制 ({} > {})",
file_size, max_size
));
}
if let ValidationResult::Invalid(e) = validate_token_format(parent_token) {
return ValidationResult::Invalid(e);
}
ValidationResult::Valid
}
pub fn validate_multipart_upload_params(
file_size: u64,
block_size: Option<u32>,
block_num: Option<u32>,
) -> ValidationResult {
if file_size == 0 {
return ValidationResult::Invalid("文件大小必须大于0".to_string());
}
if let Some(size) = block_size {
if size < 1024 {
return ValidationResult::Invalid("分片大小不能小于1KB".to_string());
}
if size > 10 * 1024 * 1024 {
return ValidationResult::Invalid("分片大小不能超过10MB".to_string());
}
if file_size < size as u64 {
return ValidationResult::Invalid("分片大小不能大于文件大小".to_string());
}
}
if let Some(num) = block_num {
if num == 0 {
return ValidationResult::Invalid("分片数量必须大于0".to_string());
}
if num > 10000 {
return ValidationResult::Invalid("分片数量不能超过10000".to_string());
}
}
ValidationResult::Valid
}
pub fn validate_permission_level(permission: &str) -> ValidationResult {
const VALID_PERMISSIONS: &[&str] = &[
"FullAccess",
"Edit",
"View",
"Comment",
"CanDownload",
"CanManageMembers",
];
if permission.trim().is_empty() {
return ValidationResult::Invalid("权限级别不能为空".to_string());
}
if !VALID_PERMISSIONS.contains(&permission) {
return ValidationResult::Invalid(format!(
"无效的权限级别: {}。支持的权限: {}",
permission,
VALID_PERMISSIONS.join(", ")
));
}
ValidationResult::Valid
}
pub fn validate_member_type(member_type: &str) -> ValidationResult {
const VALID_MEMBER_TYPES: &[&str] = &["user", "chat", "department"];
if member_type.trim().is_empty() {
return ValidationResult::Invalid("成员类型不能为空".to_string());
}
if !VALID_MEMBER_TYPES.contains(&member_type) {
return ValidationResult::Invalid(format!(
"无效的成员类型: {}。支持的类型: {}",
member_type,
VALID_MEMBER_TYPES.join(", ")
));
}
ValidationResult::Valid
}
pub fn validate_member_id(member_type: &str, member_id: &str) -> ValidationResult {
if let ValidationResult::Invalid(e) = validate_member_type(member_type) {
return ValidationResult::Invalid(e);
}
if member_id.trim().is_empty() {
return ValidationResult::Invalid("成员ID不能为空".to_string());
}
match member_type {
"user" => {
if !(member_id.starts_with("ou_") || member_id.chars().all(|c| c.is_ascii_digit())) {
return ValidationResult::Invalid("用户ID格式无效".to_string());
}
if member_id.len() > 64 {
return ValidationResult::Invalid("用户ID过长".to_string());
}
}
"chat" => {
if !member_id.starts_with("oc_") {
return ValidationResult::Invalid("群组ID应以'oc_'开头".to_string());
}
if member_id.len() != 28 {
return ValidationResult::Invalid("群组ID长度应为28字符".to_string());
}
}
"department" => {
if !member_id.starts_with("od_") {
return ValidationResult::Invalid("部门ID应以'od_'开头".to_string());
}
if member_id.is_empty() || member_id.len() > 64 {
return ValidationResult::Invalid("部门ID长度无效".to_string());
}
}
_ => {}
}
ValidationResult::Valid
}
pub fn validate_permission_settings(
external_access: Option<&str>,
security_entity: Option<&str>,
comment_entity: Option<&str>,
) -> ValidationResult {
if let Some(access) = external_access {
const VALID_EXTERNAL_ACCESS: &[&str] = &[
"open",
"closed",
"allow_share_partner_tenant",
"limited_open",
];
if !VALID_EXTERNAL_ACCESS.contains(&access) {
return ValidationResult::Invalid(format!(
"无效的外部访问设置: {}。支持的选项: {}",
access,
VALID_EXTERNAL_ACCESS.join(", ")
));
}
}
if let Some(security) = security_entity {
const VALID_SECURITY_ENTITIES: &[&str] = &[
"anyone_can_view",
"anyone_can_edit",
"only_full_access",
"specified_members",
];
if !VALID_SECURITY_ENTITIES.contains(&security) {
return ValidationResult::Invalid(format!(
"无效的安全实体设置: {}。支持的选项: {}",
security,
VALID_SECURITY_ENTITIES.join(", ")
));
}
}
if let Some(comment) = comment_entity {
const VALID_COMMENT_ENTITIES: &[&str] =
&["anyone_can_view", "anyone_can_edit", "no_comment"];
if !VALID_COMMENT_ENTITIES.contains(&comment) {
return ValidationResult::Invalid(format!(
"无效的评论实体设置: {}。支持的选项: {}",
comment,
VALID_COMMENT_ENTITIES.join(", ")
));
}
}
ValidationResult::Valid
}
pub fn validate_pagination_params(
page_size: Option<i32>,
page_token: Option<&str>,
) -> ValidationResult {
if let Some(size) = page_size {
if size <= 0 {
return ValidationResult::Invalid("页面大小必须大于0".to_string());
}
if size > 500 {
return ValidationResult::Invalid("页面大小不能超过500".to_string());
}
}
if let Some(token) = page_token {
if token.trim().is_empty() {
return ValidationResult::Invalid("页面令牌不能为空".to_string());
}
let min_len = 1;
let max_len = 1000;
if token.len() < min_len || token.len() > max_len {
return ValidationResult::Invalid(format!(
"页面令牌长度无效 ({}-{} 字符)",
min_len, max_len
));
}
}
ValidationResult::Valid
}
pub fn validate_search_params(
search_key: &str,
count: Option<i32>,
offset: Option<i32>,
) -> ValidationResult {
if search_key.trim().is_empty() {
return ValidationResult::Invalid("搜索关键词不能为空".to_string());
}
if search_key.len() > 200 {
return ValidationResult::Invalid("搜索关键词过长 (最多200字符)".to_string());
}
if let Some(cnt) = count {
if cnt <= 0 {
return ValidationResult::Invalid("搜索数量必须大于0".to_string());
}
if cnt > 200 {
return ValidationResult::Invalid("搜索数量不能超过200".to_string());
}
}
if let Some(off) = offset {
if off < 0 {
return ValidationResult::Invalid("偏移量不能为负数".to_string());
}
if off > 10000 {
return ValidationResult::Invalid("偏移量过大 (最大10000)".to_string());
}
}
ValidationResult::Valid
}
pub fn validate_token_format(token: &str) -> ValidationResult {
if token.trim().is_empty() {
return ValidationResult::Invalid("令牌不能为空".to_string());
}
let min_len = 10;
let max_len = 100;
if token.len() < min_len || token.len() > max_len {
return ValidationResult::Invalid(format!("令牌长度无效 ({}-{} 字符)", min_len, max_len));
}
if !token
.chars()
.all(|c| c.is_alphanumeric() || c == '_' || c == '-' || c == '+' || c == '=' || c == '/')
{
return ValidationResult::Invalid("令牌包含非法字符".to_string());
}
ValidationResult::Valid
}
pub fn validate_sort_params(order_by: Option<&str>, direction: Option<&str>) -> ValidationResult {
if let Some(order) = order_by {
const VALID_ORDER_FIELDS: &[&str] = &[
"created_time",
"edited_time",
"file_type",
"size",
"name",
"viewed_time",
];
if !VALID_ORDER_FIELDS.contains(&order) {
return ValidationResult::Invalid(format!(
"无效的排序字段: {}。支持的字段: {}",
order,
VALID_ORDER_FIELDS.join(", ")
));
}
}
if let Some(dir) = direction {
const VALID_DIRECTIONS: &[&str] = &["ASC", "DESC"];
if !VALID_DIRECTIONS.contains(&dir) {
return ValidationResult::Invalid(format!(
"无效的排序方向: {}。支持的方向: {}",
dir,
VALID_DIRECTIONS.join(", ")
));
}
}
ValidationResult::Valid
}
pub fn validate_file_copy_move_params(
source_token: &str,
destination_folder_token: &str,
new_name: Option<&str>,
) -> ValidationResult {
if let ValidationResult::Invalid(e) = validate_token_format(source_token) {
return ValidationResult::Invalid(e);
}
if let ValidationResult::Invalid(e) = validate_token_format(destination_folder_token) {
return ValidationResult::Invalid(e);
}
if let Some(name) = new_name {
if name.trim().is_empty() {
return ValidationResult::Invalid("新名称不能为空".to_string());
}
const MAX_NAME_LENGTH: usize = 255;
if name.len() > MAX_NAME_LENGTH {
return ValidationResult::Invalid(format!(
"新名称过长 ({} > {})",
name.len(),
MAX_NAME_LENGTH
));
}
let invalid_chars = ['/', '\\', ':', '*', '?', '"', '<', '>', '|'];
if name.chars().any(|c| invalid_chars.contains(&c)) {
return ValidationResult::Invalid("新名称包含非法字符".to_string());
}
}
if source_token == destination_folder_token {
return ValidationResult::Invalid("不能将文件复制/移动到自身".to_string());
}
ValidationResult::Valid
}
pub fn validate_batch_operation(tokens: &[String], max_batch_size: usize) -> ValidationResult {
if tokens.is_empty() {
return ValidationResult::Invalid("批量操作列表不能为空".to_string());
}
if tokens.len() > max_batch_size {
return ValidationResult::Invalid(format!(
"批量操作数量超过限制 ({} > {})",
tokens.len(),
max_batch_size
));
}
for (i, token) in tokens.iter().enumerate() {
if let ValidationResult::Invalid(msg) = validate_token_format(token) {
return ValidationResult::Invalid(format!("第{}个令牌无效: {}", i + 1, msg));
}
}
let unique_tokens: std::collections::HashSet<_> = tokens.iter().collect();
if unique_tokens.len() != tokens.len() {
return ValidationResult::Invalid("批量操作列表包含重复的令牌".to_string());
}
ValidationResult::Valid
}
pub fn validate_file_download_params(
file_token: &str,
file_type: &str,
expire_time: Option<u32>,
) -> ValidationResult {
if let ValidationResult::Invalid(e) = validate_token_format(file_token) {
return ValidationResult::Invalid(e);
}
if let ValidationResult::Invalid(e) = validate_drive_file_type(file_type) {
return ValidationResult::Invalid(e);
}
if let Some(expire) = expire_time {
if expire == 0 {
return ValidationResult::Invalid("过期时间必须大于0".to_string());
}
const MAX_EXPIRE_TIME: u32 = 7 * 24 * 3600; if expire > MAX_EXPIRE_TIME {
return ValidationResult::Invalid(format!("过期时间不能超过{}秒", MAX_EXPIRE_TIME));
}
}
ValidationResult::Valid
}
pub trait ValidateDriveBuilder {
fn validate_drive_file_type(&self, file_type: &str) -> ValidationResult {
validate_drive_file_type(file_type)
}
fn validate_folder_name(&self, name: &str) -> ValidationResult {
validate_folder_name(name)
}
fn validate_file_upload_params(
&self,
file_name: &str,
file_size: u64,
parent_token: &str,
file_type: &str,
) -> ValidationResult {
validate_file_upload_params(file_name, file_size, parent_token, file_type)
}
fn validate_permission_level(&self, permission: &str) -> ValidationResult {
validate_permission_level(permission)
}
fn validate_member_id(&self, member_type: &str, member_id: &str) -> ValidationResult {
validate_member_id(member_type, member_id)
}
fn validate_pagination_params(
&self,
page_size: Option<i32>,
page_token: Option<&str>,
) -> ValidationResult {
validate_pagination_params(page_size, page_token)
}
fn validate_search_params(
&self,
search_key: &str,
count: Option<i32>,
offset: Option<i32>,
) -> ValidationResult {
validate_search_params(search_key, count, offset)
}
fn validate_batch_operation(
&self,
tokens: &[String],
max_batch_size: usize,
) -> ValidationResult {
validate_batch_operation(tokens, max_batch_size)
}
}
impl<T: ValidateBuilder> ValidateDriveBuilder for T {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validate_drive_file_type() {
assert!(matches!(
validate_drive_file_type("doc"),
ValidationResult::Valid
));
assert!(matches!(
validate_drive_file_type("sheet"),
ValidationResult::Valid
));
assert!(matches!(
validate_drive_file_type("invalid"),
ValidationResult::Invalid(_)
));
}
#[test]
fn test_validate_folder_name() {
assert!(matches!(
validate_folder_name("Valid Folder"),
ValidationResult::Valid
));
assert!(matches!(
validate_folder_name(""),
ValidationResult::Invalid(_)
));
assert!(matches!(
validate_folder_name("Invalid/Name"),
ValidationResult::Invalid(_)
));
assert!(matches!(
validate_folder_name("CON"),
ValidationResult::Invalid(_)
));
}
#[test]
fn test_validate_file_upload_params() {
assert!(matches!(
validate_file_upload_params("test.txt", 1024, "folder_token", "file"),
ValidationResult::Valid
));
assert!(matches!(
validate_file_upload_params("", 1024, "folder_token", "file"),
ValidationResult::Invalid(_)
));
assert!(matches!(
validate_file_upload_params("test.txt", 0, "folder_token", "file"),
ValidationResult::Invalid(_)
));
}
#[test]
fn test_validate_permission_level() {
assert!(matches!(
validate_permission_level("FullAccess"),
ValidationResult::Valid
));
assert!(matches!(
validate_permission_level("View"),
ValidationResult::Valid
));
assert!(matches!(
validate_permission_level("Invalid"),
ValidationResult::Invalid(_)
));
}
#[test]
fn test_validate_member_id() {
assert!(matches!(
validate_member_id("user", "ou_1234567890"),
ValidationResult::Valid
));
assert!(matches!(
validate_member_id("chat", "oc_1234567890123456789012345"),
ValidationResult::Valid
));
assert!(matches!(
validate_member_id("department", "od_1234567890"),
ValidationResult::Valid
));
assert!(matches!(
validate_member_id("user", ""),
ValidationResult::Invalid(_)
));
}
#[test]
fn test_validate_batch_operation() {
let tokens = vec![
"doccnABC123DEF456GHI789JKL012MN".to_string(),
"doccnXYZ789ABC456DEF789GHI012JK".to_string(),
];
assert!(matches!(
validate_batch_operation(&tokens, 10),
ValidationResult::Valid
));
let empty_tokens: Vec<String> = vec![];
assert!(matches!(
validate_batch_operation(&empty_tokens, 10),
ValidationResult::Invalid(_)
));
let duplicate_tokens = vec!["token1".to_string(), "token1".to_string()];
assert!(matches!(
validate_batch_operation(&duplicate_tokens, 10),
ValidationResult::Invalid(_)
));
}
#[test]
fn test_validate_search_params() {
assert!(matches!(
validate_search_params("test", Some(10), Some(0)),
ValidationResult::Valid
));
assert!(matches!(
validate_search_params("", Some(10), Some(0)),
ValidationResult::Invalid(_)
));
assert!(matches!(
validate_search_params("test", Some(0), Some(0)),
ValidationResult::Invalid(_)
));
assert!(matches!(
validate_search_params("test", Some(300), Some(0)),
ValidationResult::Invalid(_)
));
}
}