Skip to main content

ember_protocol/command/
attributes.rs

1//! Command attribute methods — name, write classification, ACL categories.
2//!
3//! Separated from the enum definition for readability. Each method is a
4//! simple match over all variants.
5
6use super::Command;
7
8impl Command {
9    /// Returns the lowercase command name as a static string.
10    ///
11    /// Used for metrics labels and slow log entries. Zero allocation —
12    /// returns a `&'static str` for every known variant.
13    ///
14    /// The match is explicit rather than derive-generated: a proc macro would
15    /// obscure the string mappings, which are the thing most worth seeing at
16    /// a glance when auditing command names.
17    pub fn command_name(&self) -> &'static str {
18        match self {
19            // connection
20            Command::Ping(_) => "ping",
21            Command::Echo(_) => "echo",
22            Command::Auth { .. } => "auth",
23            Command::Quit => "quit",
24            Command::Monitor => "monitor",
25            Command::ClientId => "client",
26            Command::ClientSetName { .. } => "client",
27            Command::ClientGetName => "client",
28            Command::ClientList => "client",
29
30            // strings
31            Command::Get { .. } => "get",
32            Command::Set { .. } => "set",
33            Command::MGet { .. } => "mget",
34            Command::MSet { .. } => "mset",
35            Command::Incr { .. } => "incr",
36            Command::Decr { .. } => "decr",
37            Command::IncrBy { .. } => "incrby",
38            Command::DecrBy { .. } => "decrby",
39            Command::IncrByFloat { .. } => "incrbyfloat",
40            Command::Append { .. } => "append",
41            Command::Strlen { .. } => "strlen",
42            Command::GetRange { .. } => "getrange",
43            Command::SetRange { .. } => "setrange",
44
45            // bitmaps
46            Command::GetBit { .. } => "getbit",
47            Command::SetBit { .. } => "setbit",
48            Command::BitCount { .. } => "bitcount",
49            Command::BitPos { .. } => "bitpos",
50            Command::BitOp { .. } => "bitop",
51
52            // key lifecycle
53            Command::Del { .. } => "del",
54            Command::Unlink { .. } => "unlink",
55            Command::Exists { .. } => "exists",
56            Command::Rename { .. } => "rename",
57            Command::Copy { .. } => "copy",
58            Command::ObjectEncoding { .. } => "object",
59            Command::ObjectRefcount { .. } => "object",
60            Command::Keys { .. } => "keys",
61            Command::Scan { .. } => "scan",
62            Command::SScan { .. } => "sscan",
63            Command::HScan { .. } => "hscan",
64            Command::ZScan { .. } => "zscan",
65            Command::Type { .. } => "type",
66            Command::Expire { .. } => "expire",
67            Command::Expireat { .. } => "expireat",
68            Command::Ttl { .. } => "ttl",
69            Command::Persist { .. } => "persist",
70            Command::Pttl { .. } => "pttl",
71            Command::Pexpire { .. } => "pexpire",
72            Command::Pexpireat { .. } => "pexpireat",
73            Command::Expiretime { .. } => "expiretime",
74            Command::Pexpiretime { .. } => "pexpiretime",
75
76            // server
77            Command::DbSize => "dbsize",
78            Command::Info { .. } => "info",
79            Command::Time => "time",
80            Command::LastSave => "lastsave",
81            Command::Role => "role",
82            Command::Wait { .. } => "wait",
83            Command::BgSave => "bgsave",
84            Command::BgRewriteAof => "bgrewriteaof",
85            Command::FlushDb { .. } => "flushdb",
86            Command::FlushAll { .. } => "flushall",
87            Command::MemoryUsage { .. } => "memory",
88            Command::ConfigGet { .. } => "config",
89            Command::ConfigSet { .. } => "config",
90            Command::ConfigRewrite => "config",
91            Command::Multi => "multi",
92            Command::Exec => "exec",
93            Command::Discard => "discard",
94            Command::Watch { .. } => "watch",
95            Command::Unwatch => "unwatch",
96
97            // list
98            Command::LPush { .. } => "lpush",
99            Command::RPush { .. } => "rpush",
100            Command::LPop { .. } => "lpop",
101            Command::RPop { .. } => "rpop",
102            Command::LRange { .. } => "lrange",
103            Command::LLen { .. } => "llen",
104            Command::BLPop { .. } => "blpop",
105            Command::BRPop { .. } => "brpop",
106            Command::LIndex { .. } => "lindex",
107            Command::LSet { .. } => "lset",
108            Command::LTrim { .. } => "ltrim",
109            Command::LInsert { .. } => "linsert",
110            Command::LRem { .. } => "lrem",
111            Command::LPos { .. } => "lpos",
112            Command::LMove { .. } => "lmove",
113            Command::GetDel { .. } => "getdel",
114            Command::GetEx { .. } => "getex",
115            Command::GetSet { .. } => "getset",
116            Command::MSetNx { .. } => "msetnx",
117
118            // sorted set
119            Command::ZAdd { .. } => "zadd",
120            Command::ZRem { .. } => "zrem",
121            Command::ZScore { .. } => "zscore",
122            Command::ZRank { .. } => "zrank",
123            Command::ZRevRank { .. } => "zrevrank",
124            Command::ZCard { .. } => "zcard",
125            Command::ZRange { .. } => "zrange",
126            Command::ZRevRange { .. } => "zrevrange",
127            Command::ZCount { .. } => "zcount",
128            Command::ZIncrBy { .. } => "zincrby",
129            Command::ZRangeByScore { .. } => "zrangebyscore",
130            Command::ZRevRangeByScore { .. } => "zrevrangebyscore",
131            Command::ZPopMin { .. } => "zpopmin",
132            Command::ZPopMax { .. } => "zpopmax",
133            Command::Lmpop { .. } => "lmpop",
134            Command::Zmpop { .. } => "zmpop",
135            Command::ZDiff { .. } => "zdiff",
136            Command::ZInter { .. } => "zinter",
137            Command::ZUnion { .. } => "zunion",
138            Command::ZDiffStore { .. } => "zdiffstore",
139            Command::ZInterStore { .. } => "zinterstore",
140            Command::ZUnionStore { .. } => "zunionstore",
141            Command::ZRandMember { .. } => "zrandmember",
142
143            // hash
144            Command::HSet { .. } => "hset",
145            Command::HGet { .. } => "hget",
146            Command::HGetAll { .. } => "hgetall",
147            Command::HDel { .. } => "hdel",
148            Command::HExists { .. } => "hexists",
149            Command::HLen { .. } => "hlen",
150            Command::HIncrBy { .. } => "hincrby",
151            Command::HKeys { .. } => "hkeys",
152            Command::HVals { .. } => "hvals",
153            Command::HMGet { .. } => "hmget",
154            Command::HRandField { .. } => "hrandfield",
155
156            // set
157            Command::SAdd { .. } => "sadd",
158            Command::SRem { .. } => "srem",
159            Command::SMembers { .. } => "smembers",
160            Command::SIsMember { .. } => "sismember",
161            Command::SCard { .. } => "scard",
162            Command::SUnion { .. } => "sunion",
163            Command::SInter { .. } => "sinter",
164            Command::SDiff { .. } => "sdiff",
165            Command::SUnionStore { .. } => "sunionstore",
166            Command::SInterStore { .. } => "sinterstore",
167            Command::SDiffStore { .. } => "sdiffstore",
168            Command::SRandMember { .. } => "srandmember",
169            Command::SPop { .. } => "spop",
170            Command::SMisMember { .. } => "smismember",
171            Command::SMove { .. } => "smove",
172            Command::SInterCard { .. } => "sintercard",
173
174            // cluster
175            Command::ClusterInfo => "cluster_info",
176            Command::ClusterNodes => "cluster_nodes",
177            Command::ClusterSlots => "cluster_slots",
178            Command::ClusterKeySlot { .. } => "cluster_keyslot",
179            Command::ClusterMyId => "cluster_myid",
180            Command::ClusterSetSlotImporting { .. } => "cluster_setslot",
181            Command::ClusterSetSlotMigrating { .. } => "cluster_setslot",
182            Command::ClusterSetSlotNode { .. } => "cluster_setslot",
183            Command::ClusterSetSlotStable { .. } => "cluster_setslot",
184            Command::ClusterMeet { .. } => "cluster_meet",
185            Command::ClusterAddSlots { .. } => "cluster_addslots",
186            Command::ClusterAddSlotsRange { .. } => "cluster_addslotsrange",
187            Command::ClusterDelSlots { .. } => "cluster_delslots",
188            Command::ClusterForget { .. } => "cluster_forget",
189            Command::ClusterReplicate { .. } => "cluster_replicate",
190            Command::ClusterFailover { .. } => "cluster_failover",
191            Command::ClusterCountKeysInSlot { .. } => "cluster_countkeysinslot",
192            Command::ClusterGetKeysInSlot { .. } => "cluster_getkeysinslot",
193            Command::Migrate { .. } => "migrate",
194            Command::Restore { .. } => "restore",
195            Command::Asking => "asking",
196
197            // slow log
198            Command::SlowLogGet { .. } => "slowlog",
199            Command::SlowLogLen => "slowlog",
200            Command::SlowLogReset => "slowlog",
201
202            // pub/sub
203            Command::Subscribe { .. } => "subscribe",
204            Command::Unsubscribe { .. } => "unsubscribe",
205            Command::PSubscribe { .. } => "psubscribe",
206            Command::PUnsubscribe { .. } => "punsubscribe",
207            Command::Publish { .. } => "publish",
208            Command::PubSubChannels { .. } => "pubsub",
209            Command::PubSubNumSub { .. } => "pubsub",
210            Command::PubSubNumPat => "pubsub",
211
212            // vector
213            Command::VAdd { .. } => "vadd",
214            Command::VAddBatch { .. } => "vadd_batch",
215            Command::VSim { .. } => "vsim",
216            Command::VRem { .. } => "vrem",
217            Command::VGet { .. } => "vget",
218            Command::VCard { .. } => "vcard",
219            Command::VDim { .. } => "vdim",
220            Command::VInfo { .. } => "vinfo",
221
222            // protobuf
223            Command::ProtoRegister { .. } => "proto.register",
224            Command::ProtoSet { .. } => "proto.set",
225            Command::ProtoGet { .. } => "proto.get",
226            Command::ProtoType { .. } => "proto.type",
227            Command::ProtoSchemas => "proto.schemas",
228            Command::ProtoDescribe { .. } => "proto.describe",
229            Command::ProtoGetField { .. } => "proto.getfield",
230            Command::ProtoSetField { .. } => "proto.setfield",
231            Command::ProtoDelField { .. } => "proto.delfield",
232
233            // acl
234            Command::AclWhoAmI => "acl",
235            Command::AclList => "acl",
236            Command::AclUsers => "acl",
237            Command::AclGetUser { .. } => "acl",
238            Command::AclDelUser { .. } => "acl",
239            Command::AclSetUser { .. } => "acl",
240            Command::AclCat { .. } => "acl",
241
242            Command::RandomKey => "randomkey",
243            Command::Touch { .. } => "touch",
244            Command::Sort { .. } => "sort",
245
246            Command::Command { .. } => "command",
247            Command::HIncrByFloat { .. } => "hincrbyfloat",
248
249            Command::Unknown(_) => "unknown",
250        }
251    }
252
253    /// Returns `true` if this command mutates state.
254    ///
255    /// Used by the replica write-rejection layer: any command matching this
256    /// predicate is redirected to the primary via MOVED rather than executed
257    /// locally on a read-only replica.
258    pub fn is_write(&self) -> bool {
259        matches!(
260            self,
261            // string mutations
262            Command::Set { .. }
263                | Command::MSet { .. }
264                | Command::MSetNx { .. }
265                | Command::GetSet { .. }
266                | Command::Append { .. }
267                | Command::Incr { .. }
268                | Command::Decr { .. }
269                | Command::IncrBy { .. }
270                | Command::DecrBy { .. }
271                | Command::IncrByFloat { .. }
272                | Command::SetBit { .. }
273                | Command::BitOp { .. }
274            // key lifecycle
275                | Command::Del { .. }
276                | Command::Unlink { .. }
277                | Command::Rename { .. }
278                | Command::Copy { .. }
279                | Command::Expire { .. }
280                | Command::Expireat { .. }
281                | Command::Pexpire { .. }
282                | Command::Pexpireat { .. }
283                | Command::Persist { .. }
284            // list
285                | Command::LPush { .. }
286                | Command::RPush { .. }
287                | Command::LPop { .. }
288                | Command::RPop { .. }
289                | Command::BLPop { .. }
290                | Command::BRPop { .. }
291                | Command::LSet { .. }
292                | Command::LTrim { .. }
293                | Command::LInsert { .. }
294                | Command::LRem { .. }
295                | Command::LMove { .. }
296            // string extras
297                | Command::GetDel { .. }
298                | Command::GetEx { .. }
299            // sorted set
300                | Command::ZAdd { .. }
301                | Command::ZRem { .. }
302                | Command::ZIncrBy { .. }
303                | Command::ZPopMin { .. }
304                | Command::ZPopMax { .. }
305                | Command::Lmpop { .. }
306                | Command::Zmpop { .. }
307            // hash
308                | Command::HSet { .. }
309                | Command::HDel { .. }
310                | Command::HIncrBy { .. }
311            // set
312                | Command::SAdd { .. }
313                | Command::SRem { .. }
314                | Command::SUnionStore { .. }
315                | Command::SInterStore { .. }
316                | Command::SDiffStore { .. }
317                | Command::SPop { .. }
318                | Command::SMove { .. }
319            // server / persistence
320                | Command::FlushDb { .. }
321                | Command::FlushAll { .. }
322                | Command::ConfigSet { .. }
323                | Command::Exec
324                | Command::BgRewriteAof
325                | Command::BgSave
326                | Command::Restore { .. }
327            // vector
328                | Command::VAdd { .. }
329                | Command::VAddBatch { .. }
330                | Command::VRem { .. }
331            // protobuf
332                | Command::ProtoRegister { .. }
333                | Command::ProtoSet { .. }
334                | Command::ProtoSetField { .. }
335                | Command::ProtoDelField { .. }
336            // acl mutations
337                | Command::AclSetUser { .. }
338                | Command::AclDelUser { .. }
339        ) || matches!(self, Command::Sort { store: Some(_), .. })
340    }
341
342    /// Returns the ACL category bitmask for this command.
343    ///
344    /// Each bit corresponds to a category (read, write, string, list, etc.)
345    /// as defined in `ember-server/src/acl.rs`. Used by the permission check
346    /// to determine whether a user has access to a command.
347    ///
348    /// The bit values match the constants in the acl module:
349    /// READ=1<<0, WRITE=1<<1, STRING=1<<2, LIST=1<<3, SET=1<<4,
350    /// SORTEDSET=1<<5, HASH=1<<6, KEYSPACE=1<<7, SERVER=1<<8,
351    /// CONNECTION=1<<9, TRANSACTION=1<<10, PUBSUB=1<<11, FAST=1<<12,
352    /// SLOW=1<<13, ADMIN=1<<14, DANGEROUS=1<<15, CLUSTER=1<<16
353    pub fn acl_categories(&self) -> u64 {
354        // bitmask constants (duplicated from acl.rs to avoid cross-crate dep)
355        const READ: u64 = 1 << 0;
356        const WRITE: u64 = 1 << 1;
357        const STRING: u64 = 1 << 2;
358        const LIST: u64 = 1 << 3;
359        const SET: u64 = 1 << 4;
360        const SORTEDSET: u64 = 1 << 5;
361        const HASH: u64 = 1 << 6;
362        const KEYSPACE: u64 = 1 << 7;
363        const SERVER: u64 = 1 << 8;
364        const CONNECTION: u64 = 1 << 9;
365        const TRANSACTION: u64 = 1 << 10;
366        const PUBSUB: u64 = 1 << 11;
367        const FAST: u64 = 1 << 12;
368        const SLOW: u64 = 1 << 13;
369        const ADMIN: u64 = 1 << 14;
370        const DANGEROUS: u64 = 1 << 15;
371        const CLUSTER: u64 = 1 << 16;
372
373        match self {
374            // connection
375            Command::Ping(_) | Command::Echo(_) => CONNECTION | FAST,
376            Command::Auth { .. } | Command::Quit => CONNECTION | FAST,
377            Command::ClientId | Command::ClientGetName | Command::ClientSetName { .. } => {
378                CONNECTION | SLOW
379            }
380            Command::ClientList => CONNECTION | ADMIN | SLOW,
381            Command::Monitor => CONNECTION | ADMIN | SLOW,
382
383            // string — reads
384            Command::Get { .. }
385            | Command::MGet { .. }
386            | Command::Strlen { .. }
387            | Command::GetRange { .. }
388            | Command::GetBit { .. }
389            | Command::BitCount { .. }
390            | Command::BitPos { .. } => READ | STRING | FAST,
391
392            // string — writes
393            Command::Set { .. }
394            | Command::MSet { .. }
395            | Command::MSetNx { .. }
396            | Command::GetSet { .. }
397            | Command::Append { .. }
398            | Command::SetRange { .. }
399            | Command::SetBit { .. }
400            | Command::BitOp { .. } => WRITE | STRING | SLOW,
401            Command::Incr { .. }
402            | Command::Decr { .. }
403            | Command::IncrBy { .. }
404            | Command::DecrBy { .. }
405            | Command::IncrByFloat { .. } => WRITE | STRING | FAST,
406
407            // keyspace — reads
408            Command::Exists { .. } | Command::Type { .. } | Command::Touch { .. } => {
409                READ | KEYSPACE | FAST
410            }
411            Command::RandomKey => READ | KEYSPACE | FAST,
412            Command::Keys { .. } => READ | KEYSPACE | DANGEROUS | SLOW,
413            Command::Sort { .. } => WRITE | SET | SORTEDSET | LIST | SLOW,
414            Command::Scan { .. }
415            | Command::SScan { .. }
416            | Command::HScan { .. }
417            | Command::ZScan { .. } => READ | KEYSPACE | SLOW,
418            Command::Ttl { .. }
419            | Command::Pttl { .. }
420            | Command::Expiretime { .. }
421            | Command::Pexpiretime { .. }
422            | Command::ObjectEncoding { .. }
423            | Command::ObjectRefcount { .. } => READ | KEYSPACE | FAST,
424
425            // keyspace — writes
426            Command::Del { .. } | Command::Unlink { .. } => WRITE | KEYSPACE | FAST,
427            Command::Rename { .. } | Command::Copy { .. } => WRITE | KEYSPACE | SLOW,
428            Command::Expire { .. }
429            | Command::Expireat { .. }
430            | Command::Pexpire { .. }
431            | Command::Pexpireat { .. }
432            | Command::Persist { .. } => WRITE | KEYSPACE | FAST,
433
434            // list — reads
435            Command::LRange { .. } | Command::LLen { .. } => READ | LIST | SLOW,
436            Command::LIndex { .. } => READ | LIST | FAST,
437            Command::LPos { .. } => READ | LIST | SLOW,
438
439            // list — writes
440            Command::LPush { .. }
441            | Command::RPush { .. }
442            | Command::LPop { .. }
443            | Command::RPop { .. }
444            | Command::LSet { .. } => WRITE | LIST | FAST,
445            Command::LTrim { .. } | Command::LInsert { .. } | Command::LRem { .. } => {
446                WRITE | LIST | SLOW
447            }
448            Command::BLPop { .. } | Command::BRPop { .. } => WRITE | LIST | SLOW,
449
450            // sorted set — reads
451            Command::ZScore { .. }
452            | Command::ZRank { .. }
453            | Command::ZRevRank { .. }
454            | Command::ZCard { .. }
455            | Command::ZCount { .. } => READ | SORTEDSET | FAST,
456            Command::ZRange { .. }
457            | Command::ZRevRange { .. }
458            | Command::ZRangeByScore { .. }
459            | Command::ZRevRangeByScore { .. } => READ | SORTEDSET | SLOW,
460
461            // sorted set — writes
462            Command::ZAdd { .. }
463            | Command::ZRem { .. }
464            | Command::ZIncrBy { .. }
465            | Command::ZPopMin { .. }
466            | Command::ZPopMax { .. }
467            | Command::Zmpop { .. } => WRITE | SORTEDSET | SLOW,
468
469            // list — multi-key pop (Redis 7.0+)
470            Command::Lmpop { .. } => WRITE | LIST | SLOW,
471
472            // sorted set — reads (Redis 6.2+)
473            Command::ZDiff { .. }
474            | Command::ZInter { .. }
475            | Command::ZUnion { .. }
476            | Command::ZRandMember { .. } => READ | SORTEDSET | SLOW,
477
478            // sorted set — store variants (Redis 6.2+)
479            Command::ZDiffStore { .. }
480            | Command::ZInterStore { .. }
481            | Command::ZUnionStore { .. } => WRITE | SORTEDSET | SLOW,
482
483            // string extras (Redis 6.2+)
484            Command::GetDel { .. } | Command::GetEx { .. } => WRITE | STRING | FAST,
485
486            // list extras (Redis 6.2+)
487            Command::LMove { .. } => WRITE | LIST | FAST,
488
489            // hash — reads
490            Command::HGet { .. } | Command::HExists { .. } | Command::HLen { .. } => {
491                READ | HASH | FAST
492            }
493            Command::HGetAll { .. }
494            | Command::HKeys { .. }
495            | Command::HVals { .. }
496            | Command::HMGet { .. }
497            | Command::HRandField { .. } => READ | HASH | SLOW,
498
499            // hash — writes
500            Command::HSet { .. } | Command::HDel { .. } | Command::HIncrBy { .. } => {
501                WRITE | HASH | FAST
502            }
503
504            // set — reads
505            Command::SIsMember { .. } | Command::SCard { .. } | Command::SMisMember { .. } => {
506                READ | SET | FAST
507            }
508            Command::SMembers { .. } | Command::SRandMember { .. } => READ | SET | SLOW,
509            Command::SUnion { .. } | Command::SInter { .. } | Command::SDiff { .. } => {
510                READ | SET | SLOW
511            }
512            Command::SInterCard { .. } => READ | SET | SLOW,
513
514            // set — writes
515            Command::SAdd { .. } | Command::SRem { .. } | Command::SPop { .. } => {
516                WRITE | SET | FAST
517            }
518            Command::SMove { .. } => WRITE | SET | FAST,
519            Command::SUnionStore { .. }
520            | Command::SInterStore { .. }
521            | Command::SDiffStore { .. } => WRITE | SET | SLOW,
522
523            // server
524            Command::DbSize => SERVER | KEYSPACE | READ | FAST,
525            Command::Info { .. } => SERVER | SLOW,
526            Command::Time | Command::LastSave | Command::Role | Command::Wait { .. } => {
527                SERVER | FAST
528            }
529            Command::BgSave | Command::BgRewriteAof => SERVER | ADMIN | SLOW,
530            Command::FlushDb { .. } => KEYSPACE | WRITE | ADMIN | DANGEROUS | SLOW,
531            Command::FlushAll { .. } => KEYSPACE | WRITE | ADMIN | DANGEROUS | SLOW,
532            Command::MemoryUsage { .. } => READ | KEYSPACE | SLOW,
533            Command::ConfigGet { .. } => SERVER | ADMIN | SLOW,
534            Command::ConfigSet { .. } | Command::ConfigRewrite => SERVER | ADMIN | SLOW,
535            Command::SlowLogGet { .. } | Command::SlowLogLen | Command::SlowLogReset => {
536                SERVER | ADMIN | SLOW
537            }
538
539            // transactions
540            Command::Multi | Command::Exec | Command::Discard => TRANSACTION | FAST,
541            Command::Watch { .. } | Command::Unwatch => TRANSACTION | FAST,
542
543            // cluster
544            Command::ClusterInfo
545            | Command::ClusterNodes
546            | Command::ClusterSlots
547            | Command::ClusterKeySlot { .. }
548            | Command::ClusterMyId => CLUSTER | SLOW,
549            Command::ClusterSetSlotImporting { .. }
550            | Command::ClusterSetSlotMigrating { .. }
551            | Command::ClusterSetSlotNode { .. }
552            | Command::ClusterSetSlotStable { .. }
553            | Command::ClusterMeet { .. }
554            | Command::ClusterAddSlots { .. }
555            | Command::ClusterAddSlotsRange { .. }
556            | Command::ClusterDelSlots { .. }
557            | Command::ClusterForget { .. }
558            | Command::ClusterReplicate { .. }
559            | Command::ClusterFailover { .. }
560            | Command::ClusterCountKeysInSlot { .. }
561            | Command::ClusterGetKeysInSlot { .. } => CLUSTER | ADMIN | SLOW,
562            Command::Migrate { .. } | Command::Restore { .. } => CLUSTER | KEYSPACE | WRITE | SLOW,
563            Command::Asking => CLUSTER | FAST,
564
565            // pub/sub
566            Command::Subscribe { .. }
567            | Command::Unsubscribe { .. }
568            | Command::PSubscribe { .. }
569            | Command::PUnsubscribe { .. } => PUBSUB | SLOW,
570            Command::Publish { .. } => PUBSUB | FAST,
571            Command::PubSubChannels { .. }
572            | Command::PubSubNumSub { .. }
573            | Command::PubSubNumPat => PUBSUB | SLOW,
574
575            // vector
576            Command::VAdd { .. } | Command::VAddBatch { .. } | Command::VRem { .. } => WRITE | SLOW,
577            Command::VSim { .. }
578            | Command::VGet { .. }
579            | Command::VCard { .. }
580            | Command::VDim { .. }
581            | Command::VInfo { .. } => READ | SLOW,
582
583            // protobuf
584            Command::ProtoRegister { .. } => WRITE | SERVER | SLOW,
585            Command::ProtoSet { .. }
586            | Command::ProtoSetField { .. }
587            | Command::ProtoDelField { .. } => WRITE | STRING | SLOW,
588            Command::ProtoGet { .. }
589            | Command::ProtoType { .. }
590            | Command::ProtoSchemas
591            | Command::ProtoDescribe { .. }
592            | Command::ProtoGetField { .. } => READ | STRING | SLOW,
593
594            // ACL commands
595            Command::AclWhoAmI => CONNECTION | FAST,
596            Command::AclList | Command::AclUsers | Command::AclGetUser { .. } => {
597                SERVER | ADMIN | SLOW
598            }
599            Command::AclSetUser { .. } | Command::AclDelUser { .. } => SERVER | ADMIN | SLOW,
600            Command::AclCat { .. } => SERVER | SLOW,
601
602            Command::Command { .. } => SERVER | SLOW,
603            Command::HIncrByFloat { .. } => WRITE | HASH | FAST,
604
605            Command::Unknown(_) => 0,
606        }
607    }
608
609    /// Returns the primary key for this command, if there is one.
610    ///
611    /// Used to calculate the hash slot for MOVED redirects on replicas.
612    /// For multi-key commands, returns the first key.
613    pub fn primary_key(&self) -> Option<&str> {
614        match self {
615            Command::Get { key }
616            | Command::Set { key, .. }
617            | Command::Incr { key }
618            | Command::Decr { key }
619            | Command::IncrBy { key, .. }
620            | Command::DecrBy { key, .. }
621            | Command::IncrByFloat { key, .. }
622            | Command::Append { key, .. }
623            | Command::Strlen { key }
624            | Command::GetRange { key, .. }
625            | Command::SetRange { key, .. }
626            | Command::Persist { key }
627            | Command::Expire { key, .. }
628            | Command::Expireat { key, .. }
629            | Command::Pexpire { key, .. }
630            | Command::Pexpireat { key, .. }
631            | Command::Ttl { key }
632            | Command::Pttl { key }
633            | Command::Expiretime { key }
634            | Command::Pexpiretime { key }
635            | Command::Type { key }
636            | Command::Rename { key, .. }
637            | Command::ObjectEncoding { key }
638            | Command::ObjectRefcount { key }
639            | Command::GetSet { key, .. }
640            | Command::LPush { key, .. }
641            | Command::RPush { key, .. }
642            | Command::LPop { key, .. }
643            | Command::RPop { key, .. }
644            | Command::LRange { key, .. }
645            | Command::LLen { key }
646            | Command::LIndex { key, .. }
647            | Command::LSet { key, .. }
648            | Command::LTrim { key, .. }
649            | Command::LInsert { key, .. }
650            | Command::LRem { key, .. }
651            | Command::LPos { key, .. }
652            | Command::ZAdd { key, .. }
653            | Command::ZRem { key, .. }
654            | Command::ZScore { key, .. }
655            | Command::ZRank { key, .. }
656            | Command::ZRange { key, .. }
657            | Command::ZCard { key }
658            | Command::HSet { key, .. }
659            | Command::HGet { key, .. }
660            | Command::HGetAll { key }
661            | Command::HDel { key, .. }
662            | Command::HExists { key, .. }
663            | Command::HLen { key }
664            | Command::HIncrBy { key, .. }
665            | Command::HKeys { key }
666            | Command::HVals { key }
667            | Command::HMGet { key, .. }
668            | Command::HRandField { key, .. }
669            | Command::ZRandMember { key, .. }
670            | Command::SAdd { key, .. }
671            | Command::SRem { key, .. }
672            | Command::SMembers { key }
673            | Command::SIsMember { key, .. }
674            | Command::SCard { key }
675            | Command::SScan { key, .. }
676            | Command::SRandMember { key, .. }
677            | Command::SPop { key, .. }
678            | Command::SMisMember { key, .. }
679            | Command::HScan { key, .. }
680            | Command::ZScan { key, .. }
681            | Command::VAdd { key, .. }
682            | Command::VAddBatch { key, .. }
683            | Command::VSim { key, .. }
684            | Command::VRem { key, .. }
685            | Command::VGet { key, .. }
686            | Command::VCard { key }
687            | Command::VDim { key }
688            | Command::VInfo { key }
689            | Command::ProtoSet { key, .. }
690            | Command::ProtoGet { key }
691            | Command::ProtoType { key }
692            | Command::ProtoGetField { key, .. }
693            | Command::ProtoSetField { key, .. }
694            | Command::ProtoDelField { key, .. }
695            | Command::Restore { key, .. }
696            | Command::Sort { key, .. }
697            | Command::GetDel { key }
698            | Command::GetEx { key, .. }
699            | Command::MemoryUsage { key } => Some(key),
700            Command::LMove { source, .. } => Some(source),
701            Command::Copy { source, .. } => Some(source),
702            Command::SMove { source, .. } => Some(source),
703            Command::Del { keys }
704            | Command::Unlink { keys }
705            | Command::Exists { keys }
706            | Command::Touch { keys }
707            | Command::MGet { keys }
708            | Command::BLPop { keys, .. }
709            | Command::BRPop { keys, .. }
710            | Command::SUnion { keys }
711            | Command::SInter { keys }
712            | Command::SDiff { keys }
713            | Command::SInterCard { keys, .. }
714            | Command::ZDiff { keys, .. }
715            | Command::ZInter { keys, .. }
716            | Command::ZUnion { keys, .. }
717            | Command::Lmpop { keys, .. }
718            | Command::Zmpop { keys, .. } => keys.first().map(String::as_str),
719            Command::SUnionStore { dest, .. }
720            | Command::SInterStore { dest, .. }
721            | Command::SDiffStore { dest, .. }
722            | Command::ZUnionStore { dest, .. }
723            | Command::ZInterStore { dest, .. }
724            | Command::ZDiffStore { dest, .. } => Some(dest),
725            Command::HIncrByFloat { key, .. } => Some(key),
726            Command::MSet { pairs } | Command::MSetNx { pairs } => {
727                pairs.first().map(|(k, _)| k.as_str())
728            }
729            _ => None,
730        }
731    }
732}