#[repr(u16)]
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
Hash,
num_enum::TryFromPrimitive,
num_enum::IntoPrimitive,
strum::EnumString,
strum::Display,
strum::IntoStaticStr,
strum::AsRefStr,
)]
#[strum(ascii_case_insensitive, serialize_all = "SCREAMING_SNAKE_CASE")]
pub enum RespCommand {
None = 0,
Append = 1,
Bitfield = 2,
Bzmpop = 3,
Bzpopmax = 4,
Bzpopmin = 5,
Decr = 6,
Decrby = 7,
Del = 8,
Delifexpim = 9,
Delifgreater = 10,
Expire = 11,
Expireat = 12,
Flushall = 13,
Flushdb = 14,
Geoadd = 15,
Georadius = 16,
Georadiusbymember = 17,
Geosearchstore = 18,
Getdel = 19,
Getex = 20,
Getset = 21,
Hcollect = 22,
Hdel = 23,
Hexpire = 24,
Hpexpire = 25,
Hexpireat = 26,
Hpexpireat = 27,
Hpersist = 28,
Hincrby = 29,
Hincrbyfloat = 30,
Hmset = 31,
Hset = 32,
Hsetnx = 33,
Incr = 34,
Incrby = 35,
Incrbyfloat = 36,
Linsert = 37,
Lmove = 38,
Lmpop = 39,
Lpop = 40,
Lpush = 41,
Lpushx = 42,
Lrem = 43,
Lset = 44,
Ltrim = 45,
Blpop = 46,
Brpop = 47,
Blmove = 48,
Brpoplpush = 49,
Blmpop = 50,
Migrate = 51,
Mset = 52,
Msetnx = 53,
Persist = 54,
Pexpire = 55,
Pexpireat = 56,
Pfadd = 57,
Pfmerge = 58,
Psetex = 59,
Rename = 60,
Ricreate = 61,
Ridel = 62,
Riset = 65,
Restore = 66,
Renamenx = 67,
Rpop = 68,
Rpoplpush = 69,
Rpush = 70,
Rpushx = 71,
Sadd = 72,
Sdiffstore = 73,
Set = 74,
Setbit = 75,
Setex = 76,
Setexnx = 77,
Setexxx = 78,
Setnx = 79,
Setifmatch = 80,
Setifgreater = 81,
Setwithetag = 82,
Setkeepttl = 83,
Setkeepttlxx = 84,
Setrange = 85,
Sinterstore = 86,
Smove = 87,
Spop = 88,
Srem = 89,
Sunionstore = 90,
Swapdb = 91,
Unlink = 92,
Vadd = 93,
Vrem = 94,
Vsetattr = 95,
Zadd = 96,
Zcollect = 97,
Zdiffstore = 98,
Zexpire = 99,
Zpexpire = 100,
Zexpireat = 101,
Zpexpireat = 102,
Zpersist = 103,
Zincrby = 104,
Zmpop = 105,
Zinterstore = 106,
Zpopmax = 107,
Zpopmin = 108,
Zrangestore = 109,
Zrem = 110,
Zremrangebylex = 111,
Zremrangebyrank = 112,
Zremrangebyscore = 113,
Zunionstore = 114,
Bitop = 115,
BitopAnd = 116,
BitopOr = 117,
BitopXor = 118,
BitopNot = 119,
BitopDiff = 120,
Bitcount = 121,
BitfieldRo = 122,
Bitpos = 123,
Coscan = 124,
Dbsize = 125,
Dump = 126,
Exists = 127,
Expiretime = 128,
Geodist = 129,
Geohash = 130,
Geopos = 131,
GeoradiusRo = 132,
GeoradiusbymemberRo = 133,
Geosearch = 134,
Get = 135,
Getbit = 136,
Getifnotmatch = 137,
Getrange = 138,
Getwithetag = 139,
Hexists = 140,
Hget = 141,
Hgetall = 142,
Hkeys = 143,
Hlen = 144,
Hmget = 145,
Hrandfield = 146,
Hscan = 147,
Hstrlen = 148,
Hvals = 149,
Keys = 150,
Lcs = 151,
Httl = 152,
Hpttl = 153,
Hexpiretime = 154,
Hpexpiretime = 155,
Lindex = 156,
Llen = 157,
Lpos = 158,
Lrange = 159,
MemoryUsage = 160,
Mget = 161,
ObjectEncoding = 162,
ObjectFreq = 163,
ObjectIdletime = 164,
ObjectRefcount = 165,
Pexpiretime = 166,
Pfcount = 167,
Pttl = 168,
Scan = 169,
Scard = 170,
Sdiff = 171,
Sinter = 172,
Sintercard = 173,
Sismember = 174,
Smembers = 175,
Smismember = 176,
Spublish = 177,
Srandmember = 178,
Sscan = 179,
Ssubscribe = 180,
Strlen = 181,
Substr = 182,
Sunion = 183,
Ttl = 184,
Type = 185,
Vcard = 186,
Vdim = 187,
Vemb = 188,
Vgetattr = 189,
Vinfo = 190,
Vismember = 191,
Vlinks = 192,
Vrandmember = 193,
Vsim = 194,
Watch = 195,
Watchms = 196,
Watchos = 197,
Zcard = 198,
Zcount = 199,
Zdiff = 200,
Zinter = 201,
Zintercard = 202,
Zlexcount = 203,
Zmscore = 204,
Zrandmember = 205,
Zrange = 206,
Zrangebylex = 207,
Zrangebyscore = 208,
Zrank = 209,
Zrevrange = 210,
Zrevrangebylex = 211,
Zrevrangebyscore = 212,
Zrevrank = 213,
Zttl = 214,
Zpttl = 215,
Zexpiretime = 216,
Zpexpiretime = 217,
Zscan = 218,
Zscore = 219,
Zunion = 220,
Riconfig = 221,
Ricount = 222,
Riexists = 223,
Riget = 224,
Rimetrics = 225,
Rirange = 226,
Riscan = 227,
Eval = 228,
Evalsha = 229,
Async = 230,
Ping = 231,
Pubsub = 232,
PubsubChannels = 233,
PubsubNumpat = 234,
PubsubNumsub = 235,
Publish = 236,
Subscribe = 237,
Psubscribe = 238,
Unsubscribe = 239,
Punsubscribe = 240,
Asking = 241,
Select = 242,
Echo = 243,
Client = 244,
ClientId = 245,
ClientInfo = 246,
ClientList = 247,
ClientKill = 248,
ClientGetname = 249,
ClientSetname = 250,
ClientSetinfo = 251,
ClientUnblock = 252,
Monitor = 253,
Multi = 257,
Exec = 258,
Discard = 259,
Unwatch = 260,
Runtxp = 261,
Readonly = 262,
Readwrite = 263,
Replicaof = 264,
Secondaryof = 265,
Info = 266,
Time = 267,
Role = 268,
Save = 269,
Expdelscan = 270,
Lastsave = 271,
Bgsave = 272,
Commitaof = 273,
Failover = 274,
Customtxn = 275,
Customrawstringcmd = 276,
Customobjcmd = 277,
Customprocedure = 278,
Script = 279,
ScriptExists = 280,
ScriptFlush = 281,
ScriptLoad = 282,
Acl = 283,
AclCat = 284,
AclDeluser = 285,
AclGenpass = 286,
AclGetuser = 287,
AclList = 288,
AclLoad = 289,
AclSave = 290,
AclSetuser = 291,
AclUsers = 292,
AclWhoami = 293,
Command = 294,
CommandCount = 295,
CommandDocs = 296,
CommandInfo = 297,
CommandGetkeys = 298,
CommandGetkeysandflags = 299,
Memory = 300,
Object = 301,
ObjectHelp = 302,
Config = 303,
ConfigGet = 304,
ConfigRewrite = 305,
ConfigSet = 306,
Debug = 307,
Latency = 308,
LatencyHelp = 309,
LatencyHistogram = 310,
LatencyReset = 311,
Slowlog = 312,
SlowlogHelp = 313,
SlowlogLen = 314,
SlowlogGet = 315,
SlowlogReset = 316,
Cluster = 317,
ClusterAddslots = 318,
ClusterAddslotsrange = 319,
ClusterAdvanceTime = 320,
ClusterAppendlog = 321,
ClusterAttachSync = 322,
ClusterBanlist = 323,
ClusterBeginReplicaRecover = 324,
ClusterBumpepoch = 325,
ClusterCountkeysinslot = 326,
ClusterDelkeysinslot = 327,
ClusterDelkeysinslotrange = 328,
ClusterDelslots = 329,
ClusterDelslotsrange = 330,
ClusterEndpoint = 331,
ClusterFailover = 332,
ClusterFailreplicationoffset = 333,
ClusterFailstopwrites = 334,
ClusterFlushall = 335,
ClusterFlushallNs = 336,
ClusterForget = 337,
ClusterGetkeysinslot = 338,
ClusterGossip = 339,
ClusterHelp = 340,
ClusterInfo = 341,
ClusterInitiateReplicaSync = 342,
ClusterKeyslot = 343,
ClusterMeet = 344,
ClusterMigrate = 345,
ClusterMlogKeyTime = 346,
ClusterMtasks = 347,
ClusterMyid = 348,
ClusterMyparentid = 349,
ClusterNodes = 350,
ClusterPublish = 351,
ClusterSpublish = 352,
ClusterReplicas = 353,
ClusterReplicate = 354,
ClusterReserve = 355,
ClusterReset = 356,
ClusterSendCkptFileSegment = 357,
ClusterSendCkptMetadata = 358,
ClusterSetconfigepoch = 359,
ClusterSetslot = 360,
ClusterSetslotsrange = 361,
ClusterShards = 362,
ClusterSlots = 363,
ClusterSlotstate = 364,
ClusterSnapshotData = 365,
ClusterSync = 366,
Auth = 367,
Hello = 368,
Quit = 369,
Sunsubscribe = 370,
Invalid = 65535,
}
pub const FIRST_DATA_COMMAND: RespCommand = RespCommand::Append;
pub const LAST_DATA_COMMAND: RespCommand = RespCommand::Evalsha;
const FIRST_READ_COMMAND: RespCommand = RespCommand::Bitcount;
const LAST_READ_COMMAND: RespCommand = RespCommand::Riscan;
pub const LAST_VALID_COMMAND: RespCommand = RespCommand::Sunsubscribe;
#[inline]
pub const fn is_read_only(cmd: RespCommand) -> bool {
let v = (cmd as u16).wrapping_sub(FIRST_READ_COMMAND as u16);
v <= (LAST_READ_COMMAND as u16 - FIRST_READ_COMMAND as u16)
}
#[inline]
pub const fn is_data_command(cmd: RespCommand) -> bool {
match cmd {
RespCommand::Migrate
| RespCommand::Dbsize
| RespCommand::MemoryUsage
| RespCommand::Flushall
| RespCommand::Flushdb
| RespCommand::Keys
| RespCommand::Scan
| RespCommand::Swapdb => false,
_ => {
let v = (cmd as u16).wrapping_sub(FIRST_DATA_COMMAND as u16);
v <= (LAST_DATA_COMMAND as u16 - FIRST_DATA_COMMAND as u16)
}
}
}
#[inline]
pub const fn is_cluster_sub_command(cmd: RespCommand) -> bool {
let v = (cmd as u16).wrapping_sub(RespCommand::ClusterAddslots as u16);
v <= (RespCommand::ClusterSync as u16 - RespCommand::ClusterAddslots as u16)
}
#[inline]
pub const fn is_write_only(cmd: RespCommand) -> bool {
let v = (cmd as u16).wrapping_sub(FIRST_DATA_COMMAND as u16);
v <= (RespCommand::BitopDiff as u16 - FIRST_DATA_COMMAND as u16)
}
#[inline]
pub const fn one_if_write(cmd: RespCommand) -> u64 {
if is_write_only(cmd) { 1 } else { 0 }
}
#[inline]
pub const fn one_if_read(cmd: RespCommand) -> u64 {
if is_read_only(cmd) { 1 } else { 0 }
}
#[inline]
pub const fn is_vector_set_command(cmd: RespCommand) -> bool {
matches!(
cmd,
RespCommand::Vadd
| RespCommand::Vcard
| RespCommand::Vdim
| RespCommand::Vemb
| RespCommand::Vgetattr
| RespCommand::Vinfo
| RespCommand::Vismember
| RespCommand::Vlinks
| RespCommand::Vrandmember
| RespCommand::Vrem
| RespCommand::Vsetattr
| RespCommand::Vsim
)
}
#[inline]
pub const fn is_legal_on_vector_set(cmd: RespCommand) -> bool {
matches!(
cmd,
RespCommand::Del
| RespCommand::Unlink
| RespCommand::Type
| RespCommand::Debug
| RespCommand::Rename
| RespCommand::Renamenx
) || is_vector_set_command(cmd)
}
#[inline]
pub const fn is_vector_gate_exempt(cmd: RespCommand) -> bool {
matches!(
cmd,
RespCommand::Set
| RespCommand::Setex
| RespCommand::Psetex
| RespCommand::Setnx
| RespCommand::Setexnx
| RespCommand::Setexxx
| RespCommand::Setkeepttl
| RespCommand::Setkeepttlxx
| RespCommand::Mset
| RespCommand::Bitop
| RespCommand::BitopAnd
| RespCommand::BitopOr
| RespCommand::BitopXor
| RespCommand::BitopNot
| RespCommand::BitopDiff
| RespCommand::Msetnx
| RespCommand::Restore
| RespCommand::Exists
| RespCommand::Expire
| RespCommand::Pexpire
| RespCommand::Expireat
| RespCommand::Pexpireat
| RespCommand::Persist
| RespCommand::Ttl
| RespCommand::Pttl
| RespCommand::Expiretime
| RespCommand::Pexpiretime
| RespCommand::MemoryUsage
| RespCommand::Dump
| RespCommand::ObjectEncoding
| RespCommand::ObjectFreq
| RespCommand::ObjectIdletime
| RespCommand::ObjectRefcount
| RespCommand::Mget
| RespCommand::Ricreate
| RespCommand::Riset
| RespCommand::Riget
| RespCommand::Ridel
| RespCommand::Riscan
| RespCommand::Rirange
| RespCommand::Riexists
| RespCommand::Riconfig
| RespCommand::Ricount
| RespCommand::Rimetrics
| RespCommand::Watch
| RespCommand::Watchms
| RespCommand::Watchos
| RespCommand::Spublish
| RespCommand::Ssubscribe
| RespCommand::Coscan
| RespCommand::Eval
| RespCommand::Evalsha
)
}
#[inline]
pub const fn vector_gate_scan_all_keys(cmd: RespCommand) -> bool {
matches!(
cmd,
RespCommand::Pfmerge
| RespCommand::Sdiff
| RespCommand::Sinter
| RespCommand::Sunion
| RespCommand::Sdiffstore
| RespCommand::Sinterstore
| RespCommand::Sunionstore
)
}
impl RespCommand {
#[inline]
pub const fn is_legal_on_vector_set(self) -> bool {
is_legal_on_vector_set(self)
}
#[inline]
pub const fn is_vector_set_command(self) -> bool {
is_vector_set_command(self)
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use super::*;
#[test]
fn vector_write_gate_applicability_is_exhaustive() {
for raw in 0u16..=(LAST_VALID_COMMAND as u16) {
let Ok(cmd) = RespCommand::try_from(raw) else {
continue;
};
assert!(
!(is_legal_on_vector_set(cmd) && is_vector_gate_exempt(cmd)),
"白名单与豁免集交叠: {cmd:?}"
);
if vector_gate_scan_all_keys(cmd) {
assert!(
is_data_command(cmd) && !is_legal_on_vector_set(cmd) && !is_vector_gate_exempt(cmd),
"多键清单命令未被值域门接管: {cmd:?}"
);
}
}
for cmd in [
RespCommand::Set,
RespCommand::Mset,
RespCommand::Msetnx,
RespCommand::Restore,
RespCommand::Exists,
RespCommand::Mget,
RespCommand::Ttl,
RespCommand::MemoryUsage,
] {
assert!(
is_vector_gate_exempt(cmd),
"{cmd:?} 应豁免值域 WRONGTYPE 门"
);
}
for cmd in [
RespCommand::Bitop,
RespCommand::BitopAnd,
RespCommand::BitopOr,
RespCommand::BitopXor,
RespCommand::BitopNot,
RespCommand::BitopDiff,
] {
assert!(
is_vector_gate_exempt(cmd) && !vector_gate_scan_all_keys(cmd),
"{cmd:?} 应由位点内源/目分流裁决而非派发门"
);
}
for cmd in [
RespCommand::Hset,
RespCommand::Lpush,
RespCommand::Sadd,
RespCommand::Zadd,
RespCommand::Pfadd,
RespCommand::Geoadd,
RespCommand::Setbit,
RespCommand::Bitfield,
RespCommand::Get,
RespCommand::Incr,
RespCommand::Getset,
] {
assert!(
is_data_command(cmd) && !is_legal_on_vector_set(cmd) && !is_vector_gate_exempt(cmd),
"{cmd:?} 应受 args[0] 值域 WRONGTYPE 门约束"
);
}
}
#[test]
fn test_resp_command_strum_roundtrip() {
assert_eq!(RespCommand::Append.as_ref(), "APPEND");
assert_eq!(RespCommand::BitopAnd.as_ref(), "BITOP_AND");
assert_eq!(RespCommand::AclCat.as_ref(), "ACL_CAT");
assert_eq!(
RespCommand::ClusterSendCkptFileSegment.as_ref(),
"CLUSTER_SEND_CKPT_FILE_SEGMENT"
);
assert_eq!(RespCommand::from_str("APPEND"), Ok(RespCommand::Append));
assert_eq!(RespCommand::from_str("append"), Ok(RespCommand::Append));
assert_eq!(RespCommand::from_str("ApPeNd"), Ok(RespCommand::Append));
assert_eq!(RespCommand::from_str("acl_cat"), Ok(RespCommand::AclCat));
assert_eq!(
RespCommand::from_str("bitop_and"),
Ok(RespCommand::BitopAnd)
);
assert_eq!(
RespCommand::from_str("cluster_send_ckpt_file_segment"),
Ok(RespCommand::ClusterSendCkptFileSegment)
);
assert!(RespCommand::from_str("NON_EXISTENT_COMMAND").is_err());
let static_str: &'static str = RespCommand::BitopAnd.into();
assert_eq!(static_str, "BITOP_AND");
assert_eq!(RespCommand::BitopAnd.to_string(), "BITOP_AND");
}
#[test]
fn test_range_index_command_value_intervals() {
let ricount = RespCommand::Ricount as u16;
assert!((FIRST_READ_COMMAND as u16..=LAST_READ_COMMAND as u16).contains(&ricount));
assert!((FIRST_DATA_COMMAND as u16..=LAST_DATA_COMMAND as u16).contains(&ricount));
assert!(is_read_only(RespCommand::Ricount));
assert!(is_data_command(RespCommand::Ricount));
assert!(!is_write_only(RespCommand::Ricount));
}
}