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}