tyozo 0.1.0

in-memory key-value store
Documentation
use crate::command::Command;
use crate::lexer::Lexer;

pub fn parse<S: Into<String>>(input: S) -> Result<Command, String> {
    let input = split_input(input)?;
    parse_to_commnad(input)
}

fn parse_to_commnad(input: SplitedCommand) -> Result<Command, String> {
    let command_name = match input.get(0) {
        None => return Err(String::from("not input command name")),
        Some(c) => c,
    };

    let command = match command_name.as_str() {
        "set" => parse_set_command(input)?,
        "get" => parse_get_command(input)?,
        "setnx" => parse_setnx_command(input)?,
        "del" => parse_del_command(input)?,
        "shutdown" => Command::Shutdown,
        "multi" => Command::Multi,
        "exec" => Command::Exec,
        "abort" => Command::Abort,
        _ => return Err(String::from("unknown command")),
    };

    Ok(command)
}

fn parse_get_command(input: SplitedCommand) -> Result<Command, String> {
    let key = match input.get(1) {
        None => return Err(String::from("not input key")),
        Some(k) => k.to_string(),
    };

    if input.len() > 2 {
        return Err(String::from("Invalid arguments"));
    }

    Ok(Command::Get { key })
}

fn parse_set_command_common(input: SplitedCommand) -> Result<(String, String), String> {
    let key = match input.get(1) {
        None => return Err(String::from("not input key")),
        Some(k) => k.to_string(),
    };

    let value = match input.get(2) {
        None => return Err(String::from("not input value")),
        Some(v) => v.to_string(),
    };

    if input.len() > 3 {
        return Err(String::from("Invalid arguments"));
    }

    Ok((key, value))
}

fn parse_setnx_command(input: SplitedCommand) -> Result<Command, String> {
    let (key, value) = parse_set_command_common(input)?;

    Ok(Command::SetNX { key, value })
}

fn parse_set_command(input: SplitedCommand) -> Result<Command, String> {
    let (key, value) = parse_set_command_common(input)?;

    Ok(Command::Set { key, value })
}

fn parse_del_command(input: SplitedCommand) -> Result<Command, String> {
    if input.len() < 2 {
        return Err(String::from(
            "ERR wrong number of arguments for 'del' command",
        ));
    }

    Ok(Command::Del {
        keys: input[1..].to_vec(),
    })
}

type SplitedCommand = Vec<String>;

fn split_input<S: Into<String>>(input: S) -> Result<SplitedCommand, String> {
    let mut lexer = Lexer::new(input.into());

    let mut splited_command = vec![];

    lexer.read_char();

    while !lexer.is_end() {
        if lexer.current_ch() == '"' {
            let literal = lexer.read_string_literal();
            splited_command.push(literal?);
        }

        if is_letter(lexer.current_ch()) {
            let ident = lexer.read_identifier();
            splited_command.push(ident);
        }

        lexer.read_char();
    }

    Ok(splited_command)
}

