PicoBot/src/command/handler.rs

263 lines
7.0 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use crate::bus::InboundMessage;
use crate::command::context::CommandContext;
use crate::command::response::{CommandError, CommandResponse};
use crate::command::Command;
use crate::agent::AgentError;
use crate::gateway::session::SessionManager;
use async_trait::async_trait;
/// 命令处理器 trait
///
/// 实现此 trait 来处理特定类型的命令
/// 处理器是渠道无关的,只关心 Command 本身
#[async_trait]
pub trait CommandHandler: Send + Sync {
/// 是否可以处理此命令
fn can_handle(&self, cmd: &Command) -> bool;
/// 执行命令
///
/// # Arguments
/// * `cmd` - 要执行的命令
/// * `ctx` - 命令执行上下文
///
/// # Returns
/// * `Ok(CommandResponse)` - 命令执行成功
/// * `Err(CommandError)` - 命令执行失败
async fn handle(
&self,
cmd: Command,
ctx: CommandContext,
) -> Result<CommandResponse, CommandError>;
}
/// InChat 命令处理器 trait
///
/// 用于处理在聊天中直接输入的命令(如 Feishu/WeChat 等通道)
/// 接收 InboundMessage 和 SessionManager
#[async_trait]
pub trait InChatCommandHandler: Send + Sync {
/// 是否可以处理此命令
fn can_handle(&self, cmd: &Command) -> bool;
/// 执行命令
///
/// # Arguments
/// * `cmd` - 要执行的命令
/// * `inbound` - 入站消息(包含通道信息)
/// * `session_manager` - 会话管理器(用于获取 session
///
/// # Returns
/// * `Ok(Some(msg))` - 命令执行成功,返回要发送给用户的消息
/// * `Ok(None)` - 命令执行成功,无需发送消息
/// * `Err(AgentError)` - 命令执行失败
async fn handle(
&self,
cmd: Command,
inbound: &InboundMessage,
session_manager: &SessionManager,
) -> Result<Option<String>, AgentError>;
}
/// 命令路由器
///
/// 负责将命令分发到合适的处理器
pub struct CommandRouter {
handlers: Vec<Box<dyn CommandHandler>>,
}
impl CommandRouter {
/// 创建新的命令路由器
pub fn new() -> Self {
Self {
handlers: Vec::new(),
}
}
/// 注册命令处理器
///
/// # Arguments
/// * `handler` - 要注册的处理器
pub fn register(&mut self, handler: Box<dyn CommandHandler>) {
self.handlers.push(handler);
}
/// 分发命令到合适的处理器
///
/// # Arguments
/// * `cmd` - 要执行的命令
/// * `ctx` - 命令执行上下文
///
/// # Returns
/// * `Ok(CommandResponse)` - 命令执行成功
/// * `Err(CommandError)` - 没有合适的处理器或执行失败
pub async fn dispatch(
&self,
cmd: Command,
ctx: CommandContext,
) -> Result<CommandResponse, CommandError> {
// 查找能处理此命令的处理器
for handler in &self.handlers {
if handler.can_handle(&cmd) {
return handler.handle(cmd, ctx).await;
}
}
// 没有找到合适的处理器
Err(CommandError::new(
"NO_HANDLER",
format!("No handler found for command: {}", cmd.name()),
))
}
/// 分发命令,返回响应(如果失败则返回错误响应)
///
/// 与 `dispatch` 不同,此方法不会返回 Err
/// 而是将错误包装在 CommandResponse 中
pub async fn dispatch_with_response(
&self,
cmd: Command,
ctx: CommandContext,
) -> CommandResponse {
let request_id = ctx.request_id;
match self.dispatch(cmd, ctx).await {
Ok(response) => response,
Err(err) => CommandResponse::error(request_id, err),
}
}
}
impl Default for CommandRouter {
fn default() -> Self {
Self::new()
}
}
/// InChat 命令路由器
///
/// 负责将在聊天中输入的命令分发到合适的处理器
pub struct InChatCommandRouter {
handlers: Vec<Box<dyn InChatCommandHandler>>,
}
impl InChatCommandRouter {
/// 创建新的 InChat 命令路由器
pub fn new() -> Self {
Self {
handlers: Vec::new(),
}
}
/// 注册 InChat 命令处理器
///
/// # Arguments
/// * `handler` - 要注册的处理器
pub fn register(&mut self, handler: Box<dyn InChatCommandHandler>) {
self.handlers.push(handler);
}
/// 分发命令到合适的处理器
///
/// # Arguments
/// * `cmd` - 要执行的命令
/// * `inbound` - 入站消息
/// * `session_manager` - 会话管理器
///
/// # Returns
/// * `Ok(Some(msg))` - 命令被处理,返回成功消息
/// * `Ok(None)` - 没有合适的处理器
/// * `Err(AgentError)` - 执行失败
pub async fn dispatch(
&self,
cmd: Command,
inbound: &InboundMessage,
session_manager: &SessionManager,
) -> Result<Option<String>, AgentError> {
// 查找能处理此命令的处理器
for handler in &self.handlers {
if handler.can_handle(&cmd) {
let result = handler.handle(cmd, inbound, session_manager).await?;
return Ok(result);
}
}
// 没有找到合适的处理器
Ok(None)
}
}
impl Default for InChatCommandRouter {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use uuid::Uuid;
struct TestHandler;
#[async_trait]
impl CommandHandler for TestHandler {
fn can_handle(&self, cmd: &Command) -> bool {
matches!(cmd, Command::CreateSession { .. })
}
async fn handle(
&self,
_cmd: Command,
ctx: CommandContext,
) -> Result<CommandResponse, CommandError> {
Ok(CommandResponse::success(ctx.request_id)
.with_message(crate::command::response::MessageKind::Notification, "ok"))
}
}
struct NoOpHandler;
#[async_trait]
impl CommandHandler for NoOpHandler {
fn can_handle(&self, _cmd: &Command) -> bool {
false
}
async fn handle(
&self,
_cmd: Command,
_ctx: CommandContext,
) -> Result<CommandResponse, CommandError> {
unreachable!()
}
}
#[tokio::test]
async fn test_router_finds_handler() {
let mut router = CommandRouter::new();
router.register(Box::new(TestHandler));
router.register(Box::new(NoOpHandler));
let ctx = CommandContext::new("test", "test");
let cmd = Command::CreateSession { title: None };
let result = router.dispatch(cmd, ctx).await;
assert!(result.is_ok());
let resp = result.unwrap();
assert!(resp.success);
}
#[tokio::test]
async fn test_router_no_handler() {
let router = CommandRouter::new();
let ctx = CommandContext::new("test", "test");
let cmd = Command::CreateSession { title: None };
let result = router.dispatch(cmd, ctx).await;
assert!(result.is_err());
}
}