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