使用“通用”数据存储命令结构

问题描述 投票:0回答:1

我有以下特质:

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)等

rust traits
1个回答
0
投票

我会做这样的事情,使用枚举并合并

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)
    }
}

游乐场

© www.soinside.com 2019 - 2024. All rights reserved.