Browse Source

Update command flags (#27)

César D. Rodas 3 years ago
parent
commit
09cf5ee365
7 changed files with 300 additions and 113 deletions
  1. 1 0
      src/cmd/mod.rs
  2. 1 7
      src/cmd/pubsub.rs
  3. 90 0
      src/cmd/server.rs
  4. 103 3
      src/dispatcher/command.rs
  5. 101 102
      src/dispatcher/mod.rs
  6. 3 0
      src/error.rs
  7. 1 1
      src/macros.rs

+ 1 - 0
src/cmd/mod.rs

@@ -5,6 +5,7 @@ pub mod key;
 pub mod list;
 pub mod metrics;
 pub mod pubsub;
+pub mod server;
 pub mod set;
 pub mod string;
 pub mod transaction;

+ 1 - 7
src/cmd/pubsub.rs

@@ -33,13 +33,7 @@ pub async fn pubsub(conn: &Connection, args: &[Bytes]) -> Result<Value, Error> {
             .flatten()
             .collect::<Vec<Value>>()
             .into()),
-        cmd => Ok(Value::Err(
-            "ERR".to_owned(),
-            format!(
-                "Unknown subcommand or wrong number of arguments for '{}'. Try PUBSUB HELP.",
-                cmd
-            ),
-        )),
+        cmd => Err(Error::SubCommandNotFound(cmd.into(), String::from_utf8_lossy(&args[0]).into())),
     }
 }
 

+ 90 - 0
src/cmd/server.rs

@@ -0,0 +1,90 @@
+//! # Server command handlers
+use crate::{
+    check_arg, connection::Connection, error::Error, value::bytes_to_number, value::Value,
+};
+use bytes::Bytes;
+use std::time::SystemTime;
+use std::time::UNIX_EPOCH;
+use std::{convert::TryInto, ops::Neg};
+use tokio::time::Duration;
+
+/// Returns Array reply of details about all Redis commands.
+pub async fn command(conn: &Connection, args: &[Bytes]) -> Result<Value, Error> {
+    let dispatcher = conn.all_connections().get_dispatcher();
+    if args.len() == 1 {
+        return Ok(Value::Array(
+            dispatcher
+                .get_all_commands()
+                .iter()
+                .map(|command| command.get_command_info())
+                .collect(),
+        ));
+    }
+
+    match String::from_utf8_lossy(&args[1]).to_lowercase().as_str() {
+        "count" => Ok(dispatcher.get_all_commands().len().into()),
+        "info" => {
+            let mut result = vec![];
+            for command in &args[2..] {
+                result.push(
+                    dispatcher
+                        .get_handler_for_command(
+                            String::from_utf8_lossy(command).to_string().as_str(),
+                        )
+                        .map(|command| command.get_command_info())
+                        .unwrap_or_else(|_| Value::Null),
+                )
+            }
+            Ok(Value::Array(result))
+        }
+        "getkeys" => {
+            if args.len() == 2 {
+                return Err(Error::SubCommandNotFound(
+                    String::from_utf8_lossy(&args[1]).into(),
+                    String::from_utf8_lossy(&args[0]).into(),
+                ));
+            }
+            let args = &args[2..];
+            let command = dispatcher.get_handler(args)?;
+            Ok(Value::Array(
+                command
+                    .get_keys(args)
+                    .iter()
+                    .map(|key| Value::Blob((*key).clone()))
+                    .collect(),
+            ))
+        }
+        "help" => Ok(Value::Array(vec![
+            Value::String(
+                "COMMAND <subcommand> [<arg> [value] [opt] ...]. Subcommands are:".into(),
+            ),
+            Value::String("(no subcommand)".into()),
+            Value::String("\tReturn details about all Redis commands".into()),
+            Value::String("COUNT".into()),
+            Value::String("\tReturn the total number of commands in this Redis server.".into()),
+            Value::String("GETKEYS <full-command>".into()),
+            Value::String("\tReturn the keys from a full Redis command.".into()),
+            Value::String("INFO [<command-name> ...]".into()),
+            Value::String("Return details about multiple Redis commands.".into()),
+            Value::String("HELP".into()),
+            Value::String("\tPrints this help.".into()),
+        ])),
+        cmd => Err(Error::SubCommandNotFound(
+            cmd.into(),
+            String::from_utf8_lossy(&args[0]).into(),
+        )),
+    }
+}
+
+/// The TIME command returns the current server time as a two items lists: a
+/// Unix timestamp and the amount of microseconds already elapsed in the current
+/// second. Basically the interface is very similar to the one of the
+/// gettimeofday system call.
+pub async fn time(_conn: &Connection, _args: &[Bytes]) -> Result<Value, Error> {
+    let now = SystemTime::now();
+    let since_the_epoch = now.duration_since(UNIX_EPOCH).expect("Time went backwards");
+    let seconds = format!("{}", since_the_epoch.as_secs());
+    let millis = format!("{}", since_the_epoch.subsec_millis());
+
+    Ok(vec![seconds.as_str(), millis.as_str()].into())
+}

