- 移除 anyhow,使用 thiserror 定义强类型 Error 枚举 - 支持 #[from] 自动转换 mlua::Error,关键路径保留源码与上下文 - LuaRuntimeBuilder 采用惰性错误收集,简化链式初始化调用 - MirrorRegistry 实现规范化 FromLuaConversionError 错误转换
150 lines
5.5 KiB
Rust
150 lines
5.5 KiB
Rust
use crate::validators;
|
||
use mirror_core::{conversion_err, validation_error};
|
||
use mlua::{FromLua, Lua, Value};
|
||
use std::collections::HashMap;
|
||
|
||
const ALLOWED_KEYS: &[&str] = &["handler", "description"];
|
||
|
||
#[derive(Debug, Default, Clone)]
|
||
pub struct MrCommand {
|
||
pub commands: HashMap<String, MrSubCommand>,
|
||
}
|
||
impl MrCommand {
|
||
pub fn get(&self, name: &str) -> Option<&MrSubCommand> {
|
||
self.commands.get(name)
|
||
}
|
||
}
|
||
|
||
impl FromLua for MrCommand {
|
||
fn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> {
|
||
let root_tbl = match value {
|
||
Value::Table(t) => t,
|
||
other => {
|
||
return Err(conversion_err(
|
||
other.type_name(),
|
||
"MrCommand",
|
||
format!(
|
||
"commands 注册表的顶层配置必须是 Table,实际检测到: {}",
|
||
other.type_name()
|
||
),
|
||
));
|
||
}
|
||
};
|
||
|
||
let mut commands = HashMap::new();
|
||
|
||
for pair in root_tbl.pairs::<Value, Value>() {
|
||
let (cmd_name, cmd_entry) = pair?;
|
||
|
||
let cmd_name = validators::validate_command_name(&cmd_name)?;
|
||
// 2. 校验 entry 是否是 Table
|
||
if !cmd_entry.is_table() {
|
||
return Err(conversion_err(
|
||
cmd_entry.type_name(),
|
||
"MrCommand",
|
||
format!(
|
||
"子命令 '{cmd_name}' 的配置必须是 Table,实际检测到: {}",
|
||
cmd_entry.type_name()
|
||
),
|
||
));
|
||
}
|
||
// 3. 直接交由子命令解析,外层负责补充错误上下文
|
||
let cmd_def = MrSubCommand::from_lua(cmd_entry, lua).map_err(|err| {
|
||
validation_error(format!("子命令 '{cmd_name}' 配置解析失败:\n{err}"))
|
||
})?;
|
||
|
||
commands.insert(cmd_name, cmd_def);
|
||
}
|
||
Ok(Self { commands })
|
||
}
|
||
}
|
||
#[derive(Debug, Default, Clone)]
|
||
pub struct MrSubCommand {
|
||
/// 命令处理函数:必须是 Lua Function
|
||
pub(crate) module: String,
|
||
/// 命令行说明:必须是普通文本字符串
|
||
pub(crate) description: String,
|
||
}
|
||
|
||
impl FromLua for MrSubCommand {
|
||
fn from_lua(value: Value, _lua: &Lua) -> mlua::Result<Self> {
|
||
let table = match value {
|
||
Value::Table(t) => t,
|
||
other => {
|
||
return Err(conversion_err(
|
||
other.type_name(),
|
||
"MrSubCommand",
|
||
format!("配置必须是 Table,实际检测到: {}", other.type_name()),
|
||
));
|
||
}
|
||
};
|
||
// validators::LuaValidator::parse_command_entry(&tbl)
|
||
for pair in table.pairs::<Value, Value>() {
|
||
let (key, _) = pair?;
|
||
let key_str = validators::validate_command_name(&key)?;
|
||
|
||
if !ALLOWED_KEYS.contains(&key_str.as_str()) {
|
||
return Err(validation_error(format!(
|
||
"包含未知字段 '{key_str}'。\n\
|
||
==> 合法字段仅支持: {}\n\
|
||
==> 请检查是否存在拼写手误(例如把 handler 写成了 handle/handlr)",
|
||
ALLOWED_KEYS.join(", ")
|
||
)));
|
||
}
|
||
}
|
||
let module_val = table.get::<Value>("module")?;
|
||
// let handler_func = match handler_val {
|
||
// Value::Function(f) => f,
|
||
// Value::Boolean(true) => {
|
||
// return Err(validation_error(
|
||
// "子命令 handler 返回了布尔值 true。\n\
|
||
// ==>排查提示:脚本已成功加载,但未返回函数。\n\
|
||
// ==>请在末尾加上 'return function(args) ... end'。",
|
||
// ));
|
||
// }
|
||
// Value::Nil => {
|
||
// return Err(validation_error(
|
||
// "子命令缺少必填字段 handler(或对应脚本未返回函数)",
|
||
// ));
|
||
// }
|
||
// other => {
|
||
// return Err(validation_error(format!(
|
||
// "'handler' 必须是函数 (function),实际类型是: {}",
|
||
// other.type_name()
|
||
// )));
|
||
// }
|
||
// };
|
||
let module = match module_val {
|
||
Value::String(s) => {
|
||
// 兜底归一化(防止用户手写 module = "a/b" 时未走 mr.command)
|
||
// 同时兼容直接写字符串的情况
|
||
s.to_str()?.replace(['/', '\\'], ".")
|
||
}
|
||
other => {
|
||
return Err(mlua::Error::runtime(format!(
|
||
"module 必须通过 mr.command(\"xxx\") 声明,实际为 {}",
|
||
other.type_name()
|
||
)));
|
||
}
|
||
};
|
||
|
||
// 可选字段: args(缺失或 nil 时默认空列表;保留空字符串参数,与提权路径的 "" 语义一致)
|
||
let description_val = table.get::<Value>("description")?;
|
||
let description = match description_val {
|
||
Value::Nil => String::new(),
|
||
Value::String(s) => s.to_str()?.to_string(),
|
||
other => {
|
||
return Err(validation_error(format!(
|
||
"必须是字符串文本,实际类型是 {}",
|
||
other.type_name()
|
||
)));
|
||
}
|
||
};
|
||
// let handler = lua.create_registry_value(handler_func)?;
|
||
Ok(Self {
|
||
module,
|
||
description,
|
||
})
|
||
}
|
||
}
|