代码之家  ›  专栏  ›  技术社区  ›  Stefanuk12

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

  •  0
  • Stefanuk12  · 技术社区  · 2 年前

    我有以下特点:

    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)或(String,i32,i2)等。

    2 回复  |  直到 2 年前
        1
  •  1
  •   Adam Smith    2 年前

    请考虑从类型级别处理此问题。A. Command 可以从一些字符串输入中解析,并且知道如何执行一些操作并返回其结果。

    use std::str::FromStr;
    
    pub enum CommandError {
        BadlyFormattedCommand,
        CouldNotFindCommand,
        NotEnoughArguments,
    }
    
    pub enum Commands {
        SetClipboard(String),
        MouseMoveRel(i32, i32),
        MouseMoveAbs(i32, i32),
    }
    
    impl FromStr for Commands {
        type Err = CommandError;
    
        fn from_str(s: &str) -> Result<Self, Self::Err> {
            match s.split("|").collect::<Vec<&str>>().as_slice() {
                ["1", value] => Ok(Commands::SetClipboard(value.to_string())),
                ["1", ..] => Err(CommandError::NotEnoughArguments),
                ["2", x, y] => {
                    let x: i32 = x.parse().map_err(|_| CommandError::BadlyFormattedCommand)?;
                    let y: i32 = y.parse().map_err(|_| CommandError::BadlyFormattedCommand)?;
                    Ok(Commands::MouseMoveRel(x, y))
                }
                ["2", ..] => Err(CommandError::NotEnoughArguments),
                ["3", x, y] => {
                    let x: i32 = x.parse().map_err(|_| CommandError::BadlyFormattedCommand)?;
                    let y: i32 = y.parse().map_err(|_| CommandError::BadlyFormattedCommand)?;
                    Ok(Commands::MouseMoveAbs(x, y))
                }
                ["3", ..] => Err(CommandError::NotEnoughArguments),
                _ => Err(CommandError::CouldNotFindCommand),
            }
        }
    }
    
    pub struct CommandResponse; // I won't define this, but maybe this is actually an enum?
    
    impl Commands {
        pub fn execute(&self) -> Result<Option<CommandResponse>, CommandError> {
            match self {
                Self::SetClipboard(s) => {
                    // do that thing
                    Ok(None)
                }
                Self::MouseMoveRel(x, y) => {
                    // do that thing
                    Ok(Some(CommandResponse)) // Maybe this is the new location?
                }
                Self::MouseMoveAbs(x, y) => {
                    // do THAT thing
                    Ok(Some(CommandResponse))
                }
            }
        }
    }
    

    然后使用代码是:

    def run_command(s: &str) -> Result<Option<CommandResult>, ApplicationError> {
        // Parse the command
        command: Command = s.parse().map_err(|_| ApplicationError)?;
    
        // and return the result of its execution
        command.execute().map_err(|_| ApplicationError)
    }
    
        2
  •  0
  •   PitaJ    2 年前

    我会这样做,使用枚举和合并 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)
        }
    }
    
    

    playground

        3
  •  0
  •   Silvestr    2 年前

    从评论到你的问题的人都是对的。使用关联类型很难完成此任务。

    您可以将枚举用于 Payload 。例如:

    pub enum Payload {
        U32(u32),
        Tuple((String, u32, u32)),
        I32(i32),
        String(String),
    }
    

    您知道所有可能的有效载荷,否则,您无法解析该命令。 然后命令实现将如下所示:

    pub struct MouseMoveRel;
    
    impl Command for MouseMoveRel {
        fn parse(&self, args: Vec<&str>) -> Result<Payload, CommandError> {
            Ok(Payload::String("".to_owned()))
        }
    
        fn run(&self, args: Payload) -> Result<Option<Vec<String>>, CommandError> {
            let Payload::String(data) = args else {
                return Err(CommandError);
            };
            // use data
            Ok(None)
        }
    }
    

    此外,您可以通过 From 特质

    impl From<String> for Payload {
        fn from(value: String) -> Self {
            Self::String(value)
        }
    }
    
    // result of using the From trait
    //fn parse(&self, args: Vec<&str>) -> Result<Payload, CommandError> {
    //    Ok("".to_owned().into())
    //}
    
    推荐文章