+ 103 - 3
src/dispatcher/command.rs

@@ -15,12 +15,67 @@ use bytes::Bytes;
 use metered::{ErrorCount, HitCount, InFlight, ResponseTime, Throughput};
 use std::convert::TryInto;
 
+/// Command Flags
+#[derive(Debug, Eq, PartialEq, Clone, Copy)]
+pub enum Flag {
+    /// May result in database modification
+    Write,
+    /// Will never modify database
+    ReadOnly,
+    /// Can fail if the server runs out of memory
+    DenyOom,
+    /// Server Admin command
+    Admin,
+    /// Pubsub related command
+    PubSub,
+    /// Not used, added to be compatible
+    NoScript,
+    /// Random result
+    Random,
+    /// Not used, added to be compatible
+    SortForScript,
+    /// Allow command while database is loading
+    Loading,
+    /// Allow command while replica has stale data
+    Stale,
+    /// Do not show this command in MONITOR
+    SkipMonitor,
+    /// Do not gather stats about slow log
+    SkipSlowlog,
+    /// The command is fast (Close to log(N) time)
+    Fast,
+    /// Command may be replicated to other nodes
+    MayReplicate,
+}
+
+impl ToString for Flag {
+    fn to_string(&self) -> String {
+        match self {
+            Self::Write => "write",
+            Self::DenyOom => "denyoom",
+            Self::ReadOnly => "readonly",
+            Self::Admin => "admin",
+            Self::PubSub => "pubsub",
+            Self::NoScript => "noscript",
+            Self::Random => "random",
+            Self::SortForScript => "sort_for_script",
+            Self::Loading => "loading",
+            Self::Stale => "stale",
+            Self::SkipMonitor => "skip_monitor",
+            Self::SkipSlowlog => "skip_slowlog",
+            Self::Fast => "fast",
+            Self::MayReplicate => "may_replicate",
+        }
+        .to_owned()
+    }
+}
+
 /// Command definition
 #[derive(Debug)]
 pub struct Command {
     name: &'static str,
     group: &'static str,
-    tags: &'static [&'static str],
+    flags: &'static [Flag],
     min_args: i32,
     key_start: i32,
     key_stop: i32,
