use shared_kernel::normalize_required_string; use crate::{ EDITOR_AGENT_MAX_ATTACHMENTS, EditorAgentConversationCreateInput, EditorAgentConversationDeleteInput, EditorAgentConversationTouchInput, EditorAgentError, }; pub fn validate_conversation_key( conversation_id: &str, owner_user_id: &str, ) -> Result<(String, String), EditorAgentError> { let conversation_id = normalize_required_string(conversation_id) .ok_or(EditorAgentError::MissingConversationId)?; let owner_user_id = normalize_required_string(owner_user_id).ok_or(EditorAgentError::MissingOwnerUserId)?; Ok((conversation_id, owner_user_id)) } pub fn validate_create_conversation( input: &EditorAgentConversationCreateInput, ) -> Result<(), EditorAgentError> { validate_conversation_key(&input.conversation_id, &input.owner_user_id)?; if normalize_required_string(&input.project_id).is_none() { return Err(EditorAgentError::MissingProjectId); } Ok(()) } pub fn validate_touch_conversation( input: &EditorAgentConversationTouchInput, ) -> Result<(), EditorAgentError> { validate_conversation_key(&input.conversation_id, &input.owner_user_id)?; Ok(()) } pub fn validate_delete_conversation( input: &EditorAgentConversationDeleteInput, ) -> Result<(), EditorAgentError> { validate_conversation_key(&input.conversation_id, &input.owner_user_id)?; Ok(()) } /// 校验会话访问权:拥有者匹配且未软删。 pub fn ensure_conversation_accessible( snapshot_owner_user_id: &str, snapshot_deleted: bool, requester_user_id: &str, ) -> Result<(), EditorAgentError> { if snapshot_owner_user_id != requester_user_id { return Err(EditorAgentError::NotConversationOwner); } if snapshot_deleted { return Err(EditorAgentError::ConversationDeleted); } Ok(()) } /// 校验用户消息:文本不能为空,附件数量不超过上限,附件引用需带资源标识。 pub fn validate_user_message( text: &str, attachment_reference_ids: &[String], ) -> Result<(), EditorAgentError> { if normalize_required_string(text).is_none() { return Err(EditorAgentError::EmptyMessage); } if attachment_reference_ids.len() > EDITOR_AGENT_MAX_ATTACHMENTS { return Err(EditorAgentError::TooManyAttachments); } if attachment_reference_ids .iter() .any(|reference_id| normalize_required_string(reference_id).is_none()) { return Err(EditorAgentError::InvalidAttachmentReference); } Ok(()) } #[cfg(test)] mod tests { use super::*; use crate::derive_conversation_title; #[test] fn derive_title_truncates_and_normalizes_whitespace() { assert_eq!( derive_conversation_title(" 帮我生成\n一张森林背景 "), "帮我生成 一张森林背景" ); assert_eq!(derive_conversation_title(""), "新对话"); assert_eq!(derive_conversation_title(" \n "), "新对话"); let long_text = "一二三四五六七八九十一二三四五六七八九十超出部分"; assert_eq!(derive_conversation_title(long_text).chars().count(), 20); } #[test] fn validate_user_message_rules() { assert_eq!( validate_user_message("", &[]), Err(EditorAgentError::EmptyMessage) ); assert_eq!( validate_user_message("", &["resource-1".to_string()]), Err(EditorAgentError::EmptyMessage) ); assert!(validate_user_message("画一棵树", &[]).is_ok()); let too_many: Vec = (0..10).map(|i| format!("resource-{i}")).collect(); assert_eq!( validate_user_message("画一棵树", &too_many), Err(EditorAgentError::TooManyAttachments) ); assert_eq!( validate_user_message("画一棵树", &[" ".to_string()]), Err(EditorAgentError::InvalidAttachmentReference) ); } #[test] fn ensure_conversation_accessible_rules() { assert!(ensure_conversation_accessible("user-1", false, "user-1").is_ok()); assert_eq!( ensure_conversation_accessible("user-1", false, "user-2"), Err(EditorAgentError::NotConversationOwner) ); assert_eq!( ensure_conversation_accessible("user-1", true, "user-1"), Err(EditorAgentError::ConversationDeleted) ); } }