我有以下特质:
pub trait Command {
/// The expected arguments.
type Payload;
/// Returns the command ID.
fn id(&self) -> &'static str {
"0"
}
/// Returns the expected number of arguments.
fn args_count(&self) -> usize {
1
}
/// Parses the arguments.
fn parse(&self, args: Vec<&str>) -> Result<Self::Payload, CommandError>;
/// The function to run.
fn run(&self, args: Self::Payload) -> Result<Option<Vec<String>>, CommandError>;
}
可以这样使用
/// Sets the clipboard's content.
pub struct SetClipboard;
impl Command for SetClipboard {
type Payload = String;
fn id(&self) -> &'static str {
"2"
}
fn args_count(&self) -> usize {
1
}
fn parse(&self, args: Vec<&str>) -> Result<Self::Payload, CommandError> {
Ok(args.join(""))
}
fn run(&self, payload: Self::Payload) -> Result<Option<Vec<String>>, CommandError> {
// Set clipboard
if let Err(e) = set_clipboard(formats::Unicode, payload) {
return Err(CommandError::SystemError(e.raw_code()))
};
// Return
Ok(None)
}
}
但是,我当然需要引用这些“命令”。所以我将它们存储在一个数组中,但是我该如何将
Payload
类型设置为什么呢?
pub type Commands = Vec<Box<dyn Command<Payload = dyn std::any::Any>>>;
...
let commands: Commands = vec![
Box::new(MouseMoveRel),
Box::new(MouseMoveAbs),
Box::new(SetClipboard)
];
(由于类型不匹配,这不起作用)。
对于每个命令,我都会检查它是否与相同的命令 ID 匹配,以找出要使用的命令。这是在来自客户端(作为服务器)的 WebSocket 消息中完成的。
// Split the text
// Format: `COMMAND ID|JOB ID|ARGUMENTS
let split: Vec<&str> = text.split("|").collect();
// Check the length of the arguments
if split.len() < 2 {
return ctx.text(CommandError::BadlyFormattedCommand)
}
// Find which command
let command_id = split[0];
let Some(command) = self.commands.iter().find(|x| x.id() == command_id) else {
return ctx.text(CommandError::CouldNotFindCommand)
};
// Check the length of the arguments
if split.len() - 2 < command.args_count() {
return ctx.text(CommandError::NotEnoughArguments)
}
// Parse the arguments
let parsed = match command.parse(split[2..].to_vec()) {
Ok(x) => x,
Err(e) => return ctx.text(e)
};
// Run the command
match command.run(parsed) {
Ok(x) => match x {
Some(data) => ctx.text(CommandResponse {
id: split[1].to_owned(),
data
}),
None => ()
},
Err(e) => ctx.text(e)
}
有更好的方法吗?
注意:有效负载可以是任何类型,可以是字符串,也可以是任意长度的元组,例如(i32,i32)或(字符串,i32,i32)等
我会做这样的事情,使用枚举并合并
parse
和 run
。这使用 try 运算符 ?
来传播错误。
struct Unicode;
struct SetClipboardError;
fn set_clipboard(format: Unicode, s: &str) -> Result<(), SetClipboardError> {
todo!()
}
struct CommandError;
// so `?` can convert automagically
impl From<SetClipboardError> for CommandError {
fn from(other: SetClipboardError) -> CommandError {
todo!()
}
}
enum Command {
MouseMoveAbs,
SetClipboard,
}
impl Command {
fn from_id(id: &str) -> Result<Self, CommandError> {
match id {
"1" => Ok(Self::MouseMoveAbs),
"2" => Ok(Self::SetClipboard),
_ => Err(CommandError), // CouldNotFindCommand
}
}
fn parse_and_run(&self, args: &[&str]) -> Result<Option<Vec<String>>, CommandError> {
match self {
Self::MouseMoveAbs => {
todo!()
}
Self::SetClipboard => {
if args.len() < 1 {
return Err(CommandError); // NotEnoughArguments
}
// Set clipboard
set_clipboard(Unicode, args[0])?;
// Return
Ok(None)
}
}
}
}
struct CommandResponse {
id: String,
data: Vec<String>,
}
fn run_command(text: &str) -> Result<Option<CommandResponse>, CommandError> {
// Split the text
// Format: `COMMAND ID|JOB ID|ARGUMENTS
let split: Vec<&str> = text.split("|").collect();
// Check the length of the arguments
if split.len() < 2 {
return Err(CommandError); // BadlyFormattedCommand
}
// Find which command
let command = Command::from_id(split[0])?;
// Parse the arguments and run the command
if let Some(data) = command.parse_and_run(&split[2..])? {
Ok(Some(CommandResponse {
id: split[1].to_string(),
data,
}))
} else {
Ok(None)
}
}