@@ -49,7 +104,7 @@ impl Command {
     pub fn new(
         name: &'static str,
         group: &'static str,
-        tags: &'static [&'static str],
+        flags: &'static [Flag],
         min_args: i32,
         key_start: i32,
         key_stop: i32,
@@ -59,7 +114,7 @@ impl Command {
         Self {
             name,
             group,
-            tags,
+            flags,
             min_args,
             key_start,
             key_stop,
@@ -116,6 +171,51 @@ impl Command {
         }
     }
 
+    /// Returns information about this command. The response is encoded as a
+    /// Value, following the output of the COMMAND command in redis
+    pub fn get_command_info(&self) -> Value {
+        Value::Array(vec![
+            self.name().into(),
+            self.get_min_args().into(),
+            Value::Array(
+                self.get_flags()
+                    .iter()
+                    .map(|m| m.to_string().into())
+                    .collect(),
+            ),
+            self.get_key_start().into(),
+            self.get_key_stop().into(),
+            self.get_key_step().into(),
+        ])
+    }
+
+    /// Returns the command's flags
+    pub fn get_flags(&self) -> Vec<Flag> {
+        self.flags.to_vec()
+    }
+
+    /// Returns the minimum arguments (including the command name itself) that
+    /// this command takes. This is also known as the arity of a command.
+    pub fn get_min_args(&self) -> i32 {
+        self.min_args
+    }
+
+    /// Where does the first database 'key' start in the arguments list
+    pub fn get_key_start(&self) -> i32 {
+        self.key_start
+    }
+
+    /// Where does the last database 'key' start in the arguments list
+    pub fn get_key_stop(&self) -> i32 {
+        self.key_stop
+    }
+
+    /// Useful to extract 'keys' from the arguments alongside with key_stop and
+    /// key_start
+    pub fn get_key_step(&self) -> usize {
+        self.key_step
+    }
+
     /// Command group
     pub fn group(&self) -> &'static str {
         &self.group

+ 101 - 102
src/dispatcher/mod.rs

@@ -13,27 +13,17 @@ use crate::{
     value::Value,
 };
 use bytes::Bytes;
+use command::Flag;
 use std::convert::TryInto;
-use std::time::SystemTime;
-use std::time::UNIX_EPOCH;
 
 pub mod command;
 
 /// Returns the server time
-async fn do_time(_conn: &Connection, _args: &[Bytes]) -> Result<Value, Error> {
-    let now = SystemTime::now();
-    let since_the_epoch = now.duration_since(UNIX_EPOCH).expect("Time went backwards");
-    let seconds = format!("{}", since_the_epoch.as_secs());
-    let millis = format!("{}", since_the_epoch.subsec_millis());
-
-    Ok(vec![seconds.as_str(), millis.as_str()].into())
-}
-
 dispatcher! {
     set {
         sadd {
             cmd::set::sadd,
-            [""],
+            [Flag::Write Flag::DenyOom Flag::Fast],
             -3,
             1,
             1,
@@ -42,7 +32,7 @@ dispatcher! {
         },
         scard {
             cmd::set::scard,
-            [""],
+            [Flag::ReadOnly Flag::Fast],
             2,
             1,
             1,
@@ -51,7 +41,7 @@ dispatcher! {
         },
         sdiff {
             cmd::set::sdiff,
-            [""],
+            [Flag::ReadOnly Flag::SortForScript],
             -2,
             1,
             -1,
@@ -60,7 +50,7 @@ dispatcher! {
         },
         sdiffstore {
             cmd::set::sdiffstore,
-            [""],
+            [Flag::Write Flag::DenyOom],
             -3,
             1,
             -1,
@@ -69,7 +59,7 @@ dispatcher! {
         },
         sinter {
             cmd::set::sinter,
-            [""],
+            [Flag::ReadOnly Flag::SortForScript],
             -2,
             1,
             -1,
@@ -78,7 +68,7 @@ dispatcher! {
         },
         sintercard {
             cmd::set::sintercard,
-            [""],
+            [Flag::ReadOnly],
             -2,
             1,
             -1,
@@ -87,7 +77,7 @@ dispatcher! {
         },
         sinterstore {
             cmd::set::sinterstore,
-            [""],
+            [Flag::Write Flag::DenyOom],
             -3,
             1,
             -1,
@@ -96,7 +86,7 @@ dispatcher! {
         },
         sismember {
             cmd::set::sismember,
-            [""],
+            [Flag::ReadOnly Flag::Fast],
             3,
             1,
             1,
@@ -105,7 +95,7 @@ dispatcher! {
         },
         smembers {
             cmd::set::smembers,
-            [""],
+            [Flag::ReadOnly Flag::SortForScript],
             2,
             1,
             1,
@@ -114,7 +104,7 @@ dispatcher! {
         },
         smismember {
             cmd::set::smismember,
-            [""],
+            [Flag::ReadOnly Flag::Fast],
             -3,
             1,
             1,
@@ -123,7 +113,7 @@ dispatcher! {
         },
         smove {
             cmd::set::smove,
-            [""],
+            [Flag::Write Flag::Fast],
             4,
             1,
             2,
@@ -132,7 +122,7 @@ dispatcher! {
         },
         spop {
             cmd::set::spop,
-            [""],
+            [Flag::Write Flag::Random Flag::Fast],
             -2,
             1,
             1,
@@ -141,7 +131,7 @@ dispatcher! {
         },
         srandmember {
             cmd::set::srandmember,
-            [""],
+            [Flag::ReadOnly Flag::Random],
             -2,
             1,
             1,
@@ -150,7 +140,7 @@ dispatcher! {
         },
         srem {
             cmd::set::srem,
-            [""],
+            [Flag::Write Flag::Fast],
             -3,
             1,
             1,
@@ -159,7 +149,7 @@ dispatcher! {
         },
         sunion {
             cmd::set::sunion,
-            [""],
+            [Flag::ReadOnly Flag::SortForScript],
             -2,
             1,
             -1,
@@ -168,7 +158,7 @@ dispatcher! {
         },
         sunionstore {
             cmd::set::sunionstore,
-            [""],
+            [Flag::Write Flag::DenyOom],
             -2,
             1,
             -1,
@@ -179,7 +169,7 @@ dispatcher! {
     metrics {
         metrics {
             cmd::metrics::metrics,
-            [""],
+            [Flag::ReadOnly Flag::Fast],
             -1,
             0,
             0,
@@ -190,7 +180,7 @@ dispatcher! {
     list {
         blpop {
             cmd::list::blpop,
-            [""],
+            [Flag::Write Flag::NoScript],
             -3,
             1,
             -2,
@@ -199,7 +189,7 @@ dispatcher! {
         },
         brpop {
             cmd::list::brpop,
-            [""],
+            [Flag::Write Flag::NoScript],
             -3,
             1,
             -2,
@@ -208,7 +198,7 @@ dispatcher! {
         },
         lindex {
             cmd::list::lindex,
-            [""],
+            [Flag::ReadOnly],
             3,
             1,
             1,
@@ -217,7 +207,7 @@ dispatcher! {
         },
         linsert {
             cmd::list::linsert,
-            [""],
+            [Flag::Write Flag::DenyOom],
             5,
             1,
             1,
@@ -226,7 +216,7 @@ dispatcher! {
         },
         llen {
             cmd::list::llen,
-            [""],
+            [Flag::ReadOnly Flag::Fast],
             2,
             1,
             1,
@@ -235,7 +225,7 @@ dispatcher! {
         },
         lmove {
             cmd::list::lmove,
-            [""],
+            [Flag::Write Flag::DenyOom],
             5,
             1,
             2,
@@ -244,7 +234,7 @@ dispatcher! {
         },
         lpop {
             cmd::list::lpop,
-            [""],
+            [Flag::Write Flag::DenyOom],
             -2,
             1,
             -2,
@@ -253,7 +243,7 @@ dispatcher! {
         },
         lpos {
             cmd::list::lpos,
-            [""],
+            [Flag::ReadOnly],
             -2,
             1,
             1,
@@ -262,7 +252,7 @@ dispatcher! {
         },
         lpush {
             cmd::list::lpush,
-            [""],
+            [Flag::Write Flag::DenyOom Flag::Fast],
             -3,
             1,
             1,
@@ -271,7 +261,7 @@ dispatcher! {
         },
         lpushx {
             cmd::list::lpush,
-            [""],
+            [Flag::Write Flag::DenyOom Flag::Fast],
             -3,
             1,
             1,
@@ -280,7 +270,7 @@ dispatcher! {
         },
         lrange {
             cmd::list::lrange,
-            [""],
+            [Flag::ReadOnly],
             4,
             1,
             1,
@@ -289,7 +279,7 @@ dispatcher! {
         },
         lrem {
             cmd::list::lrem,
-            [""],
+            [Flag::Write],
             4,
             1,
             1,
@@ -298,7 +288,7 @@ dispatcher! {
         },
         lset {
             cmd::list::lset,
-            [""],
+            [Flag::Write Flag::DenyOom],
             4,
             1,
             1,
@@ -307,7 +297,7 @@ dispatcher! {
         },
         ltrim {
             cmd::list::ltrim,
-            [""],
+            [Flag::Write],
             4,
             1,
             1,
@@ -316,7 +306,7 @@ dispatcher! {
         },
         rpop {
             cmd::list::rpop,
-            [""],
+            [Flag::Write Flag::Fast],
             -2,
             1,
             1,
@@ -325,7 +315,7 @@ dispatcher! {
         },
         rpoplpush {
             cmd::list::rpoplpush,
-            [""],
+            [Flag::Write Flag::DenyOom],
             3,
             1,
             2,
@@ -334,7 +324,7 @@ dispatcher! {
         },
         rpush {
             cmd::list::rpush,
-            [""],
+            [Flag::Write Flag::DenyOom Flag::Fast],
             -3,
             1,
             1,
@@ -343,7 +333,7 @@ dispatcher! {
         },
         rpushx {
             cmd::list::rpush,
-            [""],
+            [Flag::Write Flag::DenyOom Flag::Fast],
             -3,
             1,
             1,
@@ -354,7 +344,7 @@ dispatcher! {
     hash {
         hdel {
             cmd::hash::hdel,
-            [""],
+            [Flag::Write Flag::Fast],
             -2,
             1,
             1,
@@ -363,7 +353,7 @@ dispatcher! {
         },
         hexists {
             cmd::hash::hexists,
-            [""],
+            [Flag::ReadOnly Flag::Fast],
             3,
             1,
             1,
@@ -372,7 +362,7 @@ dispatcher! {
         },
         hget {
             cmd::hash::hget,
-            [""],
+            [Flag::ReadOnly Flag::Fast],
             3,
             1,
             1,
@@ -381,7 +371,7 @@ dispatcher! {
         },
         hgetall {
             cmd::hash::hgetall,
-            [""],
+            [Flag::ReadOnly Flag::Random],
             2,
             1,
             1,
@@ -390,7 +380,7 @@ dispatcher! {
         },
         hincrby {
             cmd::hash::hincrby::<i64>,
-            [""],
+            [Flag::Write Flag::DenyOom Flag::Fast],
             4,
             1,
             1,
@@ -399,7 +389,7 @@ dispatcher! {
         },
         hincrbyfloat {
             cmd::hash::hincrby::<f64>,
-            [""],
+            [Flag::Write Flag::DenyOom Flag::Fast],
             4,
             1,
             1,
@@ -408,7 +398,7 @@ dispatcher! {
         },
         hkeys {
             cmd::hash::hkeys,
-            [""],
+            [Flag::ReadOnly Flag::SortForScript],
             2,
             1,
             1,
@@ -417,7 +407,7 @@ dispatcher! {
         },
         hlen {
             cmd::hash::hlen,
-            [""],
+            [Flag::ReadOnly Flag::Fast],
             2,
             1,
             1,
@@ -426,7 +416,7 @@ dispatcher! {
         },
         hmget {
             cmd::hash::hmget,
-            [""],
+            [Flag::ReadOnly Flag::Fast],
             -3,
             1,
             1,
@@ -435,7 +425,7 @@ dispatcher! {
         },
         hmset {
             cmd::hash::hset,
-            [""],
+            [Flag::Write Flag::DenyOom Flag::Fast],
             -3,
             1,
             1,
@@ -444,7 +434,7 @@ dispatcher! {
         },
         hrandfield {
             cmd::hash::hrandfield,
-            [""],
+            [Flag::ReadOnly Flag::ReadOnly],
             -2,
             1,
             1,
@@ -453,7 +443,7 @@ dispatcher! {
         },
         hset {
             cmd::hash::hset,
-            [""],
+            [Flag::Write Flag::DenyOom Flag::Fast],
             -4,
             1,
             1,
@@ -462,7 +452,7 @@ dispatcher! {
         },
         hsetnx {
             cmd::hash::hsetnx,
-            [""],
+            [Flag::Write Flag::DenyOom Flag::Fast],
             4,
             1,
             1,
@@ -471,7 +461,7 @@ dispatcher! {
         },
         hstrlen {
             cmd::hash::hstrlen,
-            [""],
+            [Flag::ReadOnly Flag::Fast],
             3,
             1,
             1,
@@ -480,7 +470,7 @@ dispatcher! {
         },
         hvals {
             cmd::hash::hvals,
-            [""],
+            [Flag::ReadOnly Flag::SortForScript],
             2,
             1,
             1,
@@ -491,7 +481,7 @@ dispatcher! {
     keys {
         del {
             cmd::key::del,
-            ["random" "loading" "stale"],
+            [Flag::Write],
             -2,
             1,
             -1,
@@ -500,7 +490,7 @@ dispatcher! {
         },
         exists {
             cmd::key::exists,
-            ["read" "fast"],
+            [Flag::ReadOnly Flag::Fast],
             -2,
             1,
             -1,
@@ -509,7 +499,7 @@ dispatcher! {
         },
         expire {
             cmd::key::expire,
-            ["read" "write" "fast"],
+            [Flag::Write Flag::Fast],
             3,
             1,
             1,
@@ -518,7 +508,7 @@ dispatcher! {
         },
         expireat {
             cmd::key::expire_at,
-            ["read" "write" "fast"],
+            [Flag::Write Flag::Fast],
             3,
             1,
             1,
@@ -527,7 +517,7 @@ dispatcher! {
         },
         expiretime {
             cmd::key::expire_time,
-            ["read" "write" "fast"],
+            [Flag::Write Flag::Fast],
             2,
             1,
             1,
@@ -536,7 +526,7 @@ dispatcher! {
         },
         persist {
             cmd::key::persist,
-            ["write" "fast"],
+            [Flag::Write Flag::Fast],
             2,
             1,
             1,
@@ -545,7 +535,7 @@ dispatcher! {
         },
         pexpire {
             cmd::key::expire,
-            ["read" "write" "fast"],
+            [Flag::Write Flag::Fast],
             3,
             1,
             1,
@@ -554,7 +544,7 @@ dispatcher! {
         },
         pexpireat {
             cmd::key::expire_at,
-            ["read" "write" "fast"],
+            [Flag::Write Flag::Fast],
             3,
             1,
             1,
@@ -563,7 +553,7 @@ dispatcher! {
         },
         pexpiretime {
             cmd::key::expire_time,
-            ["read" "write" "fast"],
+            [Flag::Write Flag::Fast],
             2,
             1,
             1,
@@ -572,7 +562,7 @@ dispatcher! {
         },
         pttl {
             cmd::key::ttl,
-            ["read" "read"],
+            [Flag::ReadOnly Flag::Random Flag::Fast],
             2,
             1,
             1,
@@ -581,7 +571,7 @@ dispatcher! {
         },
         ttl {
             cmd::key::ttl,
-            ["read" "read"],
+            [Flag::ReadOnly Flag::Random Flag::Fast],
             2,
             1,
             1,
@@ -592,7 +582,7 @@ dispatcher! {
     string {
         decr {
             cmd::string::decr,
-            ["write" "denyoom" "fast"],
+            [Flag::Write Flag::DenyOom Flag::Fast],
             2,
             1,
             1,
@@ -601,7 +591,7 @@ dispatcher! {
         },
         decrby {
             cmd::string::decr_by,
-            ["write" "denyoom" "fast"],
+            [Flag::Write Flag::DenyOom Flag::Fast],
             3,
             1,
             1,
@@ -610,7 +600,7 @@ dispatcher! {
         },
         get {
             cmd::string::get,
-            ["random" "loading" "stale"],
+            [Flag::ReadOnly Flag::Fast],
             2,
             1,
             1,
@@ -619,7 +609,7 @@ dispatcher! {
         },
         getdel {
             cmd::string::getdel,
-            ["random" "loading" "stale"],
+            [Flag::Write Flag::Fast],
             2,
             1,
             1,
@@ -628,7 +618,7 @@ dispatcher! {
         },
         getset {
             cmd::string::getset,
-            ["random" "loading" "stale"],
+            [Flag::Write Flag::DenyOom Flag::Fast],
             3,
             1,
             1,
@@ -637,7 +627,7 @@ dispatcher! {
         },
         incr {
             cmd::string::incr,
-            ["write" "denyoom" "fast"],
+            [Flag::Write Flag::DenyOom Flag::Fast],
             2,
             1,
             1,
@@ -646,7 +636,7 @@ dispatcher! {
         },
         incrby {
             cmd::string::incr_by,
-            ["write" "denyoom" "fast"],
+            [Flag::Write Flag::DenyOom Flag::Fast],
             3,
             1,
             1,
@@ -655,7 +645,7 @@ dispatcher! {
         },
         incrbyfloat {
             cmd::string::incr_by_float,
-            ["write" "denyoom" "fast"],
+            [Flag::Write Flag::DenyOom Flag::Fast],
             3,
             1,
             1,
@@ -664,7 +654,7 @@ dispatcher! {
         },
         mget {
             cmd::string::mget,
-            ["random" "loading" "stale"],
+            [Flag::ReadOnly Flag::Fast],
             -2,
             1,
             -1,
@@ -673,7 +663,7 @@ dispatcher! {
         },
         set {
             cmd::string::set,
-            ["random" "loading" "stale"],
+            [Flag::Write Flag::DenyOom],
             -3,
             1,
             1,
@@ -682,7 +672,7 @@ dispatcher! {
         },
         setex {
             cmd::string::setex,
-            ["random" "loading" "stale"],
+            [Flag::Write Flag::DenyOom],
             4,
             1,
             1,
@@ -691,7 +681,7 @@ dispatcher! {
         },
         psetex {
             cmd::string::setex,
-            ["random" "loading" "stale"],
+            [Flag::Write Flag::DenyOom],
             4,
             1,
             1,
@@ -700,7 +690,7 @@ dispatcher! {
         },
         strlen {
             cmd::string::strlen,
-            ["random" "fast"],
+            [Flag::Random Flag::Fast],
             2,
             1,
             1,
@@ -711,7 +701,7 @@ dispatcher! {
     connection {
         client {
             cmd::client::client,
-            ["random" "loading" "stale"],
+            [Flag::Admin Flag::NoScript Flag::Random Flag::Loading Flag::Stale],
             -2,
             0,
             0,
@@ -720,7 +710,7 @@ dispatcher! {
         },
         echo {
             cmd::client::echo,
-            ["random" "loading" "stale"],
+            [Flag::Fast],
             2,
             0,
             0,
@@ -729,7 +719,7 @@ dispatcher! {
         },
         ping {
             cmd::client::ping,
-            ["random" "loading" "stale"],
+            [Flag::Stale Flag::Fast],
             -1,
             0,
             0,
@@ -738,7 +728,7 @@ dispatcher! {
         },
         reset {
             cmd::client::reset,
-            [""],
+            [Flag::NoScript Flag::Loading Flag::Stale Flag::Fast],
             1,
             0,
             0,
@@ -749,7 +739,7 @@ dispatcher! {
     transaction {
         discard {
             cmd::transaction::discard,
-            [""],
+            [Flag::NoScript Flag::Loading Flag::Stale Flag::Fast],
             1,
             0,
             0,
@@ -758,7 +748,7 @@ dispatcher! {
         },
         exec {
             cmd::transaction::exec,
-            [""],
+            [Flag::NoScript Flag::Loading Flag::Stale Flag::SkipMonitor Flag::SkipSlowlog],
             1,
             0,
             0,
@@ -767,7 +757,7 @@ dispatcher! {
         },
         multi {
             cmd::transaction::multi,
-            [""],
+            [Flag::NoScript Flag::Loading Flag::Stale Flag::Fast],
             1,
             0,
             0,
@@ -776,7 +766,7 @@ dispatcher! {
         },
         watch {
             cmd::transaction::watch,
-            [""],
+            [Flag::NoScript Flag::Loading Flag::Stale Flag::Fast],
             -2,
             1,
             -1,
@@ -785,7 +775,7 @@ dispatcher! {
         },
         unwatch {
             cmd::transaction::unwatch,
-            [""],
+            [Flag::NoScript Flag::Loading Flag::Stale Flag::Fast],
             1,
             0,
             0,
@@ -796,7 +786,7 @@ dispatcher! {
     pubsub {
         publish {
             cmd::pubsub::publish,
-            [""],
+            [Flag::PubSub Flag::Loading Flag::Stale Flag::Fast Flag::MayReplicate],
             3,
             0,
             0,
@@ -805,7 +795,7 @@ dispatcher! {
         },
         pubsub {
             cmd::pubsub::pubsub,
-            [""],
+            [Flag::PubSub Flag::Random Flag::Loading Flag::Stale],
             -2,
             0,
             0,
@@ -814,7 +804,7 @@ dispatcher! {
         },
         psubscribe {
             cmd::pubsub::subscribe,
-            [""],
+            [Flag::PubSub Flag::Random Flag::Loading Flag::Stale],
             -2,
             0,
             0,
@@ -823,7 +813,7 @@ dispatcher! {
         },
         punsubscribe {
             cmd::pubsub::punsubscribe,
-            [""],
+            [Flag::PubSub Flag::Random Flag::Loading Flag::Stale],
             -1,
             0,
             0,
@@ -832,7 +822,7 @@ dispatcher! {
         },
         subscribe {
             cmd::pubsub::subscribe,
-            [""],
+            [Flag::PubSub Flag::Random Flag::Loading Flag::Stale],
             -2,
             0,
             0,
@@ -841,7 +831,7 @@ dispatcher! {
         },
         unsubscribe {
             cmd::pubsub::unsubscribe,
-            [""],
+            [Flag::PubSub Flag::Random Flag::Loading Flag::Stale],
             -1,
             0,
             0,
@@ -850,9 +840,18 @@ dispatcher! {
         },
     },
     server {
+        command {
+            cmd::server::command,
+            [Flag::Random Flag::Loading Flag::Stale],
+            -1,
+            0,
+            0,
+            0,
+            true,
+        },
         time {
-            do_time,
-            ["random" "loading" "stale"],
+            cmd::server::time,
+            [Flag::Random Flag::Loading Flag::Stale Flag::Fast],
             1,
             0,
             0,

+ 3 - 0
src/error.rs

@@ -8,6 +8,8 @@ use crate::value::Value;
 pub enum Error {
     /// A command is not found
     CommandNotFound(String),
+    /// A sub-command is not found
+    SubCommandNotFound(String, String),
     /// Invalid number of arguments
     InvalidArgsCount(String),
     /// The glob-pattern is not valid
@@ -47,6 +49,7 @@ impl From<Error> for Value {
 
         let err_msg = match value {
             Error::CommandNotFound(x) => format!("unknown command `{}`", x),
+            Error::SubCommandNotFound(x, y) => format!("Unknown subcommand or wrong number of arguments for '{}'. Try {} HELP.", x, y),
             Error::InvalidArgsCount(x) => format!("wrong number of arguments for '{}' command", x),
             Error::InvalidPattern(x) => format!("'{}' is not a valid pattern", x),
             Error::Internal => "internal error".to_owned(),

+ 1 - 1
src/macros.rs

@@ -17,7 +17,7 @@ macro_rules! dispatcher {
         $($ns:ident {
             $($command:ident {
                 $handler:expr,
-                [$($tag:tt)+],
+                [$($tag:expr)+],
                 $min_args:expr,
                 $key_start:expr,
                 $key_stop:expr,