Skip to main content

ember_protocol/
command.rs

1//! Command parsing from RESP3 frames.
2//!
3//! Converts a parsed [`Frame`] (expected to be an array) into a typed
4//! [`Command`] enum. This keeps protocol-level concerns separate from
5//! the engine that actually executes commands.
6
7use bytes::Bytes;
8
9use crate::error::ProtocolError;
10use crate::types::Frame;
11
12/// Expiration option for the SET command.
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum SetExpire {
15    /// EX seconds — expire after N seconds.
16    Ex(u64),
17    /// PX milliseconds — expire after N milliseconds.
18    Px(u64),
19}
20
21/// A parsed client command, ready for execution.
22#[derive(Debug, Clone, PartialEq)]
23pub enum Command {
24    /// PING with an optional message. Returns PONG or echoes the message.
25    Ping(Option<Bytes>),
26
27    /// ECHO `message`. Returns the message back to the client.
28    Echo(Bytes),
29
30    /// GET `key`. Returns the value or nil.
31    Get { key: String },
32
33    /// SET `key` `value` \[EX seconds | PX milliseconds\] \[NX | XX\].
34    Set {
35        key: String,
36        value: Bytes,
37        expire: Option<SetExpire>,
38        /// Only set the key if it does not already exist.
39        nx: bool,
40        /// Only set the key if it already exists.
41        xx: bool,
42    },
43
44    /// INCR `key`. Increments the integer value of a key by 1.
45    Incr { key: String },
46
47    /// DECR `key`. Decrements the integer value of a key by 1.
48    Decr { key: String },
49
50    /// DEL `key` \[key ...\]. Returns the number of keys removed.
51    Del { keys: Vec<String> },
52
53    /// EXISTS `key` \[key ...\]. Returns the number of keys that exist.
54    Exists { keys: Vec<String> },
55
56    /// MGET `key` \[key ...\]. Returns the values for all specified keys.
57    MGet { keys: Vec<String> },
58
59    /// MSET `key` `value` \[key value ...\]. Sets multiple key-value pairs.
60    MSet { pairs: Vec<(String, Bytes)> },
61
62    /// EXPIRE `key` `seconds`. Sets a TTL on an existing key.
63    Expire { key: String, seconds: u64 },
64
65    /// TTL `key`. Returns remaining time-to-live in seconds.
66    Ttl { key: String },
67
68    /// PERSIST `key`. Removes the expiration from a key.
69    Persist { key: String },
70
71    /// PTTL `key`. Returns remaining time-to-live in milliseconds.
72    Pttl { key: String },
73
74    /// PEXPIRE `key` `milliseconds`. Sets a TTL in milliseconds on an existing key.
75    Pexpire { key: String, milliseconds: u64 },
76
77    /// DBSIZE. Returns the number of keys in the database.
78    DbSize,
79
80    /// INFO \[section\]. Returns server info. Currently only supports "keyspace".
81    Info { section: Option<String> },
82
83    /// BGSAVE. Triggers a background snapshot.
84    BgSave,
85
86    /// BGREWRITEAOF. Triggers an AOF rewrite (snapshot + truncate).
87    BgRewriteAof,
88
89    /// FLUSHDB. Removes all keys from the database.
90    FlushDb,
91
92    /// SCAN `cursor` \[MATCH pattern\] \[COUNT count\]. Iterates keys.
93    Scan {
94        cursor: u64,
95        pattern: Option<String>,
96        count: Option<usize>,
97    },
98
99    /// LPUSH `key` `value` \[value ...\]. Pushes values to the head of a list.
100    LPush { key: String, values: Vec<Bytes> },
101
102    /// RPUSH `key` `value` \[value ...\]. Pushes values to the tail of a list.
103    RPush { key: String, values: Vec<Bytes> },
104
105    /// LPOP `key`. Pops a value from the head of a list.
106    LPop { key: String },
107
108    /// RPOP `key`. Pops a value from the tail of a list.
109    RPop { key: String },
110
111    /// LRANGE `key` `start` `stop`. Returns a range of elements by index.
112    LRange { key: String, start: i64, stop: i64 },
113
114    /// LLEN `key`. Returns the length of a list.
115    LLen { key: String },
116
117    /// TYPE `key`. Returns the type of the value stored at key.
118    Type { key: String },
119
120    /// ZADD `key` \[NX|XX\] \[GT|LT\] \[CH\] `score` `member` \[score member ...\].
121    ZAdd {
122        key: String,
123        flags: ZAddFlags,
124        members: Vec<(f64, String)>,
125    },
126
127    /// ZREM `key` `member` \[member ...\]. Removes members from a sorted set.
128    ZRem { key: String, members: Vec<String> },
129
130    /// ZSCORE `key` `member`. Returns the score of a member.
131    ZScore { key: String, member: String },
132
133    /// ZRANK `key` `member`. Returns the rank of a member (0-based).
134    ZRank { key: String, member: String },
135
136    /// ZCARD `key`. Returns the cardinality (number of members) of a sorted set.
137    ZCard { key: String },
138
139    /// ZRANGE `key` `start` `stop` \[WITHSCORES\]. Returns a range by rank.
140    ZRange {
141        key: String,
142        start: i64,
143        stop: i64,
144        with_scores: bool,
145    },
146
147    /// HSET `key` `field` `value` \[field value ...\]. Sets field-value pairs in a hash.
148    HSet {
149        key: String,
150        fields: Vec<(String, Bytes)>,
151    },
152
153    /// HGET `key` `field`. Gets a field's value from a hash.
154    HGet { key: String, field: String },
155
156    /// HGETALL `key`. Gets all field-value pairs from a hash.
157    HGetAll { key: String },
158
159    /// HDEL `key` `field` \[field ...\]. Deletes fields from a hash.
160    HDel { key: String, fields: Vec<String> },
161
162    /// HEXISTS `key` `field`. Checks if a field exists in a hash.
163    HExists { key: String, field: String },
164
165    /// HLEN `key`. Returns the number of fields in a hash.
166    HLen { key: String },
167
168    /// HINCRBY `key` `field` `increment`. Increments a hash field's integer value.
169    HIncrBy {
170        key: String,
171        field: String,
172        delta: i64,
173    },
174
175    /// HKEYS `key`. Returns all field names in a hash.
176    HKeys { key: String },
177
178    /// HVALS `key`. Returns all values in a hash.
179    HVals { key: String },
180
181    /// HMGET `key` `field` \[field ...\]. Gets multiple field values from a hash.
182    HMGet { key: String, fields: Vec<String> },
183
184    /// SADD `key` `member` \[member ...\]. Adds members to a set.
185    SAdd { key: String, members: Vec<String> },
186
187    /// SREM `key` `member` \[member ...\]. Removes members from a set.
188    SRem { key: String, members: Vec<String> },
189
190    /// SMEMBERS `key`. Returns all members of a set.
191    SMembers { key: String },
192
193    /// SISMEMBER `key` `member`. Checks if a member exists in a set.
194    SIsMember { key: String, member: String },
195
196    /// SCARD `key`. Returns the cardinality (number of members) of a set.
197    SCard { key: String },
198
199    // --- cluster commands ---
200    /// CLUSTER INFO. Returns cluster state and configuration information.
201    ClusterInfo,
202
203    /// CLUSTER NODES. Returns the list of cluster nodes.
204    ClusterNodes,
205
206    /// CLUSTER SLOTS. Returns the slot distribution across nodes.
207    ClusterSlots,
208
209    /// CLUSTER KEYSLOT `key`. Returns the hash slot for a key.
210    ClusterKeySlot { key: String },
211
212    /// CLUSTER MYID. Returns the node's ID.
213    ClusterMyId,
214
215    /// CLUSTER SETSLOT `slot` IMPORTING `node-id`. Mark slot as importing from node.
216    ClusterSetSlotImporting { slot: u16, node_id: String },
217
218    /// CLUSTER SETSLOT `slot` MIGRATING `node-id`. Mark slot as migrating to node.
219    ClusterSetSlotMigrating { slot: u16, node_id: String },
220
221    /// CLUSTER SETSLOT `slot` NODE `node-id`. Assign slot to node.
222    ClusterSetSlotNode { slot: u16, node_id: String },
223
224    /// CLUSTER SETSLOT `slot` STABLE. Clear importing/migrating state.
225    ClusterSetSlotStable { slot: u16 },
226
227    /// CLUSTER MEET `ip` `port`. Add a node to the cluster.
228    ClusterMeet { ip: String, port: u16 },
229
230    /// CLUSTER ADDSLOTS `slot` \[slot...\]. Assign slots to the local node.
231    ClusterAddSlots { slots: Vec<u16> },
232
233    /// CLUSTER DELSLOTS `slot` \[slot...\]. Remove slots from the local node.
234    ClusterDelSlots { slots: Vec<u16> },
235
236    /// CLUSTER FORGET `node-id`. Remove a node from the cluster.
237    ClusterForget { node_id: String },
238
239    /// CLUSTER REPLICATE `node-id`. Make this node a replica of another.
240    ClusterReplicate { node_id: String },
241
242    /// CLUSTER FAILOVER [FORCE|TAKEOVER]. Trigger a manual failover.
243    ClusterFailover { force: bool, takeover: bool },
244
245    /// CLUSTER COUNTKEYSINSLOT `slot`. Return the number of keys in a slot.
246    ClusterCountKeysInSlot { slot: u16 },
247
248    /// CLUSTER GETKEYSINSLOT `slot` `count`. Return keys in a slot.
249    ClusterGetKeysInSlot { slot: u16, count: u32 },
250
251    /// MIGRATE `host` `port` `key` `db` `timeout` \[COPY\] \[REPLACE\] \[KEYS key...\].
252    /// Migrate a key to another node.
253    Migrate {
254        host: String,
255        port: u16,
256        key: String,
257        db: u32,
258        timeout_ms: u64,
259        copy: bool,
260        replace: bool,
261    },
262
263    /// ASKING. Signals that the next command is for a migrating slot.
264    Asking,
265
266    /// SLOWLOG GET [count]. Returns recent slow log entries.
267    SlowLogGet { count: Option<usize> },
268
269    /// SLOWLOG LEN. Returns the number of entries in the slow log.
270    SlowLogLen,
271
272    /// SLOWLOG RESET. Clears the slow log.
273    SlowLogReset,
274
275    /// A command we don't recognize (yet).
276    Unknown(String),
277}
278
279/// Flags for the ZADD command.
280#[derive(Debug, Clone, Default, PartialEq)]
281pub struct ZAddFlags {
282    /// Only add new members, don't update existing scores.
283    pub nx: bool,
284    /// Only update existing members, don't add new ones.
285    pub xx: bool,
286    /// Only update when new score > current score.
287    pub gt: bool,
288    /// Only update when new score < current score.
289    pub lt: bool,
290    /// Return count of changed members (added + updated) instead of just added.
291    pub ch: bool,
292}
293
294impl Eq for ZAddFlags {}
295
296impl Command {
297    /// Returns the lowercase command name as a static string.
298    ///
299    /// Used for metrics labels and slow log entries. Zero allocation —
300    /// returns a `&'static str` for every known variant.
301    pub fn command_name(&self) -> &'static str {
302        match self {
303            Command::Ping(_) => "ping",
304            Command::Echo(_) => "echo",
305            Command::Get { .. } => "get",
306            Command::Set { .. } => "set",
307            Command::Incr { .. } => "incr",
308            Command::Decr { .. } => "decr",
309            Command::Del { .. } => "del",
310            Command::Exists { .. } => "exists",
311            Command::MGet { .. } => "mget",
312            Command::MSet { .. } => "mset",
313            Command::Expire { .. } => "expire",
314            Command::Ttl { .. } => "ttl",
315            Command::Persist { .. } => "persist",
316            Command::Pttl { .. } => "pttl",
317            Command::Pexpire { .. } => "pexpire",
318            Command::DbSize => "dbsize",
319            Command::Info { .. } => "info",
320            Command::BgSave => "bgsave",
321            Command::BgRewriteAof => "bgrewriteaof",
322            Command::FlushDb => "flushdb",
323            Command::Scan { .. } => "scan",
324            Command::LPush { .. } => "lpush",
325            Command::RPush { .. } => "rpush",
326            Command::LPop { .. } => "lpop",
327            Command::RPop { .. } => "rpop",
328            Command::LRange { .. } => "lrange",
329            Command::LLen { .. } => "llen",
330            Command::Type { .. } => "type",
331            Command::ZAdd { .. } => "zadd",
332            Command::ZRem { .. } => "zrem",
333            Command::ZScore { .. } => "zscore",
334            Command::ZRank { .. } => "zrank",
335            Command::ZCard { .. } => "zcard",
336            Command::ZRange { .. } => "zrange",
337            Command::HSet { .. } => "hset",
338            Command::HGet { .. } => "hget",
339            Command::HGetAll { .. } => "hgetall",
340            Command::HDel { .. } => "hdel",
341            Command::HExists { .. } => "hexists",
342            Command::HLen { .. } => "hlen",
343            Command::HIncrBy { .. } => "hincrby",
344            Command::HKeys { .. } => "hkeys",
345            Command::HVals { .. } => "hvals",
346            Command::HMGet { .. } => "hmget",
347            Command::SAdd { .. } => "sadd",
348            Command::SRem { .. } => "srem",
349            Command::SMembers { .. } => "smembers",
350            Command::SIsMember { .. } => "sismember",
351            Command::SCard { .. } => "scard",
352            Command::ClusterInfo => "cluster_info",
353            Command::ClusterNodes => "cluster_nodes",
354            Command::ClusterSlots => "cluster_slots",
355            Command::ClusterKeySlot { .. } => "cluster_keyslot",
356            Command::ClusterMyId => "cluster_myid",
357            Command::ClusterSetSlotImporting { .. } => "cluster_setslot",
358            Command::ClusterSetSlotMigrating { .. } => "cluster_setslot",
359            Command::ClusterSetSlotNode { .. } => "cluster_setslot",
360            Command::ClusterSetSlotStable { .. } => "cluster_setslot",
361            Command::ClusterMeet { .. } => "cluster_meet",
362            Command::ClusterAddSlots { .. } => "cluster_addslots",
363            Command::ClusterDelSlots { .. } => "cluster_delslots",
364            Command::ClusterForget { .. } => "cluster_forget",
365            Command::ClusterReplicate { .. } => "cluster_replicate",
366            Command::ClusterFailover { .. } => "cluster_failover",
367            Command::ClusterCountKeysInSlot { .. } => "cluster_countkeysinslot",
368            Command::ClusterGetKeysInSlot { .. } => "cluster_getkeysinslot",
369            Command::Migrate { .. } => "migrate",
370            Command::Asking => "asking",
371            Command::SlowLogGet { .. } => "slowlog",
372            Command::SlowLogLen => "slowlog",
373            Command::SlowLogReset => "slowlog",
374            Command::Unknown(_) => "unknown",
375        }
376    }
377
378    /// Parses a [`Frame`] into a [`Command`].
379    ///
380    /// Expects an array frame where the first element is the command name
381    /// (as a bulk or simple string) and the rest are arguments.
382    pub fn from_frame(frame: Frame) -> Result<Command, ProtocolError> {
383        let frames = match frame {
384            Frame::Array(frames) => frames,
385            _ => {
386                return Err(ProtocolError::InvalidCommandFrame(
387                    "expected array frame".into(),
388                ));
389            }
390        };
391
392        if frames.is_empty() {
393            return Err(ProtocolError::InvalidCommandFrame(
394                "empty command array".into(),
395            ));
396        }
397
398        let name = extract_string(&frames[0])?;
399        let name_upper = name.to_ascii_uppercase();
400
401        match name_upper.as_str() {
402            "PING" => parse_ping(&frames[1..]),
403            "ECHO" => parse_echo(&frames[1..]),
404            "GET" => parse_get(&frames[1..]),
405            "SET" => parse_set(&frames[1..]),
406            "INCR" => parse_incr(&frames[1..]),
407            "DECR" => parse_decr(&frames[1..]),
408            "DEL" => parse_del(&frames[1..]),
409            "EXISTS" => parse_exists(&frames[1..]),
410            "MGET" => parse_mget(&frames[1..]),
411            "MSET" => parse_mset(&frames[1..]),
412            "EXPIRE" => parse_expire(&frames[1..]),
413            "TTL" => parse_ttl(&frames[1..]),
414            "PERSIST" => parse_persist(&frames[1..]),
415            "PTTL" => parse_pttl(&frames[1..]),
416            "PEXPIRE" => parse_pexpire(&frames[1..]),
417            "DBSIZE" => parse_dbsize(&frames[1..]),
418            "INFO" => parse_info(&frames[1..]),
419            "BGSAVE" => parse_bgsave(&frames[1..]),
420            "BGREWRITEAOF" => parse_bgrewriteaof(&frames[1..]),
421            "FLUSHDB" => parse_flushdb(&frames[1..]),
422            "SCAN" => parse_scan(&frames[1..]),
423            "LPUSH" => parse_lpush(&frames[1..]),
424            "RPUSH" => parse_rpush(&frames[1..]),
425            "LPOP" => parse_lpop(&frames[1..]),
426            "RPOP" => parse_rpop(&frames[1..]),
427            "LRANGE" => parse_lrange(&frames[1..]),
428            "LLEN" => parse_llen(&frames[1..]),
429            "TYPE" => parse_type(&frames[1..]),
430            "ZADD" => parse_zadd(&frames[1..]),
431            "ZREM" => parse_zrem(&frames[1..]),
432            "ZSCORE" => parse_zscore(&frames[1..]),
433            "ZRANK" => parse_zrank(&frames[1..]),
434            "ZCARD" => parse_zcard(&frames[1..]),
435            "ZRANGE" => parse_zrange(&frames[1..]),
436            "HSET" => parse_hset(&frames[1..]),
437            "HGET" => parse_hget(&frames[1..]),
438            "HGETALL" => parse_hgetall(&frames[1..]),
439            "HDEL" => parse_hdel(&frames[1..]),
440            "HEXISTS" => parse_hexists(&frames[1..]),
441            "HLEN" => parse_hlen(&frames[1..]),
442            "HINCRBY" => parse_hincrby(&frames[1..]),
443            "HKEYS" => parse_hkeys(&frames[1..]),
444            "HVALS" => parse_hvals(&frames[1..]),
445            "HMGET" => parse_hmget(&frames[1..]),
446            "SADD" => parse_sadd(&frames[1..]),
447            "SREM" => parse_srem(&frames[1..]),
448            "SMEMBERS" => parse_smembers(&frames[1..]),
449            "SISMEMBER" => parse_sismember(&frames[1..]),
450            "SCARD" => parse_scard(&frames[1..]),
451            "CLUSTER" => parse_cluster(&frames[1..]),
452            "ASKING" => parse_asking(&frames[1..]),
453            "MIGRATE" => parse_migrate(&frames[1..]),
454            "SLOWLOG" => parse_slowlog(&frames[1..]),
455            _ => Ok(Command::Unknown(name)),
456        }
457    }
458}
459
460/// Extracts a UTF-8 string from a Bulk or Simple frame.
461fn extract_string(frame: &Frame) -> Result<String, ProtocolError> {
462    match frame {
463        Frame::Bulk(data) => String::from_utf8(data.to_vec()).map_err(|_| {
464            ProtocolError::InvalidCommandFrame("command name is not valid utf-8".into())
465        }),
466        Frame::Simple(s) => Ok(s.clone()),
467        _ => Err(ProtocolError::InvalidCommandFrame(
468            "expected bulk or simple string for command name".into(),
469        )),
470    }
471}
472
473/// Extracts raw bytes from a Bulk or Simple frame.
474fn extract_bytes(frame: &Frame) -> Result<Bytes, ProtocolError> {
475    match frame {
476        Frame::Bulk(data) => Ok(data.clone()),
477        Frame::Simple(s) => Ok(Bytes::from(s.clone().into_bytes())),
478        _ => Err(ProtocolError::InvalidCommandFrame(
479            "expected bulk or simple string argument".into(),
480        )),
481    }
482}
483
484/// Parses a string argument as a positive u64.
485fn parse_u64(frame: &Frame, cmd: &str) -> Result<u64, ProtocolError> {
486    let s = extract_string(frame)?;
487    s.parse::<u64>().map_err(|_| {
488        ProtocolError::InvalidCommandFrame(format!("value is not a valid integer for '{cmd}'"))
489    })
490}
491
492fn parse_ping(args: &[Frame]) -> Result<Command, ProtocolError> {
493    match args.len() {
494        0 => Ok(Command::Ping(None)),
495        1 => {
496            let msg = extract_bytes(&args[0])?;
497            Ok(Command::Ping(Some(msg)))
498        }
499        _ => Err(ProtocolError::WrongArity("PING".into())),
500    }
501}
502
503fn parse_echo(args: &[Frame]) -> Result<Command, ProtocolError> {
504    if args.len() != 1 {
505        return Err(ProtocolError::WrongArity("ECHO".into()));
506    }
507    let msg = extract_bytes(&args[0])?;
508    Ok(Command::Echo(msg))
509}
510
511fn parse_get(args: &[Frame]) -> Result<Command, ProtocolError> {
512    if args.len() != 1 {
513        return Err(ProtocolError::WrongArity("GET".into()));
514    }
515    let key = extract_string(&args[0])?;
516    Ok(Command::Get { key })
517}
518
519fn parse_set(args: &[Frame]) -> Result<Command, ProtocolError> {
520    if args.len() < 2 {
521        return Err(ProtocolError::WrongArity("SET".into()));
522    }
523
524    let key = extract_string(&args[0])?;
525    let value = extract_bytes(&args[1])?;
526
527    let mut expire = None;
528    let mut nx = false;
529    let mut xx = false;
530    let mut idx = 2;
531
532    while idx < args.len() {
533        let flag = extract_string(&args[idx])?.to_ascii_uppercase();
534        match flag.as_str() {
535            "NX" => {
536                nx = true;
537                idx += 1;
538            }
539            "XX" => {
540                xx = true;
541                idx += 1;
542            }
543            "EX" => {
544                idx += 1;
545                if idx >= args.len() {
546                    return Err(ProtocolError::WrongArity("SET".into()));
547                }
548                let amount = parse_u64(&args[idx], "SET")?;
549                if amount == 0 {
550                    return Err(ProtocolError::InvalidCommandFrame(
551                        "invalid expire time in 'SET' command".into(),
552                    ));
553                }
554                expire = Some(SetExpire::Ex(amount));
555                idx += 1;
556            }
557            "PX" => {
558                idx += 1;
559                if idx >= args.len() {
560                    return Err(ProtocolError::WrongArity("SET".into()));
561                }
562                let amount = parse_u64(&args[idx], "SET")?;
563                if amount == 0 {
564                    return Err(ProtocolError::InvalidCommandFrame(
565                        "invalid expire time in 'SET' command".into(),
566                    ));
567                }
568                expire = Some(SetExpire::Px(amount));
569                idx += 1;
570            }
571            _ => {
572                return Err(ProtocolError::InvalidCommandFrame(format!(
573                    "unsupported SET option '{flag}'"
574                )));
575            }
576        }
577    }
578
579    if nx && xx {
580        return Err(ProtocolError::InvalidCommandFrame(
581            "XX and NX options at the same time are not compatible".into(),
582        ));
583    }
584
585    Ok(Command::Set {
586        key,
587        value,
588        expire,
589        nx,
590        xx,
591    })
592}
593
594fn parse_incr(args: &[Frame]) -> Result<Command, ProtocolError> {
595    if args.len() != 1 {
596        return Err(ProtocolError::WrongArity("INCR".into()));
597    }
598    let key = extract_string(&args[0])?;
599    Ok(Command::Incr { key })
600}
601
602fn parse_decr(args: &[Frame]) -> Result<Command, ProtocolError> {
603    if args.len() != 1 {
604        return Err(ProtocolError::WrongArity("DECR".into()));
605    }
606    let key = extract_string(&args[0])?;
607    Ok(Command::Decr { key })
608}
609
610fn parse_del(args: &[Frame]) -> Result<Command, ProtocolError> {
611    if args.is_empty() {
612        return Err(ProtocolError::WrongArity("DEL".into()));
613    }
614    let keys = args
615        .iter()
616        .map(extract_string)
617        .collect::<Result<Vec<_>, _>>()?;
618    Ok(Command::Del { keys })
619}
620
621fn parse_exists(args: &[Frame]) -> Result<Command, ProtocolError> {
622    if args.is_empty() {
623        return Err(ProtocolError::WrongArity("EXISTS".into()));
624    }
625    let keys = args
626        .iter()
627        .map(extract_string)
628        .collect::<Result<Vec<_>, _>>()?;
629    Ok(Command::Exists { keys })
630}
631
632fn parse_mget(args: &[Frame]) -> Result<Command, ProtocolError> {
633    if args.is_empty() {
634        return Err(ProtocolError::WrongArity("MGET".into()));
635    }
636    let keys = args
637        .iter()
638        .map(extract_string)
639        .collect::<Result<Vec<_>, _>>()?;
640    Ok(Command::MGet { keys })
641}
642
643fn parse_mset(args: &[Frame]) -> Result<Command, ProtocolError> {
644    if args.is_empty() || !args.len().is_multiple_of(2) {
645        return Err(ProtocolError::WrongArity("MSET".into()));
646    }
647    let mut pairs = Vec::with_capacity(args.len() / 2);
648    for chunk in args.chunks(2) {
649        let key = extract_string(&chunk[0])?;
650        let value = extract_bytes(&chunk[1])?;
651        pairs.push((key, value));
652    }
653    Ok(Command::MSet { pairs })
654}
655
656fn parse_expire(args: &[Frame]) -> Result<Command, ProtocolError> {
657    if args.len() != 2 {
658        return Err(ProtocolError::WrongArity("EXPIRE".into()));
659    }
660    let key = extract_string(&args[0])?;
661    let seconds = parse_u64(&args[1], "EXPIRE")?;
662
663    if seconds == 0 {
664        return Err(ProtocolError::InvalidCommandFrame(
665            "invalid expire time in 'EXPIRE' command".into(),
666        ));
667    }
668
669    Ok(Command::Expire { key, seconds })
670}
671
672fn parse_ttl(args: &[Frame]) -> Result<Command, ProtocolError> {
673    if args.len() != 1 {
674        return Err(ProtocolError::WrongArity("TTL".into()));
675    }
676    let key = extract_string(&args[0])?;
677    Ok(Command::Ttl { key })
678}
679
680fn parse_persist(args: &[Frame]) -> Result<Command, ProtocolError> {
681    if args.len() != 1 {
682        return Err(ProtocolError::WrongArity("PERSIST".into()));
683    }
684    let key = extract_string(&args[0])?;
685    Ok(Command::Persist { key })
686}
687
688fn parse_pttl(args: &[Frame]) -> Result<Command, ProtocolError> {
689    if args.len() != 1 {
690        return Err(ProtocolError::WrongArity("PTTL".into()));
691    }
692    let key = extract_string(&args[0])?;
693    Ok(Command::Pttl { key })
694}
695
696fn parse_pexpire(args: &[Frame]) -> Result<Command, ProtocolError> {
697    if args.len() != 2 {
698        return Err(ProtocolError::WrongArity("PEXPIRE".into()));
699    }
700    let key = extract_string(&args[0])?;
701    let milliseconds = parse_u64(&args[1], "PEXPIRE")?;
702
703    if milliseconds == 0 {
704        return Err(ProtocolError::InvalidCommandFrame(
705            "invalid expire time in 'PEXPIRE' command".into(),
706        ));
707    }
708
709    Ok(Command::Pexpire { key, milliseconds })
710}
711
712fn parse_dbsize(args: &[Frame]) -> Result<Command, ProtocolError> {
713    if !args.is_empty() {
714        return Err(ProtocolError::WrongArity("DBSIZE".into()));
715    }
716    Ok(Command::DbSize)
717}
718
719fn parse_info(args: &[Frame]) -> Result<Command, ProtocolError> {
720    match args.len() {
721        0 => Ok(Command::Info { section: None }),
722        1 => {
723            let section = extract_string(&args[0])?;
724            Ok(Command::Info {
725                section: Some(section),
726            })
727        }
728        _ => Err(ProtocolError::WrongArity("INFO".into())),
729    }
730}
731
732fn parse_bgsave(args: &[Frame]) -> Result<Command, ProtocolError> {
733    if !args.is_empty() {
734        return Err(ProtocolError::WrongArity("BGSAVE".into()));
735    }
736    Ok(Command::BgSave)
737}
738
739fn parse_bgrewriteaof(args: &[Frame]) -> Result<Command, ProtocolError> {
740    if !args.is_empty() {
741        return Err(ProtocolError::WrongArity("BGREWRITEAOF".into()));
742    }
743    Ok(Command::BgRewriteAof)
744}
745
746fn parse_flushdb(args: &[Frame]) -> Result<Command, ProtocolError> {
747    if !args.is_empty() {
748        return Err(ProtocolError::WrongArity("FLUSHDB".into()));
749    }
750    Ok(Command::FlushDb)
751}
752
753fn parse_scan(args: &[Frame]) -> Result<Command, ProtocolError> {
754    if args.is_empty() {
755        return Err(ProtocolError::WrongArity("SCAN".into()));
756    }
757
758    let cursor = parse_u64(&args[0], "SCAN")?;
759    let mut pattern = None;
760    let mut count = None;
761    let mut idx = 1;
762
763    while idx < args.len() {
764        let flag = extract_string(&args[idx])?.to_ascii_uppercase();
765        match flag.as_str() {
766            "MATCH" => {
767                idx += 1;
768                if idx >= args.len() {
769                    return Err(ProtocolError::WrongArity("SCAN".into()));
770                }
771                pattern = Some(extract_string(&args[idx])?);
772                idx += 1;
773            }
774            "COUNT" => {
775                idx += 1;
776                if idx >= args.len() {
777                    return Err(ProtocolError::WrongArity("SCAN".into()));
778                }
779                let n = parse_u64(&args[idx], "SCAN")?;
780                count = Some(n as usize);
781                idx += 1;
782            }
783            _ => {
784                return Err(ProtocolError::InvalidCommandFrame(format!(
785                    "unsupported SCAN option '{flag}'"
786                )));
787            }
788        }
789    }
790
791    Ok(Command::Scan {
792        cursor,
793        pattern,
794        count,
795    })
796}
797
798/// Parses a string argument as an i64. Used by LRANGE for start/stop indices.
799fn parse_i64(frame: &Frame, cmd: &str) -> Result<i64, ProtocolError> {
800    let s = extract_string(frame)?;
801    s.parse::<i64>().map_err(|_| {
802        ProtocolError::InvalidCommandFrame(format!("value is not a valid integer for '{cmd}'"))
803    })
804}
805
806fn parse_lpush(args: &[Frame]) -> Result<Command, ProtocolError> {
807    if args.len() < 2 {
808        return Err(ProtocolError::WrongArity("LPUSH".into()));
809    }
810    let key = extract_string(&args[0])?;
811    let values = args[1..]
812        .iter()
813        .map(extract_bytes)
814        .collect::<Result<Vec<_>, _>>()?;
815    Ok(Command::LPush { key, values })
816}
817
818fn parse_rpush(args: &[Frame]) -> Result<Command, ProtocolError> {
819    if args.len() < 2 {
820        return Err(ProtocolError::WrongArity("RPUSH".into()));
821    }
822    let key = extract_string(&args[0])?;
823    let values = args[1..]
824        .iter()
825        .map(extract_bytes)
826        .collect::<Result<Vec<_>, _>>()?;
827    Ok(Command::RPush { key, values })
828}
829
830fn parse_lpop(args: &[Frame]) -> Result<Command, ProtocolError> {
831    if args.len() != 1 {
832        return Err(ProtocolError::WrongArity("LPOP".into()));
833    }
834    let key = extract_string(&args[0])?;
835    Ok(Command::LPop { key })
836}
837
838fn parse_rpop(args: &[Frame]) -> Result<Command, ProtocolError> {
839    if args.len() != 1 {
840        return Err(ProtocolError::WrongArity("RPOP".into()));
841    }
842    let key = extract_string(&args[0])?;
843    Ok(Command::RPop { key })
844}
845
846fn parse_lrange(args: &[Frame]) -> Result<Command, ProtocolError> {
847    if args.len() != 3 {
848        return Err(ProtocolError::WrongArity("LRANGE".into()));
849    }
850    let key = extract_string(&args[0])?;
851    let start = parse_i64(&args[1], "LRANGE")?;
852    let stop = parse_i64(&args[2], "LRANGE")?;
853    Ok(Command::LRange { key, start, stop })
854}
855
856fn parse_llen(args: &[Frame]) -> Result<Command, ProtocolError> {
857    if args.len() != 1 {
858        return Err(ProtocolError::WrongArity("LLEN".into()));
859    }
860    let key = extract_string(&args[0])?;
861    Ok(Command::LLen { key })
862}
863
864fn parse_type(args: &[Frame]) -> Result<Command, ProtocolError> {
865    if args.len() != 1 {
866        return Err(ProtocolError::WrongArity("TYPE".into()));
867    }
868    let key = extract_string(&args[0])?;
869    Ok(Command::Type { key })
870}
871
872/// Parses a string argument as an f64 score.
873fn parse_f64(frame: &Frame, cmd: &str) -> Result<f64, ProtocolError> {
874    let s = extract_string(frame)?;
875    let v = s.parse::<f64>().map_err(|_| {
876        ProtocolError::InvalidCommandFrame(format!("value is not a valid float for '{cmd}'"))
877    })?;
878    if v.is_nan() {
879        return Err(ProtocolError::InvalidCommandFrame(format!(
880            "NaN is not a valid score for '{cmd}'"
881        )));
882    }
883    Ok(v)
884}
885
886fn parse_zadd(args: &[Frame]) -> Result<Command, ProtocolError> {
887    // ZADD key [NX|XX] [GT|LT] [CH] score member [score member ...]
888    if args.len() < 3 {
889        return Err(ProtocolError::WrongArity("ZADD".into()));
890    }
891
892    let key = extract_string(&args[0])?;
893    let mut flags = ZAddFlags::default();
894    let mut idx = 1;
895
896    // parse optional flags before score/member pairs
897    while idx < args.len() {
898        let s = extract_string(&args[idx])?.to_ascii_uppercase();
899        match s.as_str() {
900            "NX" => {
901                flags.nx = true;
902                idx += 1;
903            }
904            "XX" => {
905                flags.xx = true;
906                idx += 1;
907            }
908            "GT" => {
909                flags.gt = true;
910                idx += 1;
911            }
912            "LT" => {
913                flags.lt = true;
914                idx += 1;
915            }
916            "CH" => {
917                flags.ch = true;
918                idx += 1;
919            }
920            _ => break,
921        }
922    }
923
924    // NX and XX are mutually exclusive
925    if flags.nx && flags.xx {
926        return Err(ProtocolError::InvalidCommandFrame(
927            "XX and NX options at the same time are not compatible".into(),
928        ));
929    }
930    // GT and LT are mutually exclusive
931    if flags.gt && flags.lt {
932        return Err(ProtocolError::InvalidCommandFrame(
933            "GT and LT options at the same time are not compatible".into(),
934        ));
935    }
936
937    // remaining args must be score/member pairs
938    let remaining = &args[idx..];
939    if remaining.is_empty() || !remaining.len().is_multiple_of(2) {
940        return Err(ProtocolError::WrongArity("ZADD".into()));
941    }
942
943    let mut members = Vec::with_capacity(remaining.len() / 2);
944    for pair in remaining.chunks(2) {
945        let score = parse_f64(&pair[0], "ZADD")?;
946        let member = extract_string(&pair[1])?;
947        members.push((score, member));
948    }
949
950    Ok(Command::ZAdd {
951        key,
952        flags,
953        members,
954    })
955}
956
957fn parse_zcard(args: &[Frame]) -> Result<Command, ProtocolError> {
958    if args.len() != 1 {
959        return Err(ProtocolError::WrongArity("ZCARD".into()));
960    }
961    let key = extract_string(&args[0])?;
962    Ok(Command::ZCard { key })
963}
964
965fn parse_zrem(args: &[Frame]) -> Result<Command, ProtocolError> {
966    if args.len() < 2 {
967        return Err(ProtocolError::WrongArity("ZREM".into()));
968    }
969    let key = extract_string(&args[0])?;
970    let members = args[1..]
971        .iter()
972        .map(extract_string)
973        .collect::<Result<Vec<_>, _>>()?;
974    Ok(Command::ZRem { key, members })
975}
976
977fn parse_zscore(args: &[Frame]) -> Result<Command, ProtocolError> {
978    if args.len() != 2 {
979        return Err(ProtocolError::WrongArity("ZSCORE".into()));
980    }
981    let key = extract_string(&args[0])?;
982    let member = extract_string(&args[1])?;
983    Ok(Command::ZScore { key, member })
984}
985
986fn parse_zrank(args: &[Frame]) -> Result<Command, ProtocolError> {
987    if args.len() != 2 {
988        return Err(ProtocolError::WrongArity("ZRANK".into()));
989    }
990    let key = extract_string(&args[0])?;
991    let member = extract_string(&args[1])?;
992    Ok(Command::ZRank { key, member })
993}
994
995fn parse_zrange(args: &[Frame]) -> Result<Command, ProtocolError> {
996    if args.len() < 3 || args.len() > 4 {
997        return Err(ProtocolError::WrongArity("ZRANGE".into()));
998    }
999    let key = extract_string(&args[0])?;
1000    let start = parse_i64(&args[1], "ZRANGE")?;
1001    let stop = parse_i64(&args[2], "ZRANGE")?;
1002
1003    let with_scores = if args.len() == 4 {
1004        let opt = extract_string(&args[3])?.to_ascii_uppercase();
1005        if opt != "WITHSCORES" {
1006            return Err(ProtocolError::InvalidCommandFrame(format!(
1007                "unsupported ZRANGE option '{opt}'"
1008            )));
1009        }
1010        true
1011    } else {
1012        false
1013    };
1014
1015    Ok(Command::ZRange {
1016        key,
1017        start,
1018        stop,
1019        with_scores,
1020    })
1021}
1022
1023// --- hash commands ---
1024
1025fn parse_hset(args: &[Frame]) -> Result<Command, ProtocolError> {
1026    // HSET key field value [field value ...]
1027    // args = [key, field, value, ...]
1028    // Need at least 3 args, and after key we need pairs (so remaining count must be even)
1029    if args.len() < 3 || !(args.len() - 1).is_multiple_of(2) {
1030        return Err(ProtocolError::WrongArity("HSET".into()));
1031    }
1032
1033    let key = extract_string(&args[0])?;
1034    let mut fields = Vec::with_capacity((args.len() - 1) / 2);
1035
1036    for chunk in args[1..].chunks(2) {
1037        let field = extract_string(&chunk[0])?;
1038        let value = extract_bytes(&chunk[1])?;
1039        fields.push((field, value));
1040    }
1041
1042    Ok(Command::HSet { key, fields })
1043}
1044
1045fn parse_hget(args: &[Frame]) -> Result<Command, ProtocolError> {
1046    if args.len() != 2 {
1047        return Err(ProtocolError::WrongArity("HGET".into()));
1048    }
1049    let key = extract_string(&args[0])?;
1050    let field = extract_string(&args[1])?;
1051    Ok(Command::HGet { key, field })
1052}
1053
1054fn parse_hgetall(args: &[Frame]) -> Result<Command, ProtocolError> {
1055    if args.len() != 1 {
1056        return Err(ProtocolError::WrongArity("HGETALL".into()));
1057    }
1058    let key = extract_string(&args[0])?;
1059    Ok(Command::HGetAll { key })
1060}
1061
1062fn parse_hdel(args: &[Frame]) -> Result<Command, ProtocolError> {
1063    if args.len() < 2 {
1064        return Err(ProtocolError::WrongArity("HDEL".into()));
1065    }
1066    let key = extract_string(&args[0])?;
1067    let fields = args[1..]
1068        .iter()
1069        .map(extract_string)
1070        .collect::<Result<Vec<_>, _>>()?;
1071    Ok(Command::HDel { key, fields })
1072}
1073
1074fn parse_hexists(args: &[Frame]) -> Result<Command, ProtocolError> {
1075    if args.len() != 2 {
1076        return Err(ProtocolError::WrongArity("HEXISTS".into()));
1077    }
1078    let key = extract_string(&args[0])?;
1079    let field = extract_string(&args[1])?;
1080    Ok(Command::HExists { key, field })
1081}
1082
1083fn parse_hlen(args: &[Frame]) -> Result<Command, ProtocolError> {
1084    if args.len() != 1 {
1085        return Err(ProtocolError::WrongArity("HLEN".into()));
1086    }
1087    let key = extract_string(&args[0])?;
1088    Ok(Command::HLen { key })
1089}
1090
1091fn parse_hincrby(args: &[Frame]) -> Result<Command, ProtocolError> {
1092    if args.len() != 3 {
1093        return Err(ProtocolError::WrongArity("HINCRBY".into()));
1094    }
1095    let key = extract_string(&args[0])?;
1096    let field = extract_string(&args[1])?;
1097    let delta = parse_i64(&args[2], "HINCRBY")?;
1098    Ok(Command::HIncrBy { key, field, delta })
1099}
1100
1101fn parse_hkeys(args: &[Frame]) -> Result<Command, ProtocolError> {
1102    if args.len() != 1 {
1103        return Err(ProtocolError::WrongArity("HKEYS".into()));
1104    }
1105    let key = extract_string(&args[0])?;
1106    Ok(Command::HKeys { key })
1107}
1108
1109fn parse_hvals(args: &[Frame]) -> Result<Command, ProtocolError> {
1110    if args.len() != 1 {
1111        return Err(ProtocolError::WrongArity("HVALS".into()));
1112    }
1113    let key = extract_string(&args[0])?;
1114    Ok(Command::HVals { key })
1115}
1116
1117fn parse_hmget(args: &[Frame]) -> Result<Command, ProtocolError> {
1118    if args.len() < 2 {
1119        return Err(ProtocolError::WrongArity("HMGET".into()));
1120    }
1121    let key = extract_string(&args[0])?;
1122    let fields = args[1..]
1123        .iter()
1124        .map(extract_string)
1125        .collect::<Result<Vec<_>, _>>()?;
1126    Ok(Command::HMGet { key, fields })
1127}
1128
1129// --- set commands ---
1130
1131fn parse_sadd(args: &[Frame]) -> Result<Command, ProtocolError> {
1132    if args.len() < 2 {
1133        return Err(ProtocolError::WrongArity("SADD".into()));
1134    }
1135    let key = extract_string(&args[0])?;
1136    let members = args[1..]
1137        .iter()
1138        .map(extract_string)
1139        .collect::<Result<Vec<_>, _>>()?;
1140    Ok(Command::SAdd { key, members })
1141}
1142
1143fn parse_srem(args: &[Frame]) -> Result<Command, ProtocolError> {
1144    if args.len() < 2 {
1145        return Err(ProtocolError::WrongArity("SREM".into()));
1146    }
1147    let key = extract_string(&args[0])?;
1148    let members = args[1..]
1149        .iter()
1150        .map(extract_string)
1151        .collect::<Result<Vec<_>, _>>()?;
1152    Ok(Command::SRem { key, members })
1153}
1154
1155fn parse_smembers(args: &[Frame]) -> Result<Command, ProtocolError> {
1156    if args.len() != 1 {
1157        return Err(ProtocolError::WrongArity("SMEMBERS".into()));
1158    }
1159    let key = extract_string(&args[0])?;
1160    Ok(Command::SMembers { key })
1161}
1162
1163fn parse_sismember(args: &[Frame]) -> Result<Command, ProtocolError> {
1164    if args.len() != 2 {
1165        return Err(ProtocolError::WrongArity("SISMEMBER".into()));
1166    }
1167    let key = extract_string(&args[0])?;
1168    let member = extract_string(&args[1])?;
1169    Ok(Command::SIsMember { key, member })
1170}
1171
1172fn parse_scard(args: &[Frame]) -> Result<Command, ProtocolError> {
1173    if args.len() != 1 {
1174        return Err(ProtocolError::WrongArity("SCARD".into()));
1175    }
1176    let key = extract_string(&args[0])?;
1177    Ok(Command::SCard { key })
1178}
1179
1180// --- cluster commands ---
1181
1182fn parse_cluster(args: &[Frame]) -> Result<Command, ProtocolError> {
1183    if args.is_empty() {
1184        return Err(ProtocolError::WrongArity("CLUSTER".into()));
1185    }
1186
1187    let subcommand = extract_string(&args[0])?.to_ascii_uppercase();
1188    match subcommand.as_str() {
1189        "INFO" => {
1190            if args.len() != 1 {
1191                return Err(ProtocolError::WrongArity("CLUSTER INFO".into()));
1192            }
1193            Ok(Command::ClusterInfo)
1194        }
1195        "NODES" => {
1196            if args.len() != 1 {
1197                return Err(ProtocolError::WrongArity("CLUSTER NODES".into()));
1198            }
1199            Ok(Command::ClusterNodes)
1200        }
1201        "SLOTS" => {
1202            if args.len() != 1 {
1203                return Err(ProtocolError::WrongArity("CLUSTER SLOTS".into()));
1204            }
1205            Ok(Command::ClusterSlots)
1206        }
1207        "KEYSLOT" => {
1208            if args.len() != 2 {
1209                return Err(ProtocolError::WrongArity("CLUSTER KEYSLOT".into()));
1210            }
1211            let key = extract_string(&args[1])?;
1212            Ok(Command::ClusterKeySlot { key })
1213        }
1214        "MYID" => {
1215            if args.len() != 1 {
1216                return Err(ProtocolError::WrongArity("CLUSTER MYID".into()));
1217            }
1218            Ok(Command::ClusterMyId)
1219        }
1220        "SETSLOT" => parse_cluster_setslot(&args[1..]),
1221        "MEET" => {
1222            if args.len() != 3 {
1223                return Err(ProtocolError::WrongArity("CLUSTER MEET".into()));
1224            }
1225            let ip = extract_string(&args[1])?;
1226            let port_str = extract_string(&args[2])?;
1227            let port: u16 = port_str
1228                .parse()
1229                .map_err(|_| ProtocolError::InvalidCommandFrame("invalid port number".into()))?;
1230            Ok(Command::ClusterMeet { ip, port })
1231        }
1232        "ADDSLOTS" => {
1233            if args.len() < 2 {
1234                return Err(ProtocolError::WrongArity("CLUSTER ADDSLOTS".into()));
1235            }
1236            let slots = parse_slot_list(&args[1..])?;
1237            Ok(Command::ClusterAddSlots { slots })
1238        }
1239        "DELSLOTS" => {
1240            if args.len() < 2 {
1241                return Err(ProtocolError::WrongArity("CLUSTER DELSLOTS".into()));
1242            }
1243            let slots = parse_slot_list(&args[1..])?;
1244            Ok(Command::ClusterDelSlots { slots })
1245        }
1246        "FORGET" => {
1247            if args.len() != 2 {
1248                return Err(ProtocolError::WrongArity("CLUSTER FORGET".into()));
1249            }
1250            let node_id = extract_string(&args[1])?;
1251            Ok(Command::ClusterForget { node_id })
1252        }
1253        "REPLICATE" => {
1254            if args.len() != 2 {
1255                return Err(ProtocolError::WrongArity("CLUSTER REPLICATE".into()));
1256            }
1257            let node_id = extract_string(&args[1])?;
1258            Ok(Command::ClusterReplicate { node_id })
1259        }
1260        "FAILOVER" => {
1261            let mut force = false;
1262            let mut takeover = false;
1263            for arg in &args[1..] {
1264                let opt = extract_string(arg)?.to_ascii_uppercase();
1265                match opt.as_str() {
1266                    "FORCE" => force = true,
1267                    "TAKEOVER" => takeover = true,
1268                    _ => {
1269                        return Err(ProtocolError::InvalidCommandFrame(format!(
1270                            "unknown CLUSTER FAILOVER option '{opt}'"
1271                        )))
1272                    }
1273                }
1274            }
1275            Ok(Command::ClusterFailover { force, takeover })
1276        }
1277        "COUNTKEYSINSLOT" => {
1278            if args.len() != 2 {
1279                return Err(ProtocolError::WrongArity("CLUSTER COUNTKEYSINSLOT".into()));
1280            }
1281            let slot_str = extract_string(&args[1])?;
1282            let slot: u16 = slot_str
1283                .parse()
1284                .map_err(|_| ProtocolError::InvalidCommandFrame("invalid slot number".into()))?;
1285            Ok(Command::ClusterCountKeysInSlot { slot })
1286        }
1287        "GETKEYSINSLOT" => {
1288            if args.len() != 3 {
1289                return Err(ProtocolError::WrongArity("CLUSTER GETKEYSINSLOT".into()));
1290            }
1291            let slot_str = extract_string(&args[1])?;
1292            let slot: u16 = slot_str
1293                .parse()
1294                .map_err(|_| ProtocolError::InvalidCommandFrame("invalid slot number".into()))?;
1295            let count_str = extract_string(&args[2])?;
1296            let count: u32 = count_str
1297                .parse()
1298                .map_err(|_| ProtocolError::InvalidCommandFrame("invalid count".into()))?;
1299            Ok(Command::ClusterGetKeysInSlot { slot, count })
1300        }
1301        _ => Err(ProtocolError::InvalidCommandFrame(format!(
1302            "unknown CLUSTER subcommand '{subcommand}'"
1303        ))),
1304    }
1305}
1306
1307fn parse_asking(args: &[Frame]) -> Result<Command, ProtocolError> {
1308    if !args.is_empty() {
1309        return Err(ProtocolError::WrongArity("ASKING".into()));
1310    }
1311    Ok(Command::Asking)
1312}
1313
1314fn parse_slowlog(args: &[Frame]) -> Result<Command, ProtocolError> {
1315    if args.is_empty() {
1316        return Err(ProtocolError::WrongArity("SLOWLOG".into()));
1317    }
1318
1319    let subcmd = extract_string(&args[0])?.to_ascii_uppercase();
1320    match subcmd.as_str() {
1321        "GET" => {
1322            let count = if args.len() > 1 {
1323                let n: usize = extract_string(&args[1])?.parse().map_err(|_| {
1324                    ProtocolError::InvalidCommandFrame("invalid count for SLOWLOG GET".into())
1325                })?;
1326                Some(n)
1327            } else {
1328                None
1329            };
1330            Ok(Command::SlowLogGet { count })
1331        }
1332        "LEN" => Ok(Command::SlowLogLen),
1333        "RESET" => Ok(Command::SlowLogReset),
1334        other => Err(ProtocolError::InvalidCommandFrame(format!(
1335            "unknown SLOWLOG subcommand '{other}'"
1336        ))),
1337    }
1338}
1339
1340fn parse_slot_list(args: &[Frame]) -> Result<Vec<u16>, ProtocolError> {
1341    let mut slots = Vec::with_capacity(args.len());
1342    for arg in args {
1343        let slot_str = extract_string(arg)?;
1344        let slot: u16 = slot_str
1345            .parse()
1346            .map_err(|_| ProtocolError::InvalidCommandFrame("invalid slot number".into()))?;
1347        slots.push(slot);
1348    }
1349    Ok(slots)
1350}
1351
1352fn parse_cluster_setslot(args: &[Frame]) -> Result<Command, ProtocolError> {
1353    if args.is_empty() {
1354        return Err(ProtocolError::WrongArity("CLUSTER SETSLOT".into()));
1355    }
1356
1357    let slot_str = extract_string(&args[0])?;
1358    let slot: u16 = slot_str
1359        .parse()
1360        .map_err(|_| ProtocolError::InvalidCommandFrame("invalid slot number".into()))?;
1361
1362    if args.len() < 2 {
1363        return Err(ProtocolError::WrongArity("CLUSTER SETSLOT".into()));
1364    }
1365
1366    let action = extract_string(&args[1])?.to_ascii_uppercase();
1367    match action.as_str() {
1368        "IMPORTING" => {
1369            if args.len() != 3 {
1370                return Err(ProtocolError::WrongArity(
1371                    "CLUSTER SETSLOT IMPORTING".into(),
1372                ));
1373            }
1374            let node_id = extract_string(&args[2])?;
1375            Ok(Command::ClusterSetSlotImporting { slot, node_id })
1376        }
1377        "MIGRATING" => {
1378            if args.len() != 3 {
1379                return Err(ProtocolError::WrongArity(
1380                    "CLUSTER SETSLOT MIGRATING".into(),
1381                ));
1382            }
1383            let node_id = extract_string(&args[2])?;
1384            Ok(Command::ClusterSetSlotMigrating { slot, node_id })
1385        }
1386        "NODE" => {
1387            if args.len() != 3 {
1388                return Err(ProtocolError::WrongArity("CLUSTER SETSLOT NODE".into()));
1389            }
1390            let node_id = extract_string(&args[2])?;
1391            Ok(Command::ClusterSetSlotNode { slot, node_id })
1392        }
1393        "STABLE" => {
1394            if args.len() != 2 {
1395                return Err(ProtocolError::WrongArity("CLUSTER SETSLOT STABLE".into()));
1396            }
1397            Ok(Command::ClusterSetSlotStable { slot })
1398        }
1399        _ => Err(ProtocolError::InvalidCommandFrame(format!(
1400            "unknown CLUSTER SETSLOT action '{action}'"
1401        ))),
1402    }
1403}
1404
1405fn parse_migrate(args: &[Frame]) -> Result<Command, ProtocolError> {
1406    // MIGRATE host port key db timeout [COPY] [REPLACE]
1407    if args.len() < 5 {
1408        return Err(ProtocolError::WrongArity("MIGRATE".into()));
1409    }
1410
1411    let host = extract_string(&args[0])?;
1412    let port_str = extract_string(&args[1])?;
1413    let port: u16 = port_str
1414        .parse()
1415        .map_err(|_| ProtocolError::InvalidCommandFrame("invalid port number".into()))?;
1416    let key = extract_string(&args[2])?;
1417    let db_str = extract_string(&args[3])?;
1418    let db: u32 = db_str
1419        .parse()
1420        .map_err(|_| ProtocolError::InvalidCommandFrame("invalid db number".into()))?;
1421    let timeout_str = extract_string(&args[4])?;
1422    let timeout_ms: u64 = timeout_str
1423        .parse()
1424        .map_err(|_| ProtocolError::InvalidCommandFrame("invalid timeout".into()))?;
1425
1426    let mut copy = false;
1427    let mut replace = false;
1428
1429    for arg in &args[5..] {
1430        let opt = extract_string(arg)?.to_ascii_uppercase();
1431        match opt.as_str() {
1432            "COPY" => copy = true,
1433            "REPLACE" => replace = true,
1434            _ => {
1435                return Err(ProtocolError::InvalidCommandFrame(format!(
1436                    "unknown MIGRATE option '{opt}'"
1437                )))
1438            }
1439        }
1440    }
1441
1442    Ok(Command::Migrate {
1443        host,
1444        port,
1445        key,
1446        db,
1447        timeout_ms,
1448        copy,
1449        replace,
1450    })
1451}
1452
1453#[cfg(test)]
1454mod tests {
1455    use super::*;
1456
1457    /// Helper: build an array frame from bulk strings.
1458    fn cmd(parts: &[&str]) -> Frame {
1459        Frame::Array(
1460            parts
1461                .iter()
1462                .map(|s| Frame::Bulk(Bytes::from(s.to_string())))
1463                .collect(),
1464        )
1465    }
1466
1467    // --- ping ---
1468
1469    #[test]
1470    fn ping_no_args() {
1471        assert_eq!(
1472            Command::from_frame(cmd(&["PING"])).unwrap(),
1473            Command::Ping(None),
1474        );
1475    }
1476
1477    #[test]
1478    fn ping_with_message() {
1479        assert_eq!(
1480            Command::from_frame(cmd(&["PING", "hello"])).unwrap(),
1481            Command::Ping(Some(Bytes::from("hello"))),
1482        );
1483    }
1484
1485    #[test]
1486    fn ping_case_insensitive() {
1487        assert_eq!(
1488            Command::from_frame(cmd(&["ping"])).unwrap(),
1489            Command::Ping(None),
1490        );
1491        assert_eq!(
1492            Command::from_frame(cmd(&["Ping"])).unwrap(),
1493            Command::Ping(None),
1494        );
1495    }
1496
1497    #[test]
1498    fn ping_too_many_args() {
1499        let err = Command::from_frame(cmd(&["PING", "a", "b"])).unwrap_err();
1500        assert!(matches!(err, ProtocolError::WrongArity(_)));
1501    }
1502
1503    // --- echo ---
1504
1505    #[test]
1506    fn echo() {
1507        assert_eq!(
1508            Command::from_frame(cmd(&["ECHO", "test"])).unwrap(),
1509            Command::Echo(Bytes::from("test")),
1510        );
1511    }
1512
1513    #[test]
1514    fn echo_missing_arg() {
1515        let err = Command::from_frame(cmd(&["ECHO"])).unwrap_err();
1516        assert!(matches!(err, ProtocolError::WrongArity(_)));
1517    }
1518
1519    // --- get ---
1520
1521    #[test]
1522    fn get_basic() {
1523        assert_eq!(
1524            Command::from_frame(cmd(&["GET", "mykey"])).unwrap(),
1525            Command::Get {
1526                key: "mykey".into()
1527            },
1528        );
1529    }
1530
1531    #[test]
1532    fn get_no_args() {
1533        let err = Command::from_frame(cmd(&["GET"])).unwrap_err();
1534        assert!(matches!(err, ProtocolError::WrongArity(_)));
1535    }
1536
1537    #[test]
1538    fn get_too_many_args() {
1539        let err = Command::from_frame(cmd(&["GET", "a", "b"])).unwrap_err();
1540        assert!(matches!(err, ProtocolError::WrongArity(_)));
1541    }
1542
1543    #[test]
1544    fn get_case_insensitive() {
1545        assert_eq!(
1546            Command::from_frame(cmd(&["get", "k"])).unwrap(),
1547            Command::Get { key: "k".into() },
1548        );
1549    }
1550
1551    // --- set ---
1552
1553    #[test]
1554    fn set_basic() {
1555        assert_eq!(
1556            Command::from_frame(cmd(&["SET", "key", "value"])).unwrap(),
1557            Command::Set {
1558                key: "key".into(),
1559                value: Bytes::from("value"),
1560                expire: None,
1561                nx: false,
1562                xx: false,
1563            },
1564        );
1565    }
1566
1567    #[test]
1568    fn set_with_ex() {
1569        assert_eq!(
1570            Command::from_frame(cmd(&["SET", "key", "val", "EX", "10"])).unwrap(),
1571            Command::Set {
1572                key: "key".into(),
1573                value: Bytes::from("val"),
1574                expire: Some(SetExpire::Ex(10)),
1575                nx: false,
1576                xx: false,
1577            },
1578        );
1579    }
1580
1581    #[test]
1582    fn set_with_px() {
1583        assert_eq!(
1584            Command::from_frame(cmd(&["SET", "key", "val", "PX", "5000"])).unwrap(),
1585            Command::Set {
1586                key: "key".into(),
1587                value: Bytes::from("val"),
1588                expire: Some(SetExpire::Px(5000)),
1589                nx: false,
1590                xx: false,
1591            },
1592        );
1593    }
1594
1595    #[test]
1596    fn set_ex_case_insensitive() {
1597        assert_eq!(
1598            Command::from_frame(cmd(&["set", "k", "v", "ex", "5"])).unwrap(),
1599            Command::Set {
1600                key: "k".into(),
1601                value: Bytes::from("v"),
1602                expire: Some(SetExpire::Ex(5)),
1603                nx: false,
1604                xx: false,
1605            },
1606        );
1607    }
1608
1609    #[test]
1610    fn set_nx_flag() {
1611        assert_eq!(
1612            Command::from_frame(cmd(&["SET", "key", "val", "NX"])).unwrap(),
1613            Command::Set {
1614                key: "key".into(),
1615                value: Bytes::from("val"),
1616                expire: None,
1617                nx: true,
1618                xx: false,
1619            },
1620        );
1621    }
1622
1623    #[test]
1624    fn set_xx_flag() {
1625        assert_eq!(
1626            Command::from_frame(cmd(&["SET", "key", "val", "XX"])).unwrap(),
1627            Command::Set {
1628                key: "key".into(),
1629                value: Bytes::from("val"),
1630                expire: None,
1631                nx: false,
1632                xx: true,
1633            },
1634        );
1635    }
1636
1637    #[test]
1638    fn set_nx_with_ex() {
1639        assert_eq!(
1640            Command::from_frame(cmd(&["SET", "key", "val", "EX", "10", "NX"])).unwrap(),
1641            Command::Set {
1642                key: "key".into(),
1643                value: Bytes::from("val"),
1644                expire: Some(SetExpire::Ex(10)),
1645                nx: true,
1646                xx: false,
1647            },
1648        );
1649    }
1650
1651    #[test]
1652    fn set_nx_before_ex() {
1653        assert_eq!(
1654            Command::from_frame(cmd(&["SET", "key", "val", "NX", "PX", "5000"])).unwrap(),
1655            Command::Set {
1656                key: "key".into(),
1657                value: Bytes::from("val"),
1658                expire: Some(SetExpire::Px(5000)),
1659                nx: true,
1660                xx: false,
1661            },
1662        );
1663    }
1664
1665    #[test]
1666    fn set_nx_xx_conflict() {
1667        let err = Command::from_frame(cmd(&["SET", "k", "v", "NX", "XX"])).unwrap_err();
1668        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
1669    }
1670
1671    #[test]
1672    fn set_nx_case_insensitive() {
1673        assert_eq!(
1674            Command::from_frame(cmd(&["set", "k", "v", "nx"])).unwrap(),
1675            Command::Set {
1676                key: "k".into(),
1677                value: Bytes::from("v"),
1678                expire: None,
1679                nx: true,
1680                xx: false,
1681            },
1682        );
1683    }
1684
1685    #[test]
1686    fn set_missing_value() {
1687        let err = Command::from_frame(cmd(&["SET", "key"])).unwrap_err();
1688        assert!(matches!(err, ProtocolError::WrongArity(_)));
1689    }
1690
1691    #[test]
1692    fn set_invalid_expire_value() {
1693        let err = Command::from_frame(cmd(&["SET", "k", "v", "EX", "notanum"])).unwrap_err();
1694        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
1695    }
1696
1697    #[test]
1698    fn set_zero_expire() {
1699        let err = Command::from_frame(cmd(&["SET", "k", "v", "EX", "0"])).unwrap_err();
1700        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
1701    }
1702
1703    #[test]
1704    fn set_unknown_flag() {
1705        let err = Command::from_frame(cmd(&["SET", "k", "v", "ZZ", "10"])).unwrap_err();
1706        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
1707    }
1708
1709    #[test]
1710    fn set_incomplete_expire() {
1711        // EX without a value
1712        let err = Command::from_frame(cmd(&["SET", "k", "v", "EX"])).unwrap_err();
1713        assert!(matches!(err, ProtocolError::WrongArity(_)));
1714    }
1715
1716    // --- del ---
1717
1718    #[test]
1719    fn del_single() {
1720        assert_eq!(
1721            Command::from_frame(cmd(&["DEL", "key"])).unwrap(),
1722            Command::Del {
1723                keys: vec!["key".into()]
1724            },
1725        );
1726    }
1727
1728    #[test]
1729    fn del_multiple() {
1730        assert_eq!(
1731            Command::from_frame(cmd(&["DEL", "a", "b", "c"])).unwrap(),
1732            Command::Del {
1733                keys: vec!["a".into(), "b".into(), "c".into()]
1734            },
1735        );
1736    }
1737
1738    #[test]
1739    fn del_no_args() {
1740        let err = Command::from_frame(cmd(&["DEL"])).unwrap_err();
1741        assert!(matches!(err, ProtocolError::WrongArity(_)));
1742    }
1743
1744    // --- exists ---
1745
1746    #[test]
1747    fn exists_single() {
1748        assert_eq!(
1749            Command::from_frame(cmd(&["EXISTS", "key"])).unwrap(),
1750            Command::Exists {
1751                keys: vec!["key".into()]
1752            },
1753        );
1754    }
1755
1756    #[test]
1757    fn exists_multiple() {
1758        assert_eq!(
1759            Command::from_frame(cmd(&["EXISTS", "a", "b"])).unwrap(),
1760            Command::Exists {
1761                keys: vec!["a".into(), "b".into()]
1762            },
1763        );
1764    }
1765
1766    #[test]
1767    fn exists_no_args() {
1768        let err = Command::from_frame(cmd(&["EXISTS"])).unwrap_err();
1769        assert!(matches!(err, ProtocolError::WrongArity(_)));
1770    }
1771
1772    // --- mget ---
1773
1774    #[test]
1775    fn mget_single() {
1776        assert_eq!(
1777            Command::from_frame(cmd(&["MGET", "key"])).unwrap(),
1778            Command::MGet {
1779                keys: vec!["key".into()]
1780            },
1781        );
1782    }
1783
1784    #[test]
1785    fn mget_multiple() {
1786        assert_eq!(
1787            Command::from_frame(cmd(&["MGET", "a", "b", "c"])).unwrap(),
1788            Command::MGet {
1789                keys: vec!["a".into(), "b".into(), "c".into()]
1790            },
1791        );
1792    }
1793
1794    #[test]
1795    fn mget_no_args() {
1796        let err = Command::from_frame(cmd(&["MGET"])).unwrap_err();
1797        assert!(matches!(err, ProtocolError::WrongArity(_)));
1798    }
1799
1800    // --- mset ---
1801
1802    #[test]
1803    fn mset_single_pair() {
1804        assert_eq!(
1805            Command::from_frame(cmd(&["MSET", "key", "val"])).unwrap(),
1806            Command::MSet {
1807                pairs: vec![("key".into(), Bytes::from("val"))]
1808            },
1809        );
1810    }
1811
1812    #[test]
1813    fn mset_multiple_pairs() {
1814        assert_eq!(
1815            Command::from_frame(cmd(&["MSET", "a", "1", "b", "2"])).unwrap(),
1816            Command::MSet {
1817                pairs: vec![
1818                    ("a".into(), Bytes::from("1")),
1819                    ("b".into(), Bytes::from("2")),
1820                ]
1821            },
1822        );
1823    }
1824
1825    #[test]
1826    fn mset_no_args() {
1827        let err = Command::from_frame(cmd(&["MSET"])).unwrap_err();
1828        assert!(matches!(err, ProtocolError::WrongArity(_)));
1829    }
1830
1831    #[test]
1832    fn mset_odd_args() {
1833        // missing value for second key
1834        let err = Command::from_frame(cmd(&["MSET", "a", "1", "b"])).unwrap_err();
1835        assert!(matches!(err, ProtocolError::WrongArity(_)));
1836    }
1837
1838    // --- expire ---
1839
1840    #[test]
1841    fn expire_basic() {
1842        assert_eq!(
1843            Command::from_frame(cmd(&["EXPIRE", "key", "60"])).unwrap(),
1844            Command::Expire {
1845                key: "key".into(),
1846                seconds: 60,
1847            },
1848        );
1849    }
1850
1851    #[test]
1852    fn expire_wrong_arity() {
1853        let err = Command::from_frame(cmd(&["EXPIRE", "key"])).unwrap_err();
1854        assert!(matches!(err, ProtocolError::WrongArity(_)));
1855    }
1856
1857    #[test]
1858    fn expire_invalid_seconds() {
1859        let err = Command::from_frame(cmd(&["EXPIRE", "key", "abc"])).unwrap_err();
1860        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
1861    }
1862
1863    #[test]
1864    fn expire_zero_seconds() {
1865        let err = Command::from_frame(cmd(&["EXPIRE", "key", "0"])).unwrap_err();
1866        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
1867    }
1868
1869    // --- ttl ---
1870
1871    #[test]
1872    fn ttl_basic() {
1873        assert_eq!(
1874            Command::from_frame(cmd(&["TTL", "key"])).unwrap(),
1875            Command::Ttl { key: "key".into() },
1876        );
1877    }
1878
1879    #[test]
1880    fn ttl_wrong_arity() {
1881        let err = Command::from_frame(cmd(&["TTL"])).unwrap_err();
1882        assert!(matches!(err, ProtocolError::WrongArity(_)));
1883    }
1884
1885    // --- dbsize ---
1886
1887    #[test]
1888    fn dbsize_basic() {
1889        assert_eq!(
1890            Command::from_frame(cmd(&["DBSIZE"])).unwrap(),
1891            Command::DbSize,
1892        );
1893    }
1894
1895    #[test]
1896    fn dbsize_case_insensitive() {
1897        assert_eq!(
1898            Command::from_frame(cmd(&["dbsize"])).unwrap(),
1899            Command::DbSize,
1900        );
1901    }
1902
1903    #[test]
1904    fn dbsize_extra_args() {
1905        let err = Command::from_frame(cmd(&["DBSIZE", "extra"])).unwrap_err();
1906        assert!(matches!(err, ProtocolError::WrongArity(_)));
1907    }
1908
1909    // --- info ---
1910
1911    #[test]
1912    fn info_no_section() {
1913        assert_eq!(
1914            Command::from_frame(cmd(&["INFO"])).unwrap(),
1915            Command::Info { section: None },
1916        );
1917    }
1918
1919    #[test]
1920    fn info_with_section() {
1921        assert_eq!(
1922            Command::from_frame(cmd(&["INFO", "keyspace"])).unwrap(),
1923            Command::Info {
1924                section: Some("keyspace".into())
1925            },
1926        );
1927    }
1928
1929    #[test]
1930    fn info_too_many_args() {
1931        let err = Command::from_frame(cmd(&["INFO", "a", "b"])).unwrap_err();
1932        assert!(matches!(err, ProtocolError::WrongArity(_)));
1933    }
1934
1935    // --- bgsave ---
1936
1937    #[test]
1938    fn bgsave_basic() {
1939        assert_eq!(
1940            Command::from_frame(cmd(&["BGSAVE"])).unwrap(),
1941            Command::BgSave,
1942        );
1943    }
1944
1945    #[test]
1946    fn bgsave_case_insensitive() {
1947        assert_eq!(
1948            Command::from_frame(cmd(&["bgsave"])).unwrap(),
1949            Command::BgSave,
1950        );
1951    }
1952
1953    #[test]
1954    fn bgsave_extra_args() {
1955        let err = Command::from_frame(cmd(&["BGSAVE", "extra"])).unwrap_err();
1956        assert!(matches!(err, ProtocolError::WrongArity(_)));
1957    }
1958
1959    // --- bgrewriteaof ---
1960
1961    #[test]
1962    fn bgrewriteaof_basic() {
1963        assert_eq!(
1964            Command::from_frame(cmd(&["BGREWRITEAOF"])).unwrap(),
1965            Command::BgRewriteAof,
1966        );
1967    }
1968
1969    #[test]
1970    fn bgrewriteaof_case_insensitive() {
1971        assert_eq!(
1972            Command::from_frame(cmd(&["bgrewriteaof"])).unwrap(),
1973            Command::BgRewriteAof,
1974        );
1975    }
1976
1977    #[test]
1978    fn bgrewriteaof_extra_args() {
1979        let err = Command::from_frame(cmd(&["BGREWRITEAOF", "extra"])).unwrap_err();
1980        assert!(matches!(err, ProtocolError::WrongArity(_)));
1981    }
1982
1983    // --- flushdb ---
1984
1985    #[test]
1986    fn flushdb_basic() {
1987        assert_eq!(
1988            Command::from_frame(cmd(&["FLUSHDB"])).unwrap(),
1989            Command::FlushDb,
1990        );
1991    }
1992
1993    #[test]
1994    fn flushdb_case_insensitive() {
1995        assert_eq!(
1996            Command::from_frame(cmd(&["flushdb"])).unwrap(),
1997            Command::FlushDb,
1998        );
1999    }
2000
2001    #[test]
2002    fn flushdb_extra_args() {
2003        let err = Command::from_frame(cmd(&["FLUSHDB", "extra"])).unwrap_err();
2004        assert!(matches!(err, ProtocolError::WrongArity(_)));
2005    }
2006
2007    // --- lpush ---
2008
2009    #[test]
2010    fn lpush_single() {
2011        assert_eq!(
2012            Command::from_frame(cmd(&["LPUSH", "list", "val"])).unwrap(),
2013            Command::LPush {
2014                key: "list".into(),
2015                values: vec![Bytes::from("val")],
2016            },
2017        );
2018    }
2019
2020    #[test]
2021    fn lpush_multiple() {
2022        assert_eq!(
2023            Command::from_frame(cmd(&["LPUSH", "list", "a", "b", "c"])).unwrap(),
2024            Command::LPush {
2025                key: "list".into(),
2026                values: vec![Bytes::from("a"), Bytes::from("b"), Bytes::from("c")],
2027            },
2028        );
2029    }
2030
2031    #[test]
2032    fn lpush_no_value() {
2033        let err = Command::from_frame(cmd(&["LPUSH", "key"])).unwrap_err();
2034        assert!(matches!(err, ProtocolError::WrongArity(_)));
2035    }
2036
2037    #[test]
2038    fn lpush_case_insensitive() {
2039        assert!(matches!(
2040            Command::from_frame(cmd(&["lpush", "k", "v"])).unwrap(),
2041            Command::LPush { .. }
2042        ));
2043    }
2044
2045    // --- rpush ---
2046
2047    #[test]
2048    fn rpush_single() {
2049        assert_eq!(
2050            Command::from_frame(cmd(&["RPUSH", "list", "val"])).unwrap(),
2051            Command::RPush {
2052                key: "list".into(),
2053                values: vec![Bytes::from("val")],
2054            },
2055        );
2056    }
2057
2058    #[test]
2059    fn rpush_no_value() {
2060        let err = Command::from_frame(cmd(&["RPUSH", "key"])).unwrap_err();
2061        assert!(matches!(err, ProtocolError::WrongArity(_)));
2062    }
2063
2064    // --- lpop ---
2065
2066    #[test]
2067    fn lpop_basic() {
2068        assert_eq!(
2069            Command::from_frame(cmd(&["LPOP", "list"])).unwrap(),
2070            Command::LPop { key: "list".into() },
2071        );
2072    }
2073
2074    #[test]
2075    fn lpop_wrong_arity() {
2076        let err = Command::from_frame(cmd(&["LPOP"])).unwrap_err();
2077        assert!(matches!(err, ProtocolError::WrongArity(_)));
2078    }
2079
2080    // --- rpop ---
2081
2082    #[test]
2083    fn rpop_basic() {
2084        assert_eq!(
2085            Command::from_frame(cmd(&["RPOP", "list"])).unwrap(),
2086            Command::RPop { key: "list".into() },
2087        );
2088    }
2089
2090    // --- lrange ---
2091
2092    #[test]
2093    fn lrange_basic() {
2094        assert_eq!(
2095            Command::from_frame(cmd(&["LRANGE", "list", "0", "-1"])).unwrap(),
2096            Command::LRange {
2097                key: "list".into(),
2098                start: 0,
2099                stop: -1,
2100            },
2101        );
2102    }
2103
2104    #[test]
2105    fn lrange_wrong_arity() {
2106        let err = Command::from_frame(cmd(&["LRANGE", "list", "0"])).unwrap_err();
2107        assert!(matches!(err, ProtocolError::WrongArity(_)));
2108    }
2109
2110    #[test]
2111    fn lrange_invalid_index() {
2112        let err = Command::from_frame(cmd(&["LRANGE", "list", "abc", "0"])).unwrap_err();
2113        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
2114    }
2115
2116    // --- llen ---
2117
2118    #[test]
2119    fn llen_basic() {
2120        assert_eq!(
2121            Command::from_frame(cmd(&["LLEN", "list"])).unwrap(),
2122            Command::LLen { key: "list".into() },
2123        );
2124    }
2125
2126    #[test]
2127    fn llen_wrong_arity() {
2128        let err = Command::from_frame(cmd(&["LLEN"])).unwrap_err();
2129        assert!(matches!(err, ProtocolError::WrongArity(_)));
2130    }
2131
2132    // --- type ---
2133
2134    #[test]
2135    fn type_basic() {
2136        assert_eq!(
2137            Command::from_frame(cmd(&["TYPE", "key"])).unwrap(),
2138            Command::Type { key: "key".into() },
2139        );
2140    }
2141
2142    #[test]
2143    fn type_wrong_arity() {
2144        let err = Command::from_frame(cmd(&["TYPE"])).unwrap_err();
2145        assert!(matches!(err, ProtocolError::WrongArity(_)));
2146    }
2147
2148    #[test]
2149    fn type_case_insensitive() {
2150        assert!(matches!(
2151            Command::from_frame(cmd(&["type", "k"])).unwrap(),
2152            Command::Type { .. }
2153        ));
2154    }
2155
2156    // --- general ---
2157
2158    #[test]
2159    fn unknown_command() {
2160        assert_eq!(
2161            Command::from_frame(cmd(&["FOOBAR", "arg"])).unwrap(),
2162            Command::Unknown("FOOBAR".into()),
2163        );
2164    }
2165
2166    #[test]
2167    fn non_array_frame() {
2168        let err = Command::from_frame(Frame::Simple("PING".into())).unwrap_err();
2169        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
2170    }
2171
2172    #[test]
2173    fn empty_array() {
2174        let err = Command::from_frame(Frame::Array(vec![])).unwrap_err();
2175        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
2176    }
2177
2178    // --- zadd ---
2179
2180    #[test]
2181    fn zadd_basic() {
2182        let parsed = Command::from_frame(cmd(&["ZADD", "board", "100", "alice"])).unwrap();
2183        match parsed {
2184            Command::ZAdd {
2185                key,
2186                flags,
2187                members,
2188            } => {
2189                assert_eq!(key, "board");
2190                assert_eq!(flags, ZAddFlags::default());
2191                assert_eq!(members, vec![(100.0, "alice".into())]);
2192            }
2193            other => panic!("expected ZAdd, got {other:?}"),
2194        }
2195    }
2196
2197    #[test]
2198    fn zadd_multiple_members() {
2199        let parsed =
2200            Command::from_frame(cmd(&["ZADD", "board", "100", "alice", "200", "bob"])).unwrap();
2201        match parsed {
2202            Command::ZAdd { members, .. } => {
2203                assert_eq!(members.len(), 2);
2204                assert_eq!(members[0], (100.0, "alice".into()));
2205                assert_eq!(members[1], (200.0, "bob".into()));
2206            }
2207            other => panic!("expected ZAdd, got {other:?}"),
2208        }
2209    }
2210
2211    #[test]
2212    fn zadd_with_flags() {
2213        let parsed = Command::from_frame(cmd(&["ZADD", "z", "NX", "CH", "100", "alice"])).unwrap();
2214        match parsed {
2215            Command::ZAdd { flags, .. } => {
2216                assert!(flags.nx);
2217                assert!(flags.ch);
2218                assert!(!flags.xx);
2219                assert!(!flags.gt);
2220                assert!(!flags.lt);
2221            }
2222            other => panic!("expected ZAdd, got {other:?}"),
2223        }
2224    }
2225
2226    #[test]
2227    fn zadd_gt_flag() {
2228        let parsed = Command::from_frame(cmd(&["zadd", "z", "gt", "100", "alice"])).unwrap();
2229        match parsed {
2230            Command::ZAdd { flags, .. } => assert!(flags.gt),
2231            other => panic!("expected ZAdd, got {other:?}"),
2232        }
2233    }
2234
2235    #[test]
2236    fn zadd_nx_xx_conflict() {
2237        let err = Command::from_frame(cmd(&["ZADD", "z", "NX", "XX", "100", "alice"])).unwrap_err();
2238        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
2239    }
2240
2241    #[test]
2242    fn zadd_gt_lt_conflict() {
2243        let err = Command::from_frame(cmd(&["ZADD", "z", "GT", "LT", "100", "alice"])).unwrap_err();
2244        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
2245    }
2246
2247    #[test]
2248    fn zadd_wrong_arity() {
2249        let err = Command::from_frame(cmd(&["ZADD", "z"])).unwrap_err();
2250        assert!(matches!(err, ProtocolError::WrongArity(_)));
2251    }
2252
2253    #[test]
2254    fn zadd_odd_score_member_count() {
2255        // one score without a member
2256        let err = Command::from_frame(cmd(&["ZADD", "z", "100"])).unwrap_err();
2257        assert!(matches!(err, ProtocolError::WrongArity(_)));
2258    }
2259
2260    #[test]
2261    fn zadd_invalid_score() {
2262        let err = Command::from_frame(cmd(&["ZADD", "z", "notanum", "alice"])).unwrap_err();
2263        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
2264    }
2265
2266    #[test]
2267    fn zadd_nan_score() {
2268        let err = Command::from_frame(cmd(&["ZADD", "z", "nan", "alice"])).unwrap_err();
2269        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
2270    }
2271
2272    #[test]
2273    fn zadd_negative_score() {
2274        let parsed = Command::from_frame(cmd(&["ZADD", "z", "-50.5", "alice"])).unwrap();
2275        match parsed {
2276            Command::ZAdd { members, .. } => {
2277                assert_eq!(members[0].0, -50.5);
2278            }
2279            other => panic!("expected ZAdd, got {other:?}"),
2280        }
2281    }
2282
2283    // --- zrem ---
2284
2285    #[test]
2286    fn zrem_basic() {
2287        assert_eq!(
2288            Command::from_frame(cmd(&["ZREM", "z", "alice"])).unwrap(),
2289            Command::ZRem {
2290                key: "z".into(),
2291                members: vec!["alice".into()],
2292            },
2293        );
2294    }
2295
2296    #[test]
2297    fn zrem_multiple() {
2298        let parsed = Command::from_frame(cmd(&["ZREM", "z", "a", "b", "c"])).unwrap();
2299        match parsed {
2300            Command::ZRem { members, .. } => assert_eq!(members.len(), 3),
2301            other => panic!("expected ZRem, got {other:?}"),
2302        }
2303    }
2304
2305    #[test]
2306    fn zrem_wrong_arity() {
2307        let err = Command::from_frame(cmd(&["ZREM", "z"])).unwrap_err();
2308        assert!(matches!(err, ProtocolError::WrongArity(_)));
2309    }
2310
2311    // --- zscore ---
2312
2313    #[test]
2314    fn zscore_basic() {
2315        assert_eq!(
2316            Command::from_frame(cmd(&["ZSCORE", "z", "alice"])).unwrap(),
2317            Command::ZScore {
2318                key: "z".into(),
2319                member: "alice".into(),
2320            },
2321        );
2322    }
2323
2324    #[test]
2325    fn zscore_wrong_arity() {
2326        let err = Command::from_frame(cmd(&["ZSCORE", "z"])).unwrap_err();
2327        assert!(matches!(err, ProtocolError::WrongArity(_)));
2328    }
2329
2330    // --- zrank ---
2331
2332    #[test]
2333    fn zrank_basic() {
2334        assert_eq!(
2335            Command::from_frame(cmd(&["ZRANK", "z", "alice"])).unwrap(),
2336            Command::ZRank {
2337                key: "z".into(),
2338                member: "alice".into(),
2339            },
2340        );
2341    }
2342
2343    #[test]
2344    fn zrank_wrong_arity() {
2345        let err = Command::from_frame(cmd(&["ZRANK", "z"])).unwrap_err();
2346        assert!(matches!(err, ProtocolError::WrongArity(_)));
2347    }
2348
2349    // --- zcard ---
2350
2351    #[test]
2352    fn zcard_basic() {
2353        assert_eq!(
2354            Command::from_frame(cmd(&["ZCARD", "z"])).unwrap(),
2355            Command::ZCard { key: "z".into() },
2356        );
2357    }
2358
2359    #[test]
2360    fn zcard_wrong_arity() {
2361        let err = Command::from_frame(cmd(&["ZCARD"])).unwrap_err();
2362        assert!(matches!(err, ProtocolError::WrongArity(_)));
2363    }
2364
2365    // --- zrange ---
2366
2367    #[test]
2368    fn zrange_basic() {
2369        assert_eq!(
2370            Command::from_frame(cmd(&["ZRANGE", "z", "0", "-1"])).unwrap(),
2371            Command::ZRange {
2372                key: "z".into(),
2373                start: 0,
2374                stop: -1,
2375                with_scores: false,
2376            },
2377        );
2378    }
2379
2380    #[test]
2381    fn zrange_with_scores() {
2382        assert_eq!(
2383            Command::from_frame(cmd(&["ZRANGE", "z", "0", "-1", "WITHSCORES"])).unwrap(),
2384            Command::ZRange {
2385                key: "z".into(),
2386                start: 0,
2387                stop: -1,
2388                with_scores: true,
2389            },
2390        );
2391    }
2392
2393    #[test]
2394    fn zrange_withscores_case_insensitive() {
2395        assert_eq!(
2396            Command::from_frame(cmd(&["zrange", "z", "0", "-1", "withscores"])).unwrap(),
2397            Command::ZRange {
2398                key: "z".into(),
2399                start: 0,
2400                stop: -1,
2401                with_scores: true,
2402            },
2403        );
2404    }
2405
2406    #[test]
2407    fn zrange_invalid_option() {
2408        let err = Command::from_frame(cmd(&["ZRANGE", "z", "0", "-1", "BADOPT"])).unwrap_err();
2409        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
2410    }
2411
2412    #[test]
2413    fn zrange_wrong_arity() {
2414        let err = Command::from_frame(cmd(&["ZRANGE", "z", "0"])).unwrap_err();
2415        assert!(matches!(err, ProtocolError::WrongArity(_)));
2416    }
2417
2418    // --- incr ---
2419
2420    #[test]
2421    fn incr_basic() {
2422        assert_eq!(
2423            Command::from_frame(cmd(&["INCR", "counter"])).unwrap(),
2424            Command::Incr {
2425                key: "counter".into()
2426            },
2427        );
2428    }
2429
2430    #[test]
2431    fn incr_wrong_arity() {
2432        let err = Command::from_frame(cmd(&["INCR"])).unwrap_err();
2433        assert!(matches!(err, ProtocolError::WrongArity(_)));
2434    }
2435
2436    // --- decr ---
2437
2438    #[test]
2439    fn decr_basic() {
2440        assert_eq!(
2441            Command::from_frame(cmd(&["DECR", "counter"])).unwrap(),
2442            Command::Decr {
2443                key: "counter".into()
2444            },
2445        );
2446    }
2447
2448    #[test]
2449    fn decr_wrong_arity() {
2450        let err = Command::from_frame(cmd(&["DECR"])).unwrap_err();
2451        assert!(matches!(err, ProtocolError::WrongArity(_)));
2452    }
2453
2454    // --- persist ---
2455
2456    #[test]
2457    fn persist_basic() {
2458        assert_eq!(
2459            Command::from_frame(cmd(&["PERSIST", "key"])).unwrap(),
2460            Command::Persist { key: "key".into() },
2461        );
2462    }
2463
2464    #[test]
2465    fn persist_case_insensitive() {
2466        assert_eq!(
2467            Command::from_frame(cmd(&["persist", "key"])).unwrap(),
2468            Command::Persist { key: "key".into() },
2469        );
2470    }
2471
2472    #[test]
2473    fn persist_wrong_arity() {
2474        let err = Command::from_frame(cmd(&["PERSIST"])).unwrap_err();
2475        assert!(matches!(err, ProtocolError::WrongArity(_)));
2476    }
2477
2478    // --- pttl ---
2479
2480    #[test]
2481    fn pttl_basic() {
2482        assert_eq!(
2483            Command::from_frame(cmd(&["PTTL", "key"])).unwrap(),
2484            Command::Pttl { key: "key".into() },
2485        );
2486    }
2487
2488    #[test]
2489    fn pttl_wrong_arity() {
2490        let err = Command::from_frame(cmd(&["PTTL"])).unwrap_err();
2491        assert!(matches!(err, ProtocolError::WrongArity(_)));
2492    }
2493
2494    // --- pexpire ---
2495
2496    #[test]
2497    fn pexpire_basic() {
2498        assert_eq!(
2499            Command::from_frame(cmd(&["PEXPIRE", "key", "5000"])).unwrap(),
2500            Command::Pexpire {
2501                key: "key".into(),
2502                milliseconds: 5000,
2503            },
2504        );
2505    }
2506
2507    #[test]
2508    fn pexpire_wrong_arity() {
2509        let err = Command::from_frame(cmd(&["PEXPIRE", "key"])).unwrap_err();
2510        assert!(matches!(err, ProtocolError::WrongArity(_)));
2511    }
2512
2513    #[test]
2514    fn pexpire_zero_millis() {
2515        let err = Command::from_frame(cmd(&["PEXPIRE", "key", "0"])).unwrap_err();
2516        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
2517    }
2518
2519    #[test]
2520    fn pexpire_invalid_millis() {
2521        let err = Command::from_frame(cmd(&["PEXPIRE", "key", "notanum"])).unwrap_err();
2522        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
2523    }
2524
2525    // --- scan ---
2526
2527    #[test]
2528    fn scan_basic() {
2529        assert_eq!(
2530            Command::from_frame(cmd(&["SCAN", "0"])).unwrap(),
2531            Command::Scan {
2532                cursor: 0,
2533                pattern: None,
2534                count: None,
2535            },
2536        );
2537    }
2538
2539    #[test]
2540    fn scan_with_match() {
2541        assert_eq!(
2542            Command::from_frame(cmd(&["SCAN", "0", "MATCH", "user:*"])).unwrap(),
2543            Command::Scan {
2544                cursor: 0,
2545                pattern: Some("user:*".into()),
2546                count: None,
2547            },
2548        );
2549    }
2550
2551    #[test]
2552    fn scan_with_count() {
2553        assert_eq!(
2554            Command::from_frame(cmd(&["SCAN", "42", "COUNT", "100"])).unwrap(),
2555            Command::Scan {
2556                cursor: 42,
2557                pattern: None,
2558                count: Some(100),
2559            },
2560        );
2561    }
2562
2563    #[test]
2564    fn scan_with_match_and_count() {
2565        assert_eq!(
2566            Command::from_frame(cmd(&["SCAN", "0", "MATCH", "*:data", "COUNT", "50"])).unwrap(),
2567            Command::Scan {
2568                cursor: 0,
2569                pattern: Some("*:data".into()),
2570                count: Some(50),
2571            },
2572        );
2573    }
2574
2575    #[test]
2576    fn scan_count_before_match() {
2577        assert_eq!(
2578            Command::from_frame(cmd(&["SCAN", "0", "COUNT", "10", "MATCH", "foo*"])).unwrap(),
2579            Command::Scan {
2580                cursor: 0,
2581                pattern: Some("foo*".into()),
2582                count: Some(10),
2583            },
2584        );
2585    }
2586
2587    #[test]
2588    fn scan_case_insensitive() {
2589        assert_eq!(
2590            Command::from_frame(cmd(&["scan", "0", "match", "x*", "count", "5"])).unwrap(),
2591            Command::Scan {
2592                cursor: 0,
2593                pattern: Some("x*".into()),
2594                count: Some(5),
2595            },
2596        );
2597    }
2598
2599    #[test]
2600    fn scan_wrong_arity() {
2601        let err = Command::from_frame(cmd(&["SCAN"])).unwrap_err();
2602        assert!(matches!(err, ProtocolError::WrongArity(_)));
2603    }
2604
2605    #[test]
2606    fn scan_invalid_cursor() {
2607        let err = Command::from_frame(cmd(&["SCAN", "notanum"])).unwrap_err();
2608        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
2609    }
2610
2611    #[test]
2612    fn scan_invalid_count() {
2613        let err = Command::from_frame(cmd(&["SCAN", "0", "COUNT", "bad"])).unwrap_err();
2614        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
2615    }
2616
2617    #[test]
2618    fn scan_unknown_flag() {
2619        let err = Command::from_frame(cmd(&["SCAN", "0", "BADOPT", "val"])).unwrap_err();
2620        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
2621    }
2622
2623    #[test]
2624    fn scan_match_missing_pattern() {
2625        let err = Command::from_frame(cmd(&["SCAN", "0", "MATCH"])).unwrap_err();
2626        assert!(matches!(err, ProtocolError::WrongArity(_)));
2627    }
2628
2629    #[test]
2630    fn scan_count_missing_value() {
2631        let err = Command::from_frame(cmd(&["SCAN", "0", "COUNT"])).unwrap_err();
2632        assert!(matches!(err, ProtocolError::WrongArity(_)));
2633    }
2634
2635    // --- hash commands ---
2636
2637    #[test]
2638    fn hset_single_field() {
2639        assert_eq!(
2640            Command::from_frame(cmd(&["HSET", "h", "field", "value"])).unwrap(),
2641            Command::HSet {
2642                key: "h".into(),
2643                fields: vec![("field".into(), Bytes::from("value"))],
2644            },
2645        );
2646    }
2647
2648    #[test]
2649    fn hset_multiple_fields() {
2650        let parsed = Command::from_frame(cmd(&["HSET", "h", "f1", "v1", "f2", "v2"])).unwrap();
2651        match parsed {
2652            Command::HSet { key, fields } => {
2653                assert_eq!(key, "h");
2654                assert_eq!(fields.len(), 2);
2655            }
2656            other => panic!("expected HSet, got {other:?}"),
2657        }
2658    }
2659
2660    #[test]
2661    fn hset_wrong_arity() {
2662        let err = Command::from_frame(cmd(&["HSET", "h"])).unwrap_err();
2663        assert!(matches!(err, ProtocolError::WrongArity(_)));
2664        let err = Command::from_frame(cmd(&["HSET", "h", "f"])).unwrap_err();
2665        assert!(matches!(err, ProtocolError::WrongArity(_)));
2666    }
2667
2668    #[test]
2669    fn hget_basic() {
2670        assert_eq!(
2671            Command::from_frame(cmd(&["HGET", "h", "field"])).unwrap(),
2672            Command::HGet {
2673                key: "h".into(),
2674                field: "field".into(),
2675            },
2676        );
2677    }
2678
2679    #[test]
2680    fn hget_wrong_arity() {
2681        let err = Command::from_frame(cmd(&["HGET", "h"])).unwrap_err();
2682        assert!(matches!(err, ProtocolError::WrongArity(_)));
2683    }
2684
2685    #[test]
2686    fn hgetall_basic() {
2687        assert_eq!(
2688            Command::from_frame(cmd(&["HGETALL", "h"])).unwrap(),
2689            Command::HGetAll { key: "h".into() },
2690        );
2691    }
2692
2693    #[test]
2694    fn hgetall_wrong_arity() {
2695        let err = Command::from_frame(cmd(&["HGETALL"])).unwrap_err();
2696        assert!(matches!(err, ProtocolError::WrongArity(_)));
2697    }
2698
2699    #[test]
2700    fn hdel_single() {
2701        assert_eq!(
2702            Command::from_frame(cmd(&["HDEL", "h", "f"])).unwrap(),
2703            Command::HDel {
2704                key: "h".into(),
2705                fields: vec!["f".into()],
2706            },
2707        );
2708    }
2709
2710    #[test]
2711    fn hdel_multiple() {
2712        let parsed = Command::from_frame(cmd(&["HDEL", "h", "f1", "f2", "f3"])).unwrap();
2713        match parsed {
2714            Command::HDel { fields, .. } => assert_eq!(fields.len(), 3),
2715            other => panic!("expected HDel, got {other:?}"),
2716        }
2717    }
2718
2719    #[test]
2720    fn hdel_wrong_arity() {
2721        let err = Command::from_frame(cmd(&["HDEL", "h"])).unwrap_err();
2722        assert!(matches!(err, ProtocolError::WrongArity(_)));
2723    }
2724
2725    #[test]
2726    fn hexists_basic() {
2727        assert_eq!(
2728            Command::from_frame(cmd(&["HEXISTS", "h", "f"])).unwrap(),
2729            Command::HExists {
2730                key: "h".into(),
2731                field: "f".into(),
2732            },
2733        );
2734    }
2735
2736    #[test]
2737    fn hlen_basic() {
2738        assert_eq!(
2739            Command::from_frame(cmd(&["HLEN", "h"])).unwrap(),
2740            Command::HLen { key: "h".into() },
2741        );
2742    }
2743
2744    #[test]
2745    fn hincrby_basic() {
2746        assert_eq!(
2747            Command::from_frame(cmd(&["HINCRBY", "h", "f", "5"])).unwrap(),
2748            Command::HIncrBy {
2749                key: "h".into(),
2750                field: "f".into(),
2751                delta: 5,
2752            },
2753        );
2754    }
2755
2756    #[test]
2757    fn hincrby_negative() {
2758        assert_eq!(
2759            Command::from_frame(cmd(&["HINCRBY", "h", "f", "-3"])).unwrap(),
2760            Command::HIncrBy {
2761                key: "h".into(),
2762                field: "f".into(),
2763                delta: -3,
2764            },
2765        );
2766    }
2767
2768    #[test]
2769    fn hincrby_wrong_arity() {
2770        let err = Command::from_frame(cmd(&["HINCRBY", "h", "f"])).unwrap_err();
2771        assert!(matches!(err, ProtocolError::WrongArity(_)));
2772    }
2773
2774    #[test]
2775    fn hkeys_basic() {
2776        assert_eq!(
2777            Command::from_frame(cmd(&["HKEYS", "h"])).unwrap(),
2778            Command::HKeys { key: "h".into() },
2779        );
2780    }
2781
2782    #[test]
2783    fn hvals_basic() {
2784        assert_eq!(
2785            Command::from_frame(cmd(&["HVALS", "h"])).unwrap(),
2786            Command::HVals { key: "h".into() },
2787        );
2788    }
2789
2790    #[test]
2791    fn hmget_basic() {
2792        assert_eq!(
2793            Command::from_frame(cmd(&["HMGET", "h", "f1", "f2"])).unwrap(),
2794            Command::HMGet {
2795                key: "h".into(),
2796                fields: vec!["f1".into(), "f2".into()],
2797            },
2798        );
2799    }
2800
2801    #[test]
2802    fn hmget_wrong_arity() {
2803        let err = Command::from_frame(cmd(&["HMGET", "h"])).unwrap_err();
2804        assert!(matches!(err, ProtocolError::WrongArity(_)));
2805    }
2806
2807    #[test]
2808    fn hash_commands_case_insensitive() {
2809        assert!(matches!(
2810            Command::from_frame(cmd(&["hset", "h", "f", "v"])).unwrap(),
2811            Command::HSet { .. }
2812        ));
2813        assert!(matches!(
2814            Command::from_frame(cmd(&["hget", "h", "f"])).unwrap(),
2815            Command::HGet { .. }
2816        ));
2817        assert!(matches!(
2818            Command::from_frame(cmd(&["hgetall", "h"])).unwrap(),
2819            Command::HGetAll { .. }
2820        ));
2821    }
2822
2823    // --- set commands ---
2824
2825    #[test]
2826    fn sadd_single_member() {
2827        assert_eq!(
2828            Command::from_frame(cmd(&["SADD", "s", "member"])).unwrap(),
2829            Command::SAdd {
2830                key: "s".into(),
2831                members: vec!["member".into()],
2832            },
2833        );
2834    }
2835
2836    #[test]
2837    fn sadd_multiple_members() {
2838        let parsed = Command::from_frame(cmd(&["SADD", "s", "a", "b", "c"])).unwrap();
2839        match parsed {
2840            Command::SAdd { key, members } => {
2841                assert_eq!(key, "s");
2842                assert_eq!(members.len(), 3);
2843            }
2844            other => panic!("expected SAdd, got {other:?}"),
2845        }
2846    }
2847
2848    #[test]
2849    fn sadd_wrong_arity() {
2850        let err = Command::from_frame(cmd(&["SADD", "s"])).unwrap_err();
2851        assert!(matches!(err, ProtocolError::WrongArity(_)));
2852    }
2853
2854    #[test]
2855    fn srem_single_member() {
2856        assert_eq!(
2857            Command::from_frame(cmd(&["SREM", "s", "member"])).unwrap(),
2858            Command::SRem {
2859                key: "s".into(),
2860                members: vec!["member".into()],
2861            },
2862        );
2863    }
2864
2865    #[test]
2866    fn srem_multiple_members() {
2867        let parsed = Command::from_frame(cmd(&["SREM", "s", "a", "b"])).unwrap();
2868        match parsed {
2869            Command::SRem { key, members } => {
2870                assert_eq!(key, "s");
2871                assert_eq!(members.len(), 2);
2872            }
2873            other => panic!("expected SRem, got {other:?}"),
2874        }
2875    }
2876
2877    #[test]
2878    fn srem_wrong_arity() {
2879        let err = Command::from_frame(cmd(&["SREM", "s"])).unwrap_err();
2880        assert!(matches!(err, ProtocolError::WrongArity(_)));
2881    }
2882
2883    #[test]
2884    fn smembers_basic() {
2885        assert_eq!(
2886            Command::from_frame(cmd(&["SMEMBERS", "s"])).unwrap(),
2887            Command::SMembers { key: "s".into() },
2888        );
2889    }
2890
2891    #[test]
2892    fn smembers_wrong_arity() {
2893        let err = Command::from_frame(cmd(&["SMEMBERS"])).unwrap_err();
2894        assert!(matches!(err, ProtocolError::WrongArity(_)));
2895    }
2896
2897    #[test]
2898    fn sismember_basic() {
2899        assert_eq!(
2900            Command::from_frame(cmd(&["SISMEMBER", "s", "member"])).unwrap(),
2901            Command::SIsMember {
2902                key: "s".into(),
2903                member: "member".into(),
2904            },
2905        );
2906    }
2907
2908    #[test]
2909    fn sismember_wrong_arity() {
2910        let err = Command::from_frame(cmd(&["SISMEMBER", "s"])).unwrap_err();
2911        assert!(matches!(err, ProtocolError::WrongArity(_)));
2912    }
2913
2914    #[test]
2915    fn scard_basic() {
2916        assert_eq!(
2917            Command::from_frame(cmd(&["SCARD", "s"])).unwrap(),
2918            Command::SCard { key: "s".into() },
2919        );
2920    }
2921
2922    #[test]
2923    fn scard_wrong_arity() {
2924        let err = Command::from_frame(cmd(&["SCARD"])).unwrap_err();
2925        assert!(matches!(err, ProtocolError::WrongArity(_)));
2926    }
2927
2928    #[test]
2929    fn set_commands_case_insensitive() {
2930        assert!(matches!(
2931            Command::from_frame(cmd(&["sadd", "s", "m"])).unwrap(),
2932            Command::SAdd { .. }
2933        ));
2934        assert!(matches!(
2935            Command::from_frame(cmd(&["srem", "s", "m"])).unwrap(),
2936            Command::SRem { .. }
2937        ));
2938        assert!(matches!(
2939            Command::from_frame(cmd(&["smembers", "s"])).unwrap(),
2940            Command::SMembers { .. }
2941        ));
2942    }
2943
2944    // --- cluster commands ---
2945
2946    #[test]
2947    fn cluster_info_basic() {
2948        assert_eq!(
2949            Command::from_frame(cmd(&["CLUSTER", "INFO"])).unwrap(),
2950            Command::ClusterInfo,
2951        );
2952    }
2953
2954    #[test]
2955    fn cluster_nodes_basic() {
2956        assert_eq!(
2957            Command::from_frame(cmd(&["CLUSTER", "NODES"])).unwrap(),
2958            Command::ClusterNodes,
2959        );
2960    }
2961
2962    #[test]
2963    fn cluster_slots_basic() {
2964        assert_eq!(
2965            Command::from_frame(cmd(&["CLUSTER", "SLOTS"])).unwrap(),
2966            Command::ClusterSlots,
2967        );
2968    }
2969
2970    #[test]
2971    fn cluster_keyslot_basic() {
2972        assert_eq!(
2973            Command::from_frame(cmd(&["CLUSTER", "KEYSLOT", "mykey"])).unwrap(),
2974            Command::ClusterKeySlot {
2975                key: "mykey".into()
2976            },
2977        );
2978    }
2979
2980    #[test]
2981    fn cluster_keyslot_wrong_arity() {
2982        let err = Command::from_frame(cmd(&["CLUSTER", "KEYSLOT"])).unwrap_err();
2983        assert!(matches!(err, ProtocolError::WrongArity(_)));
2984    }
2985
2986    #[test]
2987    fn cluster_myid_basic() {
2988        assert_eq!(
2989            Command::from_frame(cmd(&["CLUSTER", "MYID"])).unwrap(),
2990            Command::ClusterMyId,
2991        );
2992    }
2993
2994    #[test]
2995    fn cluster_unknown_subcommand() {
2996        let err = Command::from_frame(cmd(&["CLUSTER", "BADCMD"])).unwrap_err();
2997        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
2998    }
2999
3000    #[test]
3001    fn cluster_no_subcommand() {
3002        let err = Command::from_frame(cmd(&["CLUSTER"])).unwrap_err();
3003        assert!(matches!(err, ProtocolError::WrongArity(_)));
3004    }
3005
3006    #[test]
3007    fn cluster_case_insensitive() {
3008        assert!(matches!(
3009            Command::from_frame(cmd(&["cluster", "info"])).unwrap(),
3010            Command::ClusterInfo
3011        ));
3012        assert!(matches!(
3013            Command::from_frame(cmd(&["cluster", "keyslot", "k"])).unwrap(),
3014            Command::ClusterKeySlot { .. }
3015        ));
3016    }
3017
3018    #[test]
3019    fn asking_basic() {
3020        assert_eq!(
3021            Command::from_frame(cmd(&["ASKING"])).unwrap(),
3022            Command::Asking,
3023        );
3024    }
3025
3026    #[test]
3027    fn asking_wrong_arity() {
3028        let err = Command::from_frame(cmd(&["ASKING", "extra"])).unwrap_err();
3029        assert!(matches!(err, ProtocolError::WrongArity(_)));
3030    }
3031
3032    #[test]
3033    fn cluster_setslot_importing() {
3034        assert_eq!(
3035            Command::from_frame(cmd(&["CLUSTER", "SETSLOT", "100", "IMPORTING", "node123"]))
3036                .unwrap(),
3037            Command::ClusterSetSlotImporting {
3038                slot: 100,
3039                node_id: "node123".into()
3040            },
3041        );
3042    }
3043
3044    #[test]
3045    fn cluster_setslot_migrating() {
3046        assert_eq!(
3047            Command::from_frame(cmd(&["CLUSTER", "SETSLOT", "200", "MIGRATING", "node456"]))
3048                .unwrap(),
3049            Command::ClusterSetSlotMigrating {
3050                slot: 200,
3051                node_id: "node456".into()
3052            },
3053        );
3054    }
3055
3056    #[test]
3057    fn cluster_setslot_node() {
3058        assert_eq!(
3059            Command::from_frame(cmd(&["CLUSTER", "SETSLOT", "300", "NODE", "node789"])).unwrap(),
3060            Command::ClusterSetSlotNode {
3061                slot: 300,
3062                node_id: "node789".into()
3063            },
3064        );
3065    }
3066
3067    #[test]
3068    fn cluster_setslot_stable() {
3069        assert_eq!(
3070            Command::from_frame(cmd(&["CLUSTER", "SETSLOT", "400", "STABLE"])).unwrap(),
3071            Command::ClusterSetSlotStable { slot: 400 },
3072        );
3073    }
3074
3075    #[test]
3076    fn cluster_setslot_invalid_slot() {
3077        let err =
3078            Command::from_frame(cmd(&["CLUSTER", "SETSLOT", "notanumber", "STABLE"])).unwrap_err();
3079        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
3080    }
3081
3082    #[test]
3083    fn cluster_setslot_wrong_arity() {
3084        let err = Command::from_frame(cmd(&["CLUSTER", "SETSLOT", "100"])).unwrap_err();
3085        assert!(matches!(err, ProtocolError::WrongArity(_)));
3086    }
3087
3088    #[test]
3089    fn migrate_basic() {
3090        assert_eq!(
3091            Command::from_frame(cmd(&["MIGRATE", "127.0.0.1", "6379", "mykey", "0", "5000"]))
3092                .unwrap(),
3093            Command::Migrate {
3094                host: "127.0.0.1".into(),
3095                port: 6379,
3096                key: "mykey".into(),
3097                db: 0,
3098                timeout_ms: 5000,
3099                copy: false,
3100                replace: false,
3101            },
3102        );
3103    }
3104
3105    #[test]
3106    fn migrate_with_options() {
3107        assert_eq!(
3108            Command::from_frame(cmd(&[
3109                "MIGRATE",
3110                "192.168.1.1",
3111                "6380",
3112                "testkey",
3113                "1",
3114                "10000",
3115                "COPY",
3116                "REPLACE"
3117            ]))
3118            .unwrap(),
3119            Command::Migrate {
3120                host: "192.168.1.1".into(),
3121                port: 6380,
3122                key: "testkey".into(),
3123                db: 1,
3124                timeout_ms: 10000,
3125                copy: true,
3126                replace: true,
3127            },
3128        );
3129    }
3130
3131    #[test]
3132    fn migrate_wrong_arity() {
3133        let err = Command::from_frame(cmd(&["MIGRATE", "host", "port", "key"])).unwrap_err();
3134        assert!(matches!(err, ProtocolError::WrongArity(_)));
3135    }
3136
3137    #[test]
3138    fn migrate_invalid_port() {
3139        let err = Command::from_frame(cmd(&["MIGRATE", "host", "notaport", "key", "0", "1000"]))
3140            .unwrap_err();
3141        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
3142    }
3143
3144    #[test]
3145    fn cluster_meet_basic() {
3146        assert_eq!(
3147            Command::from_frame(cmd(&["CLUSTER", "MEET", "192.168.1.1", "6379"])).unwrap(),
3148            Command::ClusterMeet {
3149                ip: "192.168.1.1".into(),
3150                port: 6379
3151            },
3152        );
3153    }
3154
3155    #[test]
3156    fn cluster_addslots_basic() {
3157        assert_eq!(
3158            Command::from_frame(cmd(&["CLUSTER", "ADDSLOTS", "0", "1", "2"])).unwrap(),
3159            Command::ClusterAddSlots {
3160                slots: vec![0, 1, 2]
3161            },
3162        );
3163    }
3164
3165    #[test]
3166    fn cluster_delslots_basic() {
3167        assert_eq!(
3168            Command::from_frame(cmd(&["CLUSTER", "DELSLOTS", "100", "101"])).unwrap(),
3169            Command::ClusterDelSlots {
3170                slots: vec![100, 101]
3171            },
3172        );
3173    }
3174
3175    #[test]
3176    fn cluster_forget_basic() {
3177        assert_eq!(
3178            Command::from_frame(cmd(&["CLUSTER", "FORGET", "abc123"])).unwrap(),
3179            Command::ClusterForget {
3180                node_id: "abc123".into()
3181            },
3182        );
3183    }
3184
3185    #[test]
3186    fn cluster_replicate_basic() {
3187        assert_eq!(
3188            Command::from_frame(cmd(&["CLUSTER", "REPLICATE", "master-id"])).unwrap(),
3189            Command::ClusterReplicate {
3190                node_id: "master-id".into()
3191            },
3192        );
3193    }
3194
3195    #[test]
3196    fn cluster_failover_basic() {
3197        assert_eq!(
3198            Command::from_frame(cmd(&["CLUSTER", "FAILOVER"])).unwrap(),
3199            Command::ClusterFailover {
3200                force: false,
3201                takeover: false
3202            },
3203        );
3204    }
3205
3206    #[test]
3207    fn cluster_failover_force() {
3208        assert_eq!(
3209            Command::from_frame(cmd(&["CLUSTER", "FAILOVER", "FORCE"])).unwrap(),
3210            Command::ClusterFailover {
3211                force: true,
3212                takeover: false
3213            },
3214        );
3215    }
3216
3217    #[test]
3218    fn cluster_failover_takeover() {
3219        assert_eq!(
3220            Command::from_frame(cmd(&["CLUSTER", "FAILOVER", "TAKEOVER"])).unwrap(),
3221            Command::ClusterFailover {
3222                force: false,
3223                takeover: true
3224            },
3225        );
3226    }
3227
3228    #[test]
3229    fn cluster_countkeysinslot_basic() {
3230        assert_eq!(
3231            Command::from_frame(cmd(&["CLUSTER", "COUNTKEYSINSLOT", "100"])).unwrap(),
3232            Command::ClusterCountKeysInSlot { slot: 100 },
3233        );
3234    }
3235
3236    #[test]
3237    fn cluster_getkeysinslot_basic() {
3238        assert_eq!(
3239            Command::from_frame(cmd(&["CLUSTER", "GETKEYSINSLOT", "200", "10"])).unwrap(),
3240            Command::ClusterGetKeysInSlot {
3241                slot: 200,
3242                count: 10
3243            },
3244        );
3245    }
3246}