fn is_letter(ch: char) -> bool {
    const CHS: [char; 3] = ['|', '-', '+'];
    'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || CHS.iter().any(|c| &ch == c)
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_parse_to_command() {
        let test_case: Vec<(Vec<&str>, Result<Command, String>)> = vec![
            (
                vec!["set", "key", "value"],
                Ok(Command::Set {
                    key: "key".into(),
                    value: "value".into(),
                }),
            ),
            (
                vec!["setnx", "key", "value"],
                Ok(Command::SetNX {
                    key: "key".into(),
                    value: "value".into(),
                }),
            ),
            (vec!["get", "key"], Ok(Command::Get { key: "key".into() })),
            (
                vec!["del", "key", "key2"],
                Ok(Command::Del {
                    keys: str_vec_to_splited_command(vec!["key", "key2"]),
                }),
            ),
            (vec!["multi"], Ok(Command::Multi)),
            (vec!["exec"], Ok(Command::Exec)),
            (vec!["abort"], Ok(Command::Abort)),
        ];

        for (input, expect) in test_case {
            let input = str_vec_to_splited_command(input);

            assert_eq!(parse_to_commnad(input), expect);
        }
    }

    #[test]
    fn test_split_input() {
        let test_case = vec![
            (
                r#"set "key" "value hoge""#,
                vec!["set", "key", "value hoge"],
            ),
            (r#"set "key" value"#, vec!["set", "key", "value"]),
            (r#"set key "value""#, vec!["set", "key", "value"]),
            (r#""set" key value"#, vec!["set", "key", "value"]),
            (r#"set key value"#, vec!["set", "key", "value"]),
            (r#"set key        value"#, vec!["set", "key", "value"]),
        ];

        for (input, expect) in test_case {
            assert_eq!(split_input(input), Ok(str_vec_to_splited_command(expect)))
        }
    }

    #[test]
    fn test_split_input_include_delimiter() {
        let test_case = vec![
            (
                r#"set key-hoge value-hoge"#,
                vec!["set", "key-hoge", "value-hoge"],
            ),
            (
                r#"set key+hoge value+hoge"#,
                vec!["set", "key+hoge", "value+hoge"],
            ),
            (
                r#"set key|hoge value|hoge"#,
                vec!["set", "key|hoge", "value|hoge"],
            ),
        ];

        for (input, expect) in test_case {
            assert_eq!(split_input(input), Ok(str_vec_to_splited_command(expect)));
        }
    }

    #[test]
    fn test_split_input_error() {
        assert!(split_input(r#"set key "value"#).is_err());
    }

    #[test]
    fn test_parse_set_command() {
        let input = str_vec_to_splited_command(vec!["set", "key", "value"]);
        let output = parse_set_command(input);
        assert_eq!(
            output,
            Ok(Command::Set {
                key: "key".into(),
                value: "value".into()
            })
        );
    }

    #[test]
    fn test_parse_set_command_error() {
        let test_case = vec![
            (vec!["set", "key"], "not input value"),
            (vec!["set"], "not input key"),
            (vec!["set", "key", "value", "invalid"], "Invalid arguments"),
        ];

        for (input, expect) in test_case {
            let input = str_vec_to_splited_command(input);
            let output = parse_set_command(input);

            assert!(output.is_err());
            assert_eq!(output.unwrap_err(), expect);
        }
    }

    #[test]
    fn test_parse_get_command() {
        let input = str_vec_to_splited_command(vec!["get", "key"]);
        let output = parse_get_command(input);

        assert_eq!(output, Ok(Command::Get { key: "key".into() }));
    }

    #[test]
    fn test_parse_get_command_error() {
        let test_case = vec![
            (vec!["get"], "not input key"),
            (vec!["get", "key", "invalid"], "Invalid arguments"),
        ];

        for (input, expect) in test_case {
            let input = str_vec_to_splited_command(input);
            let output = parse_get_command(input);

            assert!(output.is_err());
            assert_eq!(output.unwrap_err(), expect);
        }
    }

    #[test]
    fn test_parse_setnx_command() {
        let input = str_vec_to_splited_command(vec!["setnx", "key", "value"]);
        let output = parse_setnx_command(input);

        assert_eq!(
            output,
            Ok(Command::SetNX {
                key: "key".into(),
                value: "value".into()
            })
        );
    }

    #[test]
    fn test_parse_del_command() {
        let test_case = vec![
            (
                vec!["del", "key"],
                Ok(Command::Del {
                    keys: vec!["key".into()],
                }),
            ),
            (
                vec!["del", "key1", "key2", "key3"],
                Ok(Command::Del {
                    keys: str_vec_to_splited_command(vec!["key1", "key2", "key3"]),
                }),
            ),
        ];

        for (input, expected) in test_case {
            let input = str_vec_to_splited_command(input);
            let output = parse_del_command(input);

            assert_eq!(output, expected);
        }
    }

    #[test]
    fn test_parse_del_command_error() {
        let input = str_vec_to_splited_command(vec!["del"]);
        let output = parse_del_command(input);
        assert_eq!(
            output.unwrap_err(),
            String::from("ERR wrong number of arguments for 'del' command")
        );
    }

    fn str_vec_to_splited_command(input: Vec<&str>) -> SplitedCommand {
        input.iter().map(ToString::to_string).collect()
    }
}