mini_redis/cmd/
mod.rs

1mod get;
2pub use get::Get;
3
4mod publish;
5pub use publish::Publish;
6
7mod set;
8pub use set::Set;
9
10mod subscribe;
11pub use subscribe::{Subscribe, Unsubscribe};
12
13mod unknown;
14pub use unknown::Unknown;
15
16use crate::{Connection, Db, Frame, Parse, ParseError, Shutdown};
17
18/// Enumeration of supported Redis commands.
19///
20/// Methods called on `Command` are delegated to the command implementation.
21#[derive(Debug)]
22pub enum Command {
23    Get(Get),
24    Publish(Publish),
25    Set(Set),
26    Subscribe(Subscribe),
27    Unsubscribe(Unsubscribe),
28    Unknown(Unknown),
29}
30
31impl Command {
32    /// Parse a command from a received frame.
33    ///
34    /// The `Frame` must represent a Redis command supported by `mini-redis` and
35    /// be the array variant.
36    ///
37    /// # Returns
38    ///
39    /// On success, the command value is returned, otherwise, `Err` is returned.
40    pub fn from_frame(frame: Frame) -> crate::Result<Command> {
41        // The frame  value is decorated with `Parse`. `Parse` provides a
42        // "cursor" like API which makes parsing the command easier.
43        //
44        // The frame value must be an array variant. Any other frame variants
45        // result in an error being returned.
46        let mut parse = Parse::new(frame)?;
47
48        // All redis commands begin with the command name as a string. The name
49        // is read and converted to lower cases in order to do case sensitive
50        // matching.
51        let command_name = parse.next_string()?.to_lowercase();
52
53        // Match the command name, delegating the rest of the parsing to the
54        // specific command.
55        let command = match &command_name[..] {
56            "get" => Command::Get(Get::parse_frames(&mut parse)?),
57            "publish" => Command::Publish(Publish::parse_frames(&mut parse)?),
58            "set" => Command::Set(Set::parse_frames(&mut parse)?),
59            "subscribe" => Command::Subscribe(Subscribe::parse_frames(&mut parse)?),
60            "unsubscribe" => Command::Unsubscribe(Unsubscribe::parse_frames(&mut parse)?),
61            _ => {
62                // The command is not recognized and an Unknown command is
63                // returned.
64                //
65                // `return` is called here to skip the `finish()` call below. As
66                // the command is not recognized, there is most likely
67                // unconsumed fields remaining in the `Parse` instance.
68                return Ok(Command::Unknown(Unknown::new(command_name)));
69            }
70        };
71
72        // Check if there is any remaining unconsumed fields in the `Parse`
73        // value. If fields remain, this indicates an unexpected frame format
74        // and an error is returned.
75        parse.finish()?;
76
77        // The command has been successfully parsed
78        Ok(command)
79    }
80
81    /// Apply the command to the specified `Db` instance.
82    ///
83    /// The response is written to `dst`. This is called by the server in order
84    /// to execute a received command.
85    pub(crate) async fn apply(
86        self,
87        db: &Db,
88        dst: &mut Connection,
89        shutdown: &mut Shutdown,
90    ) -> crate::Result<()> {
91        use Command::*;
92
93        match self {
94            Get(cmd) => cmd.apply(db, dst).await,
95            Publish(cmd) => cmd.apply(db, dst).await,
96            Set(cmd) => cmd.apply(db, dst).await,
97            Subscribe(cmd) => cmd.apply(db, dst, shutdown).await,
98            Unknown(cmd) => cmd.apply(dst).await,
99            // `Unsubscribe` cannot be applied. It may only be received from the
100            // context of a `Subscribe` command.
101            Unsubscribe(_) => Err("`Unsubscribe` is unsupported in this context".into()),
102        }
103    }
104
105    /// Returns the command name
106    pub(crate) fn get_name(&self) -> &str {
107        match self {
108            Command::Get(_) => "get",
109            Command::Publish(_) => "pub",
110            Command::Set(_) => "set",
111            Command::Subscribe(_) => "subscribe",
112            Command::Unsubscribe(_) => "unsubscribe",
113            Command::Unknown(cmd) => cmd.get_name(),
114        }
115    }
116}