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    /// A command we don't recognize (yet).
200    Unknown(String),
201}
202
203/// Flags for the ZADD command.
204#[derive(Debug, Clone, Default, PartialEq)]
205pub struct ZAddFlags {
206    /// Only add new members, don't update existing scores.
207    pub nx: bool,
208    /// Only update existing members, don't add new ones.
209    pub xx: bool,
210    /// Only update when new score > current score.
211    pub gt: bool,
212    /// Only update when new score < current score.
213    pub lt: bool,
214    /// Return count of changed members (added + updated) instead of just added.
215    pub ch: bool,
216}
217
218impl Eq for ZAddFlags {}
219
220impl Command {
221    /// Parses a [`Frame`] into a [`Command`].
222    ///
223    /// Expects an array frame where the first element is the command name
224    /// (as a bulk or simple string) and the rest are arguments.
225    pub fn from_frame(frame: Frame) -> Result<Command, ProtocolError> {
226        let frames = match frame {
227            Frame::Array(frames) => frames,
228            _ => {
229                return Err(ProtocolError::InvalidCommandFrame(
230                    "expected array frame".into(),
231                ));
232            }
233        };
234
235        if frames.is_empty() {
236            return Err(ProtocolError::InvalidCommandFrame(
237                "empty command array".into(),
238            ));
239        }
240
241        let name = extract_string(&frames[0])?;
242        let name_upper = name.to_ascii_uppercase();
243
244        match name_upper.as_str() {
245            "PING" => parse_ping(&frames[1..]),
246            "ECHO" => parse_echo(&frames[1..]),
247            "GET" => parse_get(&frames[1..]),
248            "SET" => parse_set(&frames[1..]),
249            "INCR" => parse_incr(&frames[1..]),
250            "DECR" => parse_decr(&frames[1..]),
251            "DEL" => parse_del(&frames[1..]),
252            "EXISTS" => parse_exists(&frames[1..]),
253            "MGET" => parse_mget(&frames[1..]),
254            "MSET" => parse_mset(&frames[1..]),
255            "EXPIRE" => parse_expire(&frames[1..]),
256            "TTL" => parse_ttl(&frames[1..]),
257            "PERSIST" => parse_persist(&frames[1..]),
258            "PTTL" => parse_pttl(&frames[1..]),
259            "PEXPIRE" => parse_pexpire(&frames[1..]),
260            "DBSIZE" => parse_dbsize(&frames[1..]),
261            "INFO" => parse_info(&frames[1..]),
262            "BGSAVE" => parse_bgsave(&frames[1..]),
263            "BGREWRITEAOF" => parse_bgrewriteaof(&frames[1..]),
264            "FLUSHDB" => parse_flushdb(&frames[1..]),
265            "SCAN" => parse_scan(&frames[1..]),
266            "LPUSH" => parse_lpush(&frames[1..]),
267            "RPUSH" => parse_rpush(&frames[1..]),
268            "LPOP" => parse_lpop(&frames[1..]),
269            "RPOP" => parse_rpop(&frames[1..]),
270            "LRANGE" => parse_lrange(&frames[1..]),
271            "LLEN" => parse_llen(&frames[1..]),
272            "TYPE" => parse_type(&frames[1..]),
273            "ZADD" => parse_zadd(&frames[1..]),
274            "ZREM" => parse_zrem(&frames[1..]),
275            "ZSCORE" => parse_zscore(&frames[1..]),
276            "ZRANK" => parse_zrank(&frames[1..]),
277            "ZCARD" => parse_zcard(&frames[1..]),
278            "ZRANGE" => parse_zrange(&frames[1..]),
279            "HSET" => parse_hset(&frames[1..]),
280            "HGET" => parse_hget(&frames[1..]),
281            "HGETALL" => parse_hgetall(&frames[1..]),
282            "HDEL" => parse_hdel(&frames[1..]),
283            "HEXISTS" => parse_hexists(&frames[1..]),
284            "HLEN" => parse_hlen(&frames[1..]),
285            "HINCRBY" => parse_hincrby(&frames[1..]),
286            "HKEYS" => parse_hkeys(&frames[1..]),
287            "HVALS" => parse_hvals(&frames[1..]),
288            "HMGET" => parse_hmget(&frames[1..]),
289            "SADD" => parse_sadd(&frames[1..]),
290            "SREM" => parse_srem(&frames[1..]),
291            "SMEMBERS" => parse_smembers(&frames[1..]),
292            "SISMEMBER" => parse_sismember(&frames[1..]),
293            "SCARD" => parse_scard(&frames[1..]),
294            _ => Ok(Command::Unknown(name)),
295        }
296    }
297}
298
299/// Extracts a UTF-8 string from a Bulk or Simple frame.
300fn extract_string(frame: &Frame) -> Result<String, ProtocolError> {
301    match frame {
302        Frame::Bulk(data) => String::from_utf8(data.to_vec()).map_err(|_| {
303            ProtocolError::InvalidCommandFrame("command name is not valid utf-8".into())
304        }),
305        Frame::Simple(s) => Ok(s.clone()),
306        _ => Err(ProtocolError::InvalidCommandFrame(
307            "expected bulk or simple string for command name".into(),
308        )),
309    }
310}
311
312/// Extracts raw bytes from a Bulk or Simple frame.
313fn extract_bytes(frame: &Frame) -> Result<Bytes, ProtocolError> {
314    match frame {
315        Frame::Bulk(data) => Ok(data.clone()),
316        Frame::Simple(s) => Ok(Bytes::from(s.clone().into_bytes())),
317        _ => Err(ProtocolError::InvalidCommandFrame(
318            "expected bulk or simple string argument".into(),
319        )),
320    }
321}
322
323/// Parses a string argument as a positive u64.
324fn parse_u64(frame: &Frame, cmd: &str) -> Result<u64, ProtocolError> {
325    let s = extract_string(frame)?;
326    s.parse::<u64>().map_err(|_| {
327        ProtocolError::InvalidCommandFrame(format!("value is not a valid integer for '{cmd}'"))
328    })
329}
330
331fn parse_ping(args: &[Frame]) -> Result<Command, ProtocolError> {
332    match args.len() {
333        0 => Ok(Command::Ping(None)),
334        1 => {
335            let msg = extract_bytes(&args[0])?;
336            Ok(Command::Ping(Some(msg)))
337        }
338        _ => Err(ProtocolError::WrongArity("PING".into())),
339    }
340}
341
342fn parse_echo(args: &[Frame]) -> Result<Command, ProtocolError> {
343    if args.len() != 1 {
344        return Err(ProtocolError::WrongArity("ECHO".into()));
345    }
346    let msg = extract_bytes(&args[0])?;
347    Ok(Command::Echo(msg))
348}
349
350fn parse_get(args: &[Frame]) -> Result<Command, ProtocolError> {
351    if args.len() != 1 {
352        return Err(ProtocolError::WrongArity("GET".into()));
353    }
354    let key = extract_string(&args[0])?;
355    Ok(Command::Get { key })
356}
357
358fn parse_set(args: &[Frame]) -> Result<Command, ProtocolError> {
359    if args.len() < 2 {
360        return Err(ProtocolError::WrongArity("SET".into()));
361    }
362
363    let key = extract_string(&args[0])?;
364    let value = extract_bytes(&args[1])?;
365
366    let mut expire = None;
367    let mut nx = false;
368    let mut xx = false;
369    let mut idx = 2;
370
371    while idx < args.len() {
372        let flag = extract_string(&args[idx])?.to_ascii_uppercase();
373        match flag.as_str() {
374            "NX" => {
375                nx = true;
376                idx += 1;
377            }
378            "XX" => {
379                xx = true;
380                idx += 1;
381            }
382            "EX" => {
383                idx += 1;
384                if idx >= args.len() {
385                    return Err(ProtocolError::WrongArity("SET".into()));
386                }
387                let amount = parse_u64(&args[idx], "SET")?;
388                if amount == 0 {
389                    return Err(ProtocolError::InvalidCommandFrame(
390                        "invalid expire time in 'SET' command".into(),
391                    ));
392                }
393                expire = Some(SetExpire::Ex(amount));
394                idx += 1;
395            }
396            "PX" => {
397                idx += 1;
398                if idx >= args.len() {
399                    return Err(ProtocolError::WrongArity("SET".into()));
400                }
401                let amount = parse_u64(&args[idx], "SET")?;
402                if amount == 0 {
403                    return Err(ProtocolError::InvalidCommandFrame(
404                        "invalid expire time in 'SET' command".into(),
405                    ));
406                }
407                expire = Some(SetExpire::Px(amount));
408                idx += 1;
409            }
410            _ => {
411                return Err(ProtocolError::InvalidCommandFrame(format!(
412                    "unsupported SET option '{flag}'"
413                )));
414            }
415        }
416    }
417
418    if nx && xx {
419        return Err(ProtocolError::InvalidCommandFrame(
420            "XX and NX options at the same time are not compatible".into(),
421        ));
422    }
423
424    Ok(Command::Set {
425        key,
426        value,
427        expire,
428        nx,
429        xx,
430    })
431}
432
433fn parse_incr(args: &[Frame]) -> Result<Command, ProtocolError> {
434    if args.len() != 1 {
435        return Err(ProtocolError::WrongArity("INCR".into()));
436    }
437    let key = extract_string(&args[0])?;
438    Ok(Command::Incr { key })
439}
440
441fn parse_decr(args: &[Frame]) -> Result<Command, ProtocolError> {
442    if args.len() != 1 {
443        return Err(ProtocolError::WrongArity("DECR".into()));
444    }
445    let key = extract_string(&args[0])?;
446    Ok(Command::Decr { key })
447}
448
449fn parse_del(args: &[Frame]) -> Result<Command, ProtocolError> {
450    if args.is_empty() {
451        return Err(ProtocolError::WrongArity("DEL".into()));
452    }
453    let keys = args
454        .iter()
455        .map(extract_string)
456        .collect::<Result<Vec<_>, _>>()?;
457    Ok(Command::Del { keys })
458}
459
460fn parse_exists(args: &[Frame]) -> Result<Command, ProtocolError> {
461    if args.is_empty() {
462        return Err(ProtocolError::WrongArity("EXISTS".into()));
463    }
464    let keys = args
465        .iter()
466        .map(extract_string)
467        .collect::<Result<Vec<_>, _>>()?;
468    Ok(Command::Exists { keys })
469}
470
471fn parse_mget(args: &[Frame]) -> Result<Command, ProtocolError> {
472    if args.is_empty() {
473        return Err(ProtocolError::WrongArity("MGET".into()));
474    }
475    let keys = args
476        .iter()
477        .map(extract_string)
478        .collect::<Result<Vec<_>, _>>()?;
479    Ok(Command::MGet { keys })
480}
481
482fn parse_mset(args: &[Frame]) -> Result<Command, ProtocolError> {
483    if args.is_empty() || !args.len().is_multiple_of(2) {
484        return Err(ProtocolError::WrongArity("MSET".into()));
485    }
486    let mut pairs = Vec::with_capacity(args.len() / 2);
487    for chunk in args.chunks(2) {
488        let key = extract_string(&chunk[0])?;
489        let value = extract_bytes(&chunk[1])?;
490        pairs.push((key, value));
491    }
492    Ok(Command::MSet { pairs })
493}
494
495fn parse_expire(args: &[Frame]) -> Result<Command, ProtocolError> {
496    if args.len() != 2 {
497        return Err(ProtocolError::WrongArity("EXPIRE".into()));
498    }
499    let key = extract_string(&args[0])?;
500    let seconds = parse_u64(&args[1], "EXPIRE")?;
501
502    if seconds == 0 {
503        return Err(ProtocolError::InvalidCommandFrame(
504            "invalid expire time in 'EXPIRE' command".into(),
505        ));
506    }
507
508    Ok(Command::Expire { key, seconds })
509}
510
511fn parse_ttl(args: &[Frame]) -> Result<Command, ProtocolError> {
512    if args.len() != 1 {
513        return Err(ProtocolError::WrongArity("TTL".into()));
514    }
515    let key = extract_string(&args[0])?;
516    Ok(Command::Ttl { key })
517}
518
519fn parse_persist(args: &[Frame]) -> Result<Command, ProtocolError> {
520    if args.len() != 1 {
521        return Err(ProtocolError::WrongArity("PERSIST".into()));
522    }
523    let key = extract_string(&args[0])?;
524    Ok(Command::Persist { key })
525}
526
527fn parse_pttl(args: &[Frame]) -> Result<Command, ProtocolError> {
528    if args.len() != 1 {
529        return Err(ProtocolError::WrongArity("PTTL".into()));
530    }
531    let key = extract_string(&args[0])?;
532    Ok(Command::Pttl { key })
533}
534
535fn parse_pexpire(args: &[Frame]) -> Result<Command, ProtocolError> {
536    if args.len() != 2 {
537        return Err(ProtocolError::WrongArity("PEXPIRE".into()));
538    }
539    let key = extract_string(&args[0])?;
540    let milliseconds = parse_u64(&args[1], "PEXPIRE")?;
541
542    if milliseconds == 0 {
543        return Err(ProtocolError::InvalidCommandFrame(
544            "invalid expire time in 'PEXPIRE' command".into(),
545        ));
546    }
547
548    Ok(Command::Pexpire { key, milliseconds })
549}
550
551fn parse_dbsize(args: &[Frame]) -> Result<Command, ProtocolError> {
552    if !args.is_empty() {
553        return Err(ProtocolError::WrongArity("DBSIZE".into()));
554    }
555    Ok(Command::DbSize)
556}
557
558fn parse_info(args: &[Frame]) -> Result<Command, ProtocolError> {
559    match args.len() {
560        0 => Ok(Command::Info { section: None }),
561        1 => {
562            let section = extract_string(&args[0])?;
563            Ok(Command::Info {
564                section: Some(section),
565            })
566        }
567        _ => Err(ProtocolError::WrongArity("INFO".into())),
568    }
569}
570
571fn parse_bgsave(args: &[Frame]) -> Result<Command, ProtocolError> {
572    if !args.is_empty() {
573        return Err(ProtocolError::WrongArity("BGSAVE".into()));
574    }
575    Ok(Command::BgSave)
576}
577
578fn parse_bgrewriteaof(args: &[Frame]) -> Result<Command, ProtocolError> {
579    if !args.is_empty() {
580        return Err(ProtocolError::WrongArity("BGREWRITEAOF".into()));
581    }
582    Ok(Command::BgRewriteAof)
583}
584
585fn parse_flushdb(args: &[Frame]) -> Result<Command, ProtocolError> {
586    if !args.is_empty() {
587        return Err(ProtocolError::WrongArity("FLUSHDB".into()));
588    }
589    Ok(Command::FlushDb)
590}
591
592fn parse_scan(args: &[Frame]) -> Result<Command, ProtocolError> {
593    if args.is_empty() {
594        return Err(ProtocolError::WrongArity("SCAN".into()));
595    }
596
597    let cursor = parse_u64(&args[0], "SCAN")?;
598    let mut pattern = None;
599    let mut count = None;
600    let mut idx = 1;
601
602    while idx < args.len() {
603        let flag = extract_string(&args[idx])?.to_ascii_uppercase();
604        match flag.as_str() {
605            "MATCH" => {
606                idx += 1;
607                if idx >= args.len() {
608                    return Err(ProtocolError::WrongArity("SCAN".into()));
609                }
610                pattern = Some(extract_string(&args[idx])?);
611                idx += 1;
612            }
613            "COUNT" => {
614                idx += 1;
615                if idx >= args.len() {
616                    return Err(ProtocolError::WrongArity("SCAN".into()));
617                }
618                let n = parse_u64(&args[idx], "SCAN")?;
619                count = Some(n as usize);
620                idx += 1;
621            }
622            _ => {
623                return Err(ProtocolError::InvalidCommandFrame(format!(
624                    "unsupported SCAN option '{flag}'"
625                )));
626            }
627        }
628    }
629
630    Ok(Command::Scan {
631        cursor,
632        pattern,
633        count,
634    })
635}
636
637/// Parses a string argument as an i64. Used by LRANGE for start/stop indices.
638fn parse_i64(frame: &Frame, cmd: &str) -> Result<i64, ProtocolError> {
639    let s = extract_string(frame)?;
640    s.parse::<i64>().map_err(|_| {
641        ProtocolError::InvalidCommandFrame(format!("value is not a valid integer for '{cmd}'"))
642    })
643}
644
645fn parse_lpush(args: &[Frame]) -> Result<Command, ProtocolError> {
646    if args.len() < 2 {
647        return Err(ProtocolError::WrongArity("LPUSH".into()));
648    }
649    let key = extract_string(&args[0])?;
650    let values = args[1..]
651        .iter()
652        .map(extract_bytes)
653        .collect::<Result<Vec<_>, _>>()?;
654    Ok(Command::LPush { key, values })
655}
656
657fn parse_rpush(args: &[Frame]) -> Result<Command, ProtocolError> {
658    if args.len() < 2 {
659        return Err(ProtocolError::WrongArity("RPUSH".into()));
660    }
661    let key = extract_string(&args[0])?;
662    let values = args[1..]
663        .iter()
664        .map(extract_bytes)
665        .collect::<Result<Vec<_>, _>>()?;
666    Ok(Command::RPush { key, values })
667}
668
669fn parse_lpop(args: &[Frame]) -> Result<Command, ProtocolError> {
670    if args.len() != 1 {
671        return Err(ProtocolError::WrongArity("LPOP".into()));
672    }
673    let key = extract_string(&args[0])?;
674    Ok(Command::LPop { key })
675}
676
677fn parse_rpop(args: &[Frame]) -> Result<Command, ProtocolError> {
678    if args.len() != 1 {
679        return Err(ProtocolError::WrongArity("RPOP".into()));
680    }
681    let key = extract_string(&args[0])?;
682    Ok(Command::RPop { key })
683}
684
685fn parse_lrange(args: &[Frame]) -> Result<Command, ProtocolError> {
686    if args.len() != 3 {
687        return Err(ProtocolError::WrongArity("LRANGE".into()));
688    }
689    let key = extract_string(&args[0])?;
690    let start = parse_i64(&args[1], "LRANGE")?;
691    let stop = parse_i64(&args[2], "LRANGE")?;
692    Ok(Command::LRange { key, start, stop })
693}
694
695fn parse_llen(args: &[Frame]) -> Result<Command, ProtocolError> {
696    if args.len() != 1 {
697        return Err(ProtocolError::WrongArity("LLEN".into()));
698    }
699    let key = extract_string(&args[0])?;
700    Ok(Command::LLen { key })
701}
702
703fn parse_type(args: &[Frame]) -> Result<Command, ProtocolError> {
704    if args.len() != 1 {
705        return Err(ProtocolError::WrongArity("TYPE".into()));
706    }
707    let key = extract_string(&args[0])?;
708    Ok(Command::Type { key })
709}
710
711/// Parses a string argument as an f64 score.
712fn parse_f64(frame: &Frame, cmd: &str) -> Result<f64, ProtocolError> {
713    let s = extract_string(frame)?;
714    let v = s.parse::<f64>().map_err(|_| {
715        ProtocolError::InvalidCommandFrame(format!("value is not a valid float for '{cmd}'"))
716    })?;
717    if v.is_nan() {
718        return Err(ProtocolError::InvalidCommandFrame(format!(
719            "NaN is not a valid score for '{cmd}'"
720        )));
721    }
722    Ok(v)
723}
724
725fn parse_zadd(args: &[Frame]) -> Result<Command, ProtocolError> {
726    // ZADD key [NX|XX] [GT|LT] [CH] score member [score member ...]
727    if args.len() < 3 {
728        return Err(ProtocolError::WrongArity("ZADD".into()));
729    }
730
731    let key = extract_string(&args[0])?;
732    let mut flags = ZAddFlags::default();
733    let mut idx = 1;
734
735    // parse optional flags before score/member pairs
736    while idx < args.len() {
737        let s = extract_string(&args[idx])?.to_ascii_uppercase();
738        match s.as_str() {
739            "NX" => {
740                flags.nx = true;
741                idx += 1;
742            }
743            "XX" => {
744                flags.xx = true;
745                idx += 1;
746            }
747            "GT" => {
748                flags.gt = true;
749                idx += 1;
750            }
751            "LT" => {
752                flags.lt = true;
753                idx += 1;
754            }
755            "CH" => {
756                flags.ch = true;
757                idx += 1;
758            }
759            _ => break,
760        }
761    }
762
763    // NX and XX are mutually exclusive
764    if flags.nx && flags.xx {
765        return Err(ProtocolError::InvalidCommandFrame(
766            "XX and NX options at the same time are not compatible".into(),
767        ));
768    }
769    // GT and LT are mutually exclusive
770    if flags.gt && flags.lt {
771        return Err(ProtocolError::InvalidCommandFrame(
772            "GT and LT options at the same time are not compatible".into(),
773        ));
774    }
775
776    // remaining args must be score/member pairs
777    let remaining = &args[idx..];
778    if remaining.is_empty() || !remaining.len().is_multiple_of(2) {
779        return Err(ProtocolError::WrongArity("ZADD".into()));
780    }
781
782    let mut members = Vec::with_capacity(remaining.len() / 2);
783    for pair in remaining.chunks(2) {
784        let score = parse_f64(&pair[0], "ZADD")?;
785        let member = extract_string(&pair[1])?;
786        members.push((score, member));
787    }
788
789    Ok(Command::ZAdd {
790        key,
791        flags,
792        members,
793    })
794}
795
796fn parse_zcard(args: &[Frame]) -> Result<Command, ProtocolError> {
797    if args.len() != 1 {
798        return Err(ProtocolError::WrongArity("ZCARD".into()));
799    }
800    let key = extract_string(&args[0])?;
801    Ok(Command::ZCard { key })
802}
803
804fn parse_zrem(args: &[Frame]) -> Result<Command, ProtocolError> {
805    if args.len() < 2 {
806        return Err(ProtocolError::WrongArity("ZREM".into()));
807    }
808    let key = extract_string(&args[0])?;
809    let members = args[1..]
810        .iter()
811        .map(extract_string)
812        .collect::<Result<Vec<_>, _>>()?;
813    Ok(Command::ZRem { key, members })
814}
815
816fn parse_zscore(args: &[Frame]) -> Result<Command, ProtocolError> {
817    if args.len() != 2 {
818        return Err(ProtocolError::WrongArity("ZSCORE".into()));
819    }
820    let key = extract_string(&args[0])?;
821    let member = extract_string(&args[1])?;
822    Ok(Command::ZScore { key, member })
823}
824
825fn parse_zrank(args: &[Frame]) -> Result<Command, ProtocolError> {
826    if args.len() != 2 {
827        return Err(ProtocolError::WrongArity("ZRANK".into()));
828    }
829    let key = extract_string(&args[0])?;
830    let member = extract_string(&args[1])?;
831    Ok(Command::ZRank { key, member })
832}
833
834fn parse_zrange(args: &[Frame]) -> Result<Command, ProtocolError> {
835    if args.len() < 3 || args.len() > 4 {
836        return Err(ProtocolError::WrongArity("ZRANGE".into()));
837    }
838    let key = extract_string(&args[0])?;
839    let start = parse_i64(&args[1], "ZRANGE")?;
840    let stop = parse_i64(&args[2], "ZRANGE")?;
841
842    let with_scores = if args.len() == 4 {
843        let opt = extract_string(&args[3])?.to_ascii_uppercase();
844        if opt != "WITHSCORES" {
845            return Err(ProtocolError::InvalidCommandFrame(format!(
846                "unsupported ZRANGE option '{opt}'"
847            )));
848        }
849        true
850    } else {
851        false
852    };
853
854    Ok(Command::ZRange {
855        key,
856        start,
857        stop,
858        with_scores,
859    })
860}
861
862// --- hash commands ---
863
864fn parse_hset(args: &[Frame]) -> Result<Command, ProtocolError> {
865    // HSET key field value [field value ...]
866    // args = [key, field, value, ...]
867    // Need at least 3 args, and after key we need pairs (so remaining count must be even)
868    if args.len() < 3 || !(args.len() - 1).is_multiple_of(2) {
869        return Err(ProtocolError::WrongArity("HSET".into()));
870    }
871
872    let key = extract_string(&args[0])?;
873    let mut fields = Vec::with_capacity((args.len() - 1) / 2);
874
875    for chunk in args[1..].chunks(2) {
876        let field = extract_string(&chunk[0])?;
877        let value = extract_bytes(&chunk[1])?;
878        fields.push((field, value));
879    }
880
881    Ok(Command::HSet { key, fields })
882}
883
884fn parse_hget(args: &[Frame]) -> Result<Command, ProtocolError> {
885    if args.len() != 2 {
886        return Err(ProtocolError::WrongArity("HGET".into()));
887    }
888    let key = extract_string(&args[0])?;
889    let field = extract_string(&args[1])?;
890    Ok(Command::HGet { key, field })
891}
892
893fn parse_hgetall(args: &[Frame]) -> Result<Command, ProtocolError> {
894    if args.len() != 1 {
895        return Err(ProtocolError::WrongArity("HGETALL".into()));
896    }
897    let key = extract_string(&args[0])?;
898    Ok(Command::HGetAll { key })
899}
900
901fn parse_hdel(args: &[Frame]) -> Result<Command, ProtocolError> {
902    if args.len() < 2 {
903        return Err(ProtocolError::WrongArity("HDEL".into()));
904    }
905    let key = extract_string(&args[0])?;
906    let fields = args[1..]
907        .iter()
908        .map(extract_string)
909        .collect::<Result<Vec<_>, _>>()?;
910    Ok(Command::HDel { key, fields })
911}
912
913fn parse_hexists(args: &[Frame]) -> Result<Command, ProtocolError> {
914    if args.len() != 2 {
915        return Err(ProtocolError::WrongArity("HEXISTS".into()));
916    }
917    let key = extract_string(&args[0])?;
918    let field = extract_string(&args[1])?;
919    Ok(Command::HExists { key, field })
920}
921
922fn parse_hlen(args: &[Frame]) -> Result<Command, ProtocolError> {
923    if args.len() != 1 {
924        return Err(ProtocolError::WrongArity("HLEN".into()));
925    }
926    let key = extract_string(&args[0])?;
927    Ok(Command::HLen { key })
928}
929
930fn parse_hincrby(args: &[Frame]) -> Result<Command, ProtocolError> {
931    if args.len() != 3 {
932        return Err(ProtocolError::WrongArity("HINCRBY".into()));
933    }
934    let key = extract_string(&args[0])?;
935    let field = extract_string(&args[1])?;
936    let delta = parse_i64(&args[2], "HINCRBY")?;
937    Ok(Command::HIncrBy { key, field, delta })
938}
939
940fn parse_hkeys(args: &[Frame]) -> Result<Command, ProtocolError> {
941    if args.len() != 1 {
942        return Err(ProtocolError::WrongArity("HKEYS".into()));
943    }
944    let key = extract_string(&args[0])?;
945    Ok(Command::HKeys { key })
946}
947
948fn parse_hvals(args: &[Frame]) -> Result<Command, ProtocolError> {
949    if args.len() != 1 {
950        return Err(ProtocolError::WrongArity("HVALS".into()));
951    }
952    let key = extract_string(&args[0])?;
953    Ok(Command::HVals { key })
954}
955
956fn parse_hmget(args: &[Frame]) -> Result<Command, ProtocolError> {
957    if args.len() < 2 {
958        return Err(ProtocolError::WrongArity("HMGET".into()));
959    }
960    let key = extract_string(&args[0])?;
961    let fields = args[1..]
962        .iter()
963        .map(extract_string)
964        .collect::<Result<Vec<_>, _>>()?;
965    Ok(Command::HMGet { key, fields })
966}
967
968// --- set commands ---
969
970fn parse_sadd(args: &[Frame]) -> Result<Command, ProtocolError> {
971    if args.len() < 2 {
972        return Err(ProtocolError::WrongArity("SADD".into()));
973    }
974    let key = extract_string(&args[0])?;
975    let members = args[1..]
976        .iter()
977        .map(extract_string)
978        .collect::<Result<Vec<_>, _>>()?;
979    Ok(Command::SAdd { key, members })
980}
981
982fn parse_srem(args: &[Frame]) -> Result<Command, ProtocolError> {
983    if args.len() < 2 {
984        return Err(ProtocolError::WrongArity("SREM".into()));
985    }
986    let key = extract_string(&args[0])?;
987    let members = args[1..]
988        .iter()
989        .map(extract_string)
990        .collect::<Result<Vec<_>, _>>()?;
991    Ok(Command::SRem { key, members })
992}
993
994fn parse_smembers(args: &[Frame]) -> Result<Command, ProtocolError> {
995    if args.len() != 1 {
996        return Err(ProtocolError::WrongArity("SMEMBERS".into()));
997    }
998    let key = extract_string(&args[0])?;
999    Ok(Command::SMembers { key })
1000}
1001
1002fn parse_sismember(args: &[Frame]) -> Result<Command, ProtocolError> {
1003    if args.len() != 2 {
1004        return Err(ProtocolError::WrongArity("SISMEMBER".into()));
1005    }
1006    let key = extract_string(&args[0])?;
1007    let member = extract_string(&args[1])?;
1008    Ok(Command::SIsMember { key, member })
1009}
1010
1011fn parse_scard(args: &[Frame]) -> Result<Command, ProtocolError> {
1012    if args.len() != 1 {
1013        return Err(ProtocolError::WrongArity("SCARD".into()));
1014    }
1015    let key = extract_string(&args[0])?;
1016    Ok(Command::SCard { key })
1017}
1018
1019#[cfg(test)]
1020mod tests {
1021    use super::*;
1022
1023    /// Helper: build an array frame from bulk strings.
1024    fn cmd(parts: &[&str]) -> Frame {
1025        Frame::Array(
1026            parts
1027                .iter()
1028                .map(|s| Frame::Bulk(Bytes::from(s.to_string())))
1029                .collect(),
1030        )
1031    }
1032
1033    // --- ping ---
1034
1035    #[test]
1036    fn ping_no_args() {
1037        assert_eq!(
1038            Command::from_frame(cmd(&["PING"])).unwrap(),
1039            Command::Ping(None),
1040        );
1041    }
1042
1043    #[test]
1044    fn ping_with_message() {
1045        assert_eq!(
1046            Command::from_frame(cmd(&["PING", "hello"])).unwrap(),
1047            Command::Ping(Some(Bytes::from("hello"))),
1048        );
1049    }
1050
1051    #[test]
1052    fn ping_case_insensitive() {
1053        assert_eq!(
1054            Command::from_frame(cmd(&["ping"])).unwrap(),
1055            Command::Ping(None),
1056        );
1057        assert_eq!(
1058            Command::from_frame(cmd(&["Ping"])).unwrap(),
1059            Command::Ping(None),
1060        );
1061    }
1062
1063    #[test]
1064    fn ping_too_many_args() {
1065        let err = Command::from_frame(cmd(&["PING", "a", "b"])).unwrap_err();
1066        assert!(matches!(err, ProtocolError::WrongArity(_)));
1067    }
1068
1069    // --- echo ---
1070
1071    #[test]
1072    fn echo() {
1073        assert_eq!(
1074            Command::from_frame(cmd(&["ECHO", "test"])).unwrap(),
1075            Command::Echo(Bytes::from("test")),
1076        );
1077    }
1078
1079    #[test]
1080    fn echo_missing_arg() {
1081        let err = Command::from_frame(cmd(&["ECHO"])).unwrap_err();
1082        assert!(matches!(err, ProtocolError::WrongArity(_)));
1083    }
1084
1085    // --- get ---
1086
1087    #[test]
1088    fn get_basic() {
1089        assert_eq!(
1090            Command::from_frame(cmd(&["GET", "mykey"])).unwrap(),
1091            Command::Get {
1092                key: "mykey".into()
1093            },
1094        );
1095    }
1096
1097    #[test]
1098    fn get_no_args() {
1099        let err = Command::from_frame(cmd(&["GET"])).unwrap_err();
1100        assert!(matches!(err, ProtocolError::WrongArity(_)));
1101    }
1102
1103    #[test]
1104    fn get_too_many_args() {
1105        let err = Command::from_frame(cmd(&["GET", "a", "b"])).unwrap_err();
1106        assert!(matches!(err, ProtocolError::WrongArity(_)));
1107    }
1108
1109    #[test]
1110    fn get_case_insensitive() {
1111        assert_eq!(
1112            Command::from_frame(cmd(&["get", "k"])).unwrap(),
1113            Command::Get { key: "k".into() },
1114        );
1115    }
1116
1117    // --- set ---
1118
1119    #[test]
1120    fn set_basic() {
1121        assert_eq!(
1122            Command::from_frame(cmd(&["SET", "key", "value"])).unwrap(),
1123            Command::Set {
1124                key: "key".into(),
1125                value: Bytes::from("value"),
1126                expire: None,
1127                nx: false,
1128                xx: false,
1129            },
1130        );
1131    }
1132
1133    #[test]
1134    fn set_with_ex() {
1135        assert_eq!(
1136            Command::from_frame(cmd(&["SET", "key", "val", "EX", "10"])).unwrap(),
1137            Command::Set {
1138                key: "key".into(),
1139                value: Bytes::from("val"),
1140                expire: Some(SetExpire::Ex(10)),
1141                nx: false,
1142                xx: false,
1143            },
1144        );
1145    }
1146
1147    #[test]
1148    fn set_with_px() {
1149        assert_eq!(
1150            Command::from_frame(cmd(&["SET", "key", "val", "PX", "5000"])).unwrap(),
1151            Command::Set {
1152                key: "key".into(),
1153                value: Bytes::from("val"),
1154                expire: Some(SetExpire::Px(5000)),
1155                nx: false,
1156                xx: false,
1157            },
1158        );
1159    }
1160
1161    #[test]
1162    fn set_ex_case_insensitive() {
1163        assert_eq!(
1164            Command::from_frame(cmd(&["set", "k", "v", "ex", "5"])).unwrap(),
1165            Command::Set {
1166                key: "k".into(),
1167                value: Bytes::from("v"),
1168                expire: Some(SetExpire::Ex(5)),
1169                nx: false,
1170                xx: false,
1171            },
1172        );
1173    }
1174
1175    #[test]
1176    fn set_nx_flag() {
1177        assert_eq!(
1178            Command::from_frame(cmd(&["SET", "key", "val", "NX"])).unwrap(),
1179            Command::Set {
1180                key: "key".into(),
1181                value: Bytes::from("val"),
1182                expire: None,
1183                nx: true,
1184                xx: false,
1185            },
1186        );
1187    }
1188
1189    #[test]
1190    fn set_xx_flag() {
1191        assert_eq!(
1192            Command::from_frame(cmd(&["SET", "key", "val", "XX"])).unwrap(),
1193            Command::Set {
1194                key: "key".into(),
1195                value: Bytes::from("val"),
1196                expire: None,
1197                nx: false,
1198                xx: true,
1199            },
1200        );
1201    }
1202
1203    #[test]
1204    fn set_nx_with_ex() {
1205        assert_eq!(
1206            Command::from_frame(cmd(&["SET", "key", "val", "EX", "10", "NX"])).unwrap(),
1207            Command::Set {
1208                key: "key".into(),
1209                value: Bytes::from("val"),
1210                expire: Some(SetExpire::Ex(10)),
1211                nx: true,
1212                xx: false,
1213            },
1214        );
1215    }
1216
1217    #[test]
1218    fn set_nx_before_ex() {
1219        assert_eq!(
1220            Command::from_frame(cmd(&["SET", "key", "val", "NX", "PX", "5000"])).unwrap(),
1221            Command::Set {
1222                key: "key".into(),
1223                value: Bytes::from("val"),
1224                expire: Some(SetExpire::Px(5000)),
1225                nx: true,
1226                xx: false,
1227            },
1228        );
1229    }
1230
1231    #[test]
1232    fn set_nx_xx_conflict() {
1233        let err = Command::from_frame(cmd(&["SET", "k", "v", "NX", "XX"])).unwrap_err();
1234        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
1235    }
1236
1237    #[test]
1238    fn set_nx_case_insensitive() {
1239        assert_eq!(
1240            Command::from_frame(cmd(&["set", "k", "v", "nx"])).unwrap(),
1241            Command::Set {
1242                key: "k".into(),
1243                value: Bytes::from("v"),
1244                expire: None,
1245                nx: true,
1246                xx: false,
1247            },
1248        );
1249    }
1250
1251    #[test]
1252    fn set_missing_value() {
1253        let err = Command::from_frame(cmd(&["SET", "key"])).unwrap_err();
1254        assert!(matches!(err, ProtocolError::WrongArity(_)));
1255    }
1256
1257    #[test]
1258    fn set_invalid_expire_value() {
1259        let err = Command::from_frame(cmd(&["SET", "k", "v", "EX", "notanum"])).unwrap_err();
1260        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
1261    }
1262
1263    #[test]
1264    fn set_zero_expire() {
1265        let err = Command::from_frame(cmd(&["SET", "k", "v", "EX", "0"])).unwrap_err();
1266        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
1267    }
1268
1269    #[test]
1270    fn set_unknown_flag() {
1271        let err = Command::from_frame(cmd(&["SET", "k", "v", "ZZ", "10"])).unwrap_err();
1272        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
1273    }
1274
1275    #[test]
1276    fn set_incomplete_expire() {
1277        // EX without a value
1278        let err = Command::from_frame(cmd(&["SET", "k", "v", "EX"])).unwrap_err();
1279        assert!(matches!(err, ProtocolError::WrongArity(_)));
1280    }
1281
1282    // --- del ---
1283
1284    #[test]
1285    fn del_single() {
1286        assert_eq!(
1287            Command::from_frame(cmd(&["DEL", "key"])).unwrap(),
1288            Command::Del {
1289                keys: vec!["key".into()]
1290            },
1291        );
1292    }
1293
1294    #[test]
1295    fn del_multiple() {
1296        assert_eq!(
1297            Command::from_frame(cmd(&["DEL", "a", "b", "c"])).unwrap(),
1298            Command::Del {
1299                keys: vec!["a".into(), "b".into(), "c".into()]
1300            },
1301        );
1302    }
1303
1304    #[test]
1305    fn del_no_args() {
1306        let err = Command::from_frame(cmd(&["DEL"])).unwrap_err();
1307        assert!(matches!(err, ProtocolError::WrongArity(_)));
1308    }
1309
1310    // --- exists ---
1311
1312    #[test]
1313    fn exists_single() {
1314        assert_eq!(
1315            Command::from_frame(cmd(&["EXISTS", "key"])).unwrap(),
1316            Command::Exists {
1317                keys: vec!["key".into()]
1318            },
1319        );
1320    }
1321
1322    #[test]
1323    fn exists_multiple() {
1324        assert_eq!(
1325            Command::from_frame(cmd(&["EXISTS", "a", "b"])).unwrap(),
1326            Command::Exists {
1327                keys: vec!["a".into(), "b".into()]
1328            },
1329        );
1330    }
1331
1332    #[test]
1333    fn exists_no_args() {
1334        let err = Command::from_frame(cmd(&["EXISTS"])).unwrap_err();
1335        assert!(matches!(err, ProtocolError::WrongArity(_)));
1336    }
1337
1338    // --- mget ---
1339
1340    #[test]
1341    fn mget_single() {
1342        assert_eq!(
1343            Command::from_frame(cmd(&["MGET", "key"])).unwrap(),
1344            Command::MGet {
1345                keys: vec!["key".into()]
1346            },
1347        );
1348    }
1349
1350    #[test]
1351    fn mget_multiple() {
1352        assert_eq!(
1353            Command::from_frame(cmd(&["MGET", "a", "b", "c"])).unwrap(),
1354            Command::MGet {
1355                keys: vec!["a".into(), "b".into(), "c".into()]
1356            },
1357        );
1358    }
1359
1360    #[test]
1361    fn mget_no_args() {
1362        let err = Command::from_frame(cmd(&["MGET"])).unwrap_err();
1363        assert!(matches!(err, ProtocolError::WrongArity(_)));
1364    }
1365
1366    // --- mset ---
1367
1368    #[test]
1369    fn mset_single_pair() {
1370        assert_eq!(
1371            Command::from_frame(cmd(&["MSET", "key", "val"])).unwrap(),
1372            Command::MSet {
1373                pairs: vec![("key".into(), Bytes::from("val"))]
1374            },
1375        );
1376    }
1377
1378    #[test]
1379    fn mset_multiple_pairs() {
1380        assert_eq!(
1381            Command::from_frame(cmd(&["MSET", "a", "1", "b", "2"])).unwrap(),
1382            Command::MSet {
1383                pairs: vec![
1384                    ("a".into(), Bytes::from("1")),
1385                    ("b".into(), Bytes::from("2")),
1386                ]
1387            },
1388        );
1389    }
1390
1391    #[test]
1392    fn mset_no_args() {
1393        let err = Command::from_frame(cmd(&["MSET"])).unwrap_err();
1394        assert!(matches!(err, ProtocolError::WrongArity(_)));
1395    }
1396
1397    #[test]
1398    fn mset_odd_args() {
1399        // missing value for second key
1400        let err = Command::from_frame(cmd(&["MSET", "a", "1", "b"])).unwrap_err();
1401        assert!(matches!(err, ProtocolError::WrongArity(_)));
1402    }
1403
1404    // --- expire ---
1405
1406    #[test]
1407    fn expire_basic() {
1408        assert_eq!(
1409            Command::from_frame(cmd(&["EXPIRE", "key", "60"])).unwrap(),
1410            Command::Expire {
1411                key: "key".into(),
1412                seconds: 60,
1413            },
1414        );
1415    }
1416
1417    #[test]
1418    fn expire_wrong_arity() {
1419        let err = Command::from_frame(cmd(&["EXPIRE", "key"])).unwrap_err();
1420        assert!(matches!(err, ProtocolError::WrongArity(_)));
1421    }
1422
1423    #[test]
1424    fn expire_invalid_seconds() {
1425        let err = Command::from_frame(cmd(&["EXPIRE", "key", "abc"])).unwrap_err();
1426        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
1427    }
1428
1429    #[test]
1430    fn expire_zero_seconds() {
1431        let err = Command::from_frame(cmd(&["EXPIRE", "key", "0"])).unwrap_err();
1432        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
1433    }
1434
1435    // --- ttl ---
1436
1437    #[test]
1438    fn ttl_basic() {
1439        assert_eq!(
1440            Command::from_frame(cmd(&["TTL", "key"])).unwrap(),
1441            Command::Ttl { key: "key".into() },
1442        );
1443    }
1444
1445    #[test]
1446    fn ttl_wrong_arity() {
1447        let err = Command::from_frame(cmd(&["TTL"])).unwrap_err();
1448        assert!(matches!(err, ProtocolError::WrongArity(_)));
1449    }
1450
1451    // --- dbsize ---
1452
1453    #[test]
1454    fn dbsize_basic() {
1455        assert_eq!(
1456            Command::from_frame(cmd(&["DBSIZE"])).unwrap(),
1457            Command::DbSize,
1458        );
1459    }
1460
1461    #[test]
1462    fn dbsize_case_insensitive() {
1463        assert_eq!(
1464            Command::from_frame(cmd(&["dbsize"])).unwrap(),
1465            Command::DbSize,
1466        );
1467    }
1468
1469    #[test]
1470    fn dbsize_extra_args() {
1471        let err = Command::from_frame(cmd(&["DBSIZE", "extra"])).unwrap_err();
1472        assert!(matches!(err, ProtocolError::WrongArity(_)));
1473    }
1474
1475    // --- info ---
1476
1477    #[test]
1478    fn info_no_section() {
1479        assert_eq!(
1480            Command::from_frame(cmd(&["INFO"])).unwrap(),
1481            Command::Info { section: None },
1482        );
1483    }
1484
1485    #[test]
1486    fn info_with_section() {
1487        assert_eq!(
1488            Command::from_frame(cmd(&["INFO", "keyspace"])).unwrap(),
1489            Command::Info {
1490                section: Some("keyspace".into())
1491            },
1492        );
1493    }
1494
1495    #[test]
1496    fn info_too_many_args() {
1497        let err = Command::from_frame(cmd(&["INFO", "a", "b"])).unwrap_err();
1498        assert!(matches!(err, ProtocolError::WrongArity(_)));
1499    }
1500
1501    // --- bgsave ---
1502
1503    #[test]
1504    fn bgsave_basic() {
1505        assert_eq!(
1506            Command::from_frame(cmd(&["BGSAVE"])).unwrap(),
1507            Command::BgSave,
1508        );
1509    }
1510
1511    #[test]
1512    fn bgsave_case_insensitive() {
1513        assert_eq!(
1514            Command::from_frame(cmd(&["bgsave"])).unwrap(),
1515            Command::BgSave,
1516        );
1517    }
1518
1519    #[test]
1520    fn bgsave_extra_args() {
1521        let err = Command::from_frame(cmd(&["BGSAVE", "extra"])).unwrap_err();
1522        assert!(matches!(err, ProtocolError::WrongArity(_)));
1523    }
1524
1525    // --- bgrewriteaof ---
1526
1527    #[test]
1528    fn bgrewriteaof_basic() {
1529        assert_eq!(
1530            Command::from_frame(cmd(&["BGREWRITEAOF"])).unwrap(),
1531            Command::BgRewriteAof,
1532        );
1533    }
1534
1535    #[test]
1536    fn bgrewriteaof_case_insensitive() {
1537        assert_eq!(
1538            Command::from_frame(cmd(&["bgrewriteaof"])).unwrap(),
1539            Command::BgRewriteAof,
1540        );
1541    }
1542
1543    #[test]
1544    fn bgrewriteaof_extra_args() {
1545        let err = Command::from_frame(cmd(&["BGREWRITEAOF", "extra"])).unwrap_err();
1546        assert!(matches!(err, ProtocolError::WrongArity(_)));
1547    }
1548
1549    // --- flushdb ---
1550
1551    #[test]
1552    fn flushdb_basic() {
1553        assert_eq!(
1554            Command::from_frame(cmd(&["FLUSHDB"])).unwrap(),
1555            Command::FlushDb,
1556        );
1557    }
1558
1559    #[test]
1560    fn flushdb_case_insensitive() {
1561        assert_eq!(
1562            Command::from_frame(cmd(&["flushdb"])).unwrap(),
1563            Command::FlushDb,
1564        );
1565    }
1566
1567    #[test]
1568    fn flushdb_extra_args() {
1569        let err = Command::from_frame(cmd(&["FLUSHDB", "extra"])).unwrap_err();
1570        assert!(matches!(err, ProtocolError::WrongArity(_)));
1571    }
1572
1573    // --- lpush ---
1574
1575    #[test]
1576    fn lpush_single() {
1577        assert_eq!(
1578            Command::from_frame(cmd(&["LPUSH", "list", "val"])).unwrap(),
1579            Command::LPush {
1580                key: "list".into(),
1581                values: vec![Bytes::from("val")],
1582            },
1583        );
1584    }
1585
1586    #[test]
1587    fn lpush_multiple() {
1588        assert_eq!(
1589            Command::from_frame(cmd(&["LPUSH", "list", "a", "b", "c"])).unwrap(),
1590            Command::LPush {
1591                key: "list".into(),
1592                values: vec![Bytes::from("a"), Bytes::from("b"), Bytes::from("c")],
1593            },
1594        );
1595    }
1596
1597    #[test]
1598    fn lpush_no_value() {
1599        let err = Command::from_frame(cmd(&["LPUSH", "key"])).unwrap_err();
1600        assert!(matches!(err, ProtocolError::WrongArity(_)));
1601    }
1602
1603    #[test]
1604    fn lpush_case_insensitive() {
1605        assert!(matches!(
1606            Command::from_frame(cmd(&["lpush", "k", "v"])).unwrap(),
1607            Command::LPush { .. }
1608        ));
1609    }
1610
1611    // --- rpush ---
1612
1613    #[test]
1614    fn rpush_single() {
1615        assert_eq!(
1616            Command::from_frame(cmd(&["RPUSH", "list", "val"])).unwrap(),
1617            Command::RPush {
1618                key: "list".into(),
1619                values: vec![Bytes::from("val")],
1620            },
1621        );
1622    }
1623
1624    #[test]
1625    fn rpush_no_value() {
1626        let err = Command::from_frame(cmd(&["RPUSH", "key"])).unwrap_err();
1627        assert!(matches!(err, ProtocolError::WrongArity(_)));
1628    }
1629
1630    // --- lpop ---
1631
1632    #[test]
1633    fn lpop_basic() {
1634        assert_eq!(
1635            Command::from_frame(cmd(&["LPOP", "list"])).unwrap(),
1636            Command::LPop { key: "list".into() },
1637        );
1638    }
1639
1640    #[test]
1641    fn lpop_wrong_arity() {
1642        let err = Command::from_frame(cmd(&["LPOP"])).unwrap_err();
1643        assert!(matches!(err, ProtocolError::WrongArity(_)));
1644    }
1645
1646    // --- rpop ---
1647
1648    #[test]
1649    fn rpop_basic() {
1650        assert_eq!(
1651            Command::from_frame(cmd(&["RPOP", "list"])).unwrap(),
1652            Command::RPop { key: "list".into() },
1653        );
1654    }
1655
1656    // --- lrange ---
1657
1658    #[test]
1659    fn lrange_basic() {
1660        assert_eq!(
1661            Command::from_frame(cmd(&["LRANGE", "list", "0", "-1"])).unwrap(),
1662            Command::LRange {
1663                key: "list".into(),
1664                start: 0,
1665                stop: -1,
1666            },
1667        );
1668    }
1669
1670    #[test]
1671    fn lrange_wrong_arity() {
1672        let err = Command::from_frame(cmd(&["LRANGE", "list", "0"])).unwrap_err();
1673        assert!(matches!(err, ProtocolError::WrongArity(_)));
1674    }
1675
1676    #[test]
1677    fn lrange_invalid_index() {
1678        let err = Command::from_frame(cmd(&["LRANGE", "list", "abc", "0"])).unwrap_err();
1679        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
1680    }
1681
1682    // --- llen ---
1683
1684    #[test]
1685    fn llen_basic() {
1686        assert_eq!(
1687            Command::from_frame(cmd(&["LLEN", "list"])).unwrap(),
1688            Command::LLen { key: "list".into() },
1689        );
1690    }
1691
1692    #[test]
1693    fn llen_wrong_arity() {
1694        let err = Command::from_frame(cmd(&["LLEN"])).unwrap_err();
1695        assert!(matches!(err, ProtocolError::WrongArity(_)));
1696    }
1697
1698    // --- type ---
1699
1700    #[test]
1701    fn type_basic() {
1702        assert_eq!(
1703            Command::from_frame(cmd(&["TYPE", "key"])).unwrap(),
1704            Command::Type { key: "key".into() },
1705        );
1706    }
1707
1708    #[test]
1709    fn type_wrong_arity() {
1710        let err = Command::from_frame(cmd(&["TYPE"])).unwrap_err();
1711        assert!(matches!(err, ProtocolError::WrongArity(_)));
1712    }
1713
1714    #[test]
1715    fn type_case_insensitive() {
1716        assert!(matches!(
1717            Command::from_frame(cmd(&["type", "k"])).unwrap(),
1718            Command::Type { .. }
1719        ));
1720    }
1721
1722    // --- general ---
1723
1724    #[test]
1725    fn unknown_command() {
1726        assert_eq!(
1727            Command::from_frame(cmd(&["FOOBAR", "arg"])).unwrap(),
1728            Command::Unknown("FOOBAR".into()),
1729        );
1730    }
1731
1732    #[test]
1733    fn non_array_frame() {
1734        let err = Command::from_frame(Frame::Simple("PING".into())).unwrap_err();
1735        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
1736    }
1737
1738    #[test]
1739    fn empty_array() {
1740        let err = Command::from_frame(Frame::Array(vec![])).unwrap_err();
1741        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
1742    }
1743
1744    // --- zadd ---
1745
1746    #[test]
1747    fn zadd_basic() {
1748        let parsed = Command::from_frame(cmd(&["ZADD", "board", "100", "alice"])).unwrap();
1749        match parsed {
1750            Command::ZAdd {
1751                key,
1752                flags,
1753                members,
1754            } => {
1755                assert_eq!(key, "board");
1756                assert_eq!(flags, ZAddFlags::default());
1757                assert_eq!(members, vec![(100.0, "alice".into())]);
1758            }
1759            other => panic!("expected ZAdd, got {other:?}"),
1760        }
1761    }
1762
1763    #[test]
1764    fn zadd_multiple_members() {
1765        let parsed =
1766            Command::from_frame(cmd(&["ZADD", "board", "100", "alice", "200", "bob"])).unwrap();
1767        match parsed {
1768            Command::ZAdd { members, .. } => {
1769                assert_eq!(members.len(), 2);
1770                assert_eq!(members[0], (100.0, "alice".into()));
1771                assert_eq!(members[1], (200.0, "bob".into()));
1772            }
1773            other => panic!("expected ZAdd, got {other:?}"),
1774        }
1775    }
1776
1777    #[test]
1778    fn zadd_with_flags() {
1779        let parsed = Command::from_frame(cmd(&["ZADD", "z", "NX", "CH", "100", "alice"])).unwrap();
1780        match parsed {
1781            Command::ZAdd { flags, .. } => {
1782                assert!(flags.nx);
1783                assert!(flags.ch);
1784                assert!(!flags.xx);
1785                assert!(!flags.gt);
1786                assert!(!flags.lt);
1787            }
1788            other => panic!("expected ZAdd, got {other:?}"),
1789        }
1790    }
1791
1792    #[test]
1793    fn zadd_gt_flag() {
1794        let parsed = Command::from_frame(cmd(&["zadd", "z", "gt", "100", "alice"])).unwrap();
1795        match parsed {
1796            Command::ZAdd { flags, .. } => assert!(flags.gt),
1797            other => panic!("expected ZAdd, got {other:?}"),
1798        }
1799    }
1800
1801    #[test]
1802    fn zadd_nx_xx_conflict() {
1803        let err = Command::from_frame(cmd(&["ZADD", "z", "NX", "XX", "100", "alice"])).unwrap_err();
1804        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
1805    }
1806
1807    #[test]
1808    fn zadd_gt_lt_conflict() {
1809        let err = Command::from_frame(cmd(&["ZADD", "z", "GT", "LT", "100", "alice"])).unwrap_err();
1810        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
1811    }
1812
1813    #[test]
1814    fn zadd_wrong_arity() {
1815        let err = Command::from_frame(cmd(&["ZADD", "z"])).unwrap_err();
1816        assert!(matches!(err, ProtocolError::WrongArity(_)));
1817    }
1818
1819    #[test]
1820    fn zadd_odd_score_member_count() {
1821        // one score without a member
1822        let err = Command::from_frame(cmd(&["ZADD", "z", "100"])).unwrap_err();
1823        assert!(matches!(err, ProtocolError::WrongArity(_)));
1824    }
1825
1826    #[test]
1827    fn zadd_invalid_score() {
1828        let err = Command::from_frame(cmd(&["ZADD", "z", "notanum", "alice"])).unwrap_err();
1829        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
1830    }
1831
1832    #[test]
1833    fn zadd_nan_score() {
1834        let err = Command::from_frame(cmd(&["ZADD", "z", "nan", "alice"])).unwrap_err();
1835        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
1836    }
1837
1838    #[test]
1839    fn zadd_negative_score() {
1840        let parsed = Command::from_frame(cmd(&["ZADD", "z", "-50.5", "alice"])).unwrap();
1841        match parsed {
1842            Command::ZAdd { members, .. } => {
1843                assert_eq!(members[0].0, -50.5);
1844            }
1845            other => panic!("expected ZAdd, got {other:?}"),
1846        }
1847    }
1848
1849    // --- zrem ---
1850
1851    #[test]
1852    fn zrem_basic() {
1853        assert_eq!(
1854            Command::from_frame(cmd(&["ZREM", "z", "alice"])).unwrap(),
1855            Command::ZRem {
1856                key: "z".into(),
1857                members: vec!["alice".into()],
1858            },
1859        );
1860    }
1861
1862    #[test]
1863    fn zrem_multiple() {
1864        let parsed = Command::from_frame(cmd(&["ZREM", "z", "a", "b", "c"])).unwrap();
1865        match parsed {
1866            Command::ZRem { members, .. } => assert_eq!(members.len(), 3),
1867            other => panic!("expected ZRem, got {other:?}"),
1868        }
1869    }
1870
1871    #[test]
1872    fn zrem_wrong_arity() {
1873        let err = Command::from_frame(cmd(&["ZREM", "z"])).unwrap_err();
1874        assert!(matches!(err, ProtocolError::WrongArity(_)));
1875    }
1876
1877    // --- zscore ---
1878
1879    #[test]
1880    fn zscore_basic() {
1881        assert_eq!(
1882            Command::from_frame(cmd(&["ZSCORE", "z", "alice"])).unwrap(),
1883            Command::ZScore {
1884                key: "z".into(),
1885                member: "alice".into(),
1886            },
1887        );
1888    }
1889
1890    #[test]
1891    fn zscore_wrong_arity() {
1892        let err = Command::from_frame(cmd(&["ZSCORE", "z"])).unwrap_err();
1893        assert!(matches!(err, ProtocolError::WrongArity(_)));
1894    }
1895
1896    // --- zrank ---
1897
1898    #[test]
1899    fn zrank_basic() {
1900        assert_eq!(
1901            Command::from_frame(cmd(&["ZRANK", "z", "alice"])).unwrap(),
1902            Command::ZRank {
1903                key: "z".into(),
1904                member: "alice".into(),
1905            },
1906        );
1907    }
1908
1909    #[test]
1910    fn zrank_wrong_arity() {
1911        let err = Command::from_frame(cmd(&["ZRANK", "z"])).unwrap_err();
1912        assert!(matches!(err, ProtocolError::WrongArity(_)));
1913    }
1914
1915    // --- zcard ---
1916
1917    #[test]
1918    fn zcard_basic() {
1919        assert_eq!(
1920            Command::from_frame(cmd(&["ZCARD", "z"])).unwrap(),
1921            Command::ZCard { key: "z".into() },
1922        );
1923    }
1924
1925    #[test]
1926    fn zcard_wrong_arity() {
1927        let err = Command::from_frame(cmd(&["ZCARD"])).unwrap_err();
1928        assert!(matches!(err, ProtocolError::WrongArity(_)));
1929    }
1930
1931    // --- zrange ---
1932
1933    #[test]
1934    fn zrange_basic() {
1935        assert_eq!(
1936            Command::from_frame(cmd(&["ZRANGE", "z", "0", "-1"])).unwrap(),
1937            Command::ZRange {
1938                key: "z".into(),
1939                start: 0,
1940                stop: -1,
1941                with_scores: false,
1942            },
1943        );
1944    }
1945
1946    #[test]
1947    fn zrange_with_scores() {
1948        assert_eq!(
1949            Command::from_frame(cmd(&["ZRANGE", "z", "0", "-1", "WITHSCORES"])).unwrap(),
1950            Command::ZRange {
1951                key: "z".into(),
1952                start: 0,
1953                stop: -1,
1954                with_scores: true,
1955            },
1956        );
1957    }
1958
1959    #[test]
1960    fn zrange_withscores_case_insensitive() {
1961        assert_eq!(
1962            Command::from_frame(cmd(&["zrange", "z", "0", "-1", "withscores"])).unwrap(),
1963            Command::ZRange {
1964                key: "z".into(),
1965                start: 0,
1966                stop: -1,
1967                with_scores: true,
1968            },
1969        );
1970    }
1971
1972    #[test]
1973    fn zrange_invalid_option() {
1974        let err = Command::from_frame(cmd(&["ZRANGE", "z", "0", "-1", "BADOPT"])).unwrap_err();
1975        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
1976    }
1977
1978    #[test]
1979    fn zrange_wrong_arity() {
1980        let err = Command::from_frame(cmd(&["ZRANGE", "z", "0"])).unwrap_err();
1981        assert!(matches!(err, ProtocolError::WrongArity(_)));
1982    }
1983
1984    // --- incr ---
1985
1986    #[test]
1987    fn incr_basic() {
1988        assert_eq!(
1989            Command::from_frame(cmd(&["INCR", "counter"])).unwrap(),
1990            Command::Incr {
1991                key: "counter".into()
1992            },
1993        );
1994    }
1995
1996    #[test]
1997    fn incr_wrong_arity() {
1998        let err = Command::from_frame(cmd(&["INCR"])).unwrap_err();
1999        assert!(matches!(err, ProtocolError::WrongArity(_)));
2000    }
2001
2002    // --- decr ---
2003
2004    #[test]
2005    fn decr_basic() {
2006        assert_eq!(
2007            Command::from_frame(cmd(&["DECR", "counter"])).unwrap(),
2008            Command::Decr {
2009                key: "counter".into()
2010            },
2011        );
2012    }
2013
2014    #[test]
2015    fn decr_wrong_arity() {
2016        let err = Command::from_frame(cmd(&["DECR"])).unwrap_err();
2017        assert!(matches!(err, ProtocolError::WrongArity(_)));
2018    }
2019
2020    // --- persist ---
2021
2022    #[test]
2023    fn persist_basic() {
2024        assert_eq!(
2025            Command::from_frame(cmd(&["PERSIST", "key"])).unwrap(),
2026            Command::Persist { key: "key".into() },
2027        );
2028    }
2029
2030    #[test]
2031    fn persist_case_insensitive() {
2032        assert_eq!(
2033            Command::from_frame(cmd(&["persist", "key"])).unwrap(),
2034            Command::Persist { key: "key".into() },
2035        );
2036    }
2037
2038    #[test]
2039    fn persist_wrong_arity() {
2040        let err = Command::from_frame(cmd(&["PERSIST"])).unwrap_err();
2041        assert!(matches!(err, ProtocolError::WrongArity(_)));
2042    }
2043
2044    // --- pttl ---
2045
2046    #[test]
2047    fn pttl_basic() {
2048        assert_eq!(
2049            Command::from_frame(cmd(&["PTTL", "key"])).unwrap(),
2050            Command::Pttl { key: "key".into() },
2051        );
2052    }
2053
2054    #[test]
2055    fn pttl_wrong_arity() {
2056        let err = Command::from_frame(cmd(&["PTTL"])).unwrap_err();
2057        assert!(matches!(err, ProtocolError::WrongArity(_)));
2058    }
2059
2060    // --- pexpire ---
2061
2062    #[test]
2063    fn pexpire_basic() {
2064        assert_eq!(
2065            Command::from_frame(cmd(&["PEXPIRE", "key", "5000"])).unwrap(),
2066            Command::Pexpire {
2067                key: "key".into(),
2068                milliseconds: 5000,
2069            },
2070        );
2071    }
2072
2073    #[test]
2074    fn pexpire_wrong_arity() {
2075        let err = Command::from_frame(cmd(&["PEXPIRE", "key"])).unwrap_err();
2076        assert!(matches!(err, ProtocolError::WrongArity(_)));
2077    }
2078
2079    #[test]
2080    fn pexpire_zero_millis() {
2081        let err = Command::from_frame(cmd(&["PEXPIRE", "key", "0"])).unwrap_err();
2082        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
2083    }
2084
2085    #[test]
2086    fn pexpire_invalid_millis() {
2087        let err = Command::from_frame(cmd(&["PEXPIRE", "key", "notanum"])).unwrap_err();
2088        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
2089    }
2090
2091    // --- scan ---
2092
2093    #[test]
2094    fn scan_basic() {
2095        assert_eq!(
2096            Command::from_frame(cmd(&["SCAN", "0"])).unwrap(),
2097            Command::Scan {
2098                cursor: 0,
2099                pattern: None,
2100                count: None,
2101            },
2102        );
2103    }
2104
2105    #[test]
2106    fn scan_with_match() {
2107        assert_eq!(
2108            Command::from_frame(cmd(&["SCAN", "0", "MATCH", "user:*"])).unwrap(),
2109            Command::Scan {
2110                cursor: 0,
2111                pattern: Some("user:*".into()),
2112                count: None,
2113            },
2114        );
2115    }
2116
2117    #[test]
2118    fn scan_with_count() {
2119        assert_eq!(
2120            Command::from_frame(cmd(&["SCAN", "42", "COUNT", "100"])).unwrap(),
2121            Command::Scan {
2122                cursor: 42,
2123                pattern: None,
2124                count: Some(100),
2125            },
2126        );
2127    }
2128
2129    #[test]
2130    fn scan_with_match_and_count() {
2131        assert_eq!(
2132            Command::from_frame(cmd(&["SCAN", "0", "MATCH", "*:data", "COUNT", "50"])).unwrap(),
2133            Command::Scan {
2134                cursor: 0,
2135                pattern: Some("*:data".into()),
2136                count: Some(50),
2137            },
2138        );
2139    }
2140
2141    #[test]
2142    fn scan_count_before_match() {
2143        assert_eq!(
2144            Command::from_frame(cmd(&["SCAN", "0", "COUNT", "10", "MATCH", "foo*"])).unwrap(),
2145            Command::Scan {
2146                cursor: 0,
2147                pattern: Some("foo*".into()),
2148                count: Some(10),
2149            },
2150        );
2151    }
2152
2153    #[test]
2154    fn scan_case_insensitive() {
2155        assert_eq!(
2156            Command::from_frame(cmd(&["scan", "0", "match", "x*", "count", "5"])).unwrap(),
2157            Command::Scan {
2158                cursor: 0,
2159                pattern: Some("x*".into()),
2160                count: Some(5),
2161            },
2162        );
2163    }
2164
2165    #[test]
2166    fn scan_wrong_arity() {
2167        let err = Command::from_frame(cmd(&["SCAN"])).unwrap_err();
2168        assert!(matches!(err, ProtocolError::WrongArity(_)));
2169    }
2170
2171    #[test]
2172    fn scan_invalid_cursor() {
2173        let err = Command::from_frame(cmd(&["SCAN", "notanum"])).unwrap_err();
2174        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
2175    }
2176
2177    #[test]
2178    fn scan_invalid_count() {
2179        let err = Command::from_frame(cmd(&["SCAN", "0", "COUNT", "bad"])).unwrap_err();
2180        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
2181    }
2182
2183    #[test]
2184    fn scan_unknown_flag() {
2185        let err = Command::from_frame(cmd(&["SCAN", "0", "BADOPT", "val"])).unwrap_err();
2186        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
2187    }
2188
2189    #[test]
2190    fn scan_match_missing_pattern() {
2191        let err = Command::from_frame(cmd(&["SCAN", "0", "MATCH"])).unwrap_err();
2192        assert!(matches!(err, ProtocolError::WrongArity(_)));
2193    }
2194
2195    #[test]
2196    fn scan_count_missing_value() {
2197        let err = Command::from_frame(cmd(&["SCAN", "0", "COUNT"])).unwrap_err();
2198        assert!(matches!(err, ProtocolError::WrongArity(_)));
2199    }
2200
2201    // --- hash commands ---
2202
2203    #[test]
2204    fn hset_single_field() {
2205        assert_eq!(
2206            Command::from_frame(cmd(&["HSET", "h", "field", "value"])).unwrap(),
2207            Command::HSet {
2208                key: "h".into(),
2209                fields: vec![("field".into(), Bytes::from("value"))],
2210            },
2211        );
2212    }
2213
2214    #[test]
2215    fn hset_multiple_fields() {
2216        let parsed = Command::from_frame(cmd(&["HSET", "h", "f1", "v1", "f2", "v2"])).unwrap();
2217        match parsed {
2218            Command::HSet { key, fields } => {
2219                assert_eq!(key, "h");
2220                assert_eq!(fields.len(), 2);
2221            }
2222            other => panic!("expected HSet, got {other:?}"),
2223        }
2224    }
2225
2226    #[test]
2227    fn hset_wrong_arity() {
2228        let err = Command::from_frame(cmd(&["HSET", "h"])).unwrap_err();
2229        assert!(matches!(err, ProtocolError::WrongArity(_)));
2230        let err = Command::from_frame(cmd(&["HSET", "h", "f"])).unwrap_err();
2231        assert!(matches!(err, ProtocolError::WrongArity(_)));
2232    }
2233
2234    #[test]
2235    fn hget_basic() {
2236        assert_eq!(
2237            Command::from_frame(cmd(&["HGET", "h", "field"])).unwrap(),
2238            Command::HGet {
2239                key: "h".into(),
2240                field: "field".into(),
2241            },
2242        );
2243    }
2244
2245    #[test]
2246    fn hget_wrong_arity() {
2247        let err = Command::from_frame(cmd(&["HGET", "h"])).unwrap_err();
2248        assert!(matches!(err, ProtocolError::WrongArity(_)));
2249    }
2250
2251    #[test]
2252    fn hgetall_basic() {
2253        assert_eq!(
2254            Command::from_frame(cmd(&["HGETALL", "h"])).unwrap(),
2255            Command::HGetAll { key: "h".into() },
2256        );
2257    }
2258
2259    #[test]
2260    fn hgetall_wrong_arity() {
2261        let err = Command::from_frame(cmd(&["HGETALL"])).unwrap_err();
2262        assert!(matches!(err, ProtocolError::WrongArity(_)));
2263    }
2264
2265    #[test]
2266    fn hdel_single() {
2267        assert_eq!(
2268            Command::from_frame(cmd(&["HDEL", "h", "f"])).unwrap(),
2269            Command::HDel {
2270                key: "h".into(),
2271                fields: vec!["f".into()],
2272            },
2273        );
2274    }
2275
2276    #[test]
2277    fn hdel_multiple() {
2278        let parsed = Command::from_frame(cmd(&["HDEL", "h", "f1", "f2", "f3"])).unwrap();
2279        match parsed {
2280            Command::HDel { fields, .. } => assert_eq!(fields.len(), 3),
2281            other => panic!("expected HDel, got {other:?}"),
2282        }
2283    }
2284
2285    #[test]
2286    fn hdel_wrong_arity() {
2287        let err = Command::from_frame(cmd(&["HDEL", "h"])).unwrap_err();
2288        assert!(matches!(err, ProtocolError::WrongArity(_)));
2289    }
2290
2291    #[test]
2292    fn hexists_basic() {
2293        assert_eq!(
2294            Command::from_frame(cmd(&["HEXISTS", "h", "f"])).unwrap(),
2295            Command::HExists {
2296                key: "h".into(),
2297                field: "f".into(),
2298            },
2299        );
2300    }
2301
2302    #[test]
2303    fn hlen_basic() {
2304        assert_eq!(
2305            Command::from_frame(cmd(&["HLEN", "h"])).unwrap(),
2306            Command::HLen { key: "h".into() },
2307        );
2308    }
2309
2310    #[test]
2311    fn hincrby_basic() {
2312        assert_eq!(
2313            Command::from_frame(cmd(&["HINCRBY", "h", "f", "5"])).unwrap(),
2314            Command::HIncrBy {
2315                key: "h".into(),
2316                field: "f".into(),
2317                delta: 5,
2318            },
2319        );
2320    }
2321
2322    #[test]
2323    fn hincrby_negative() {
2324        assert_eq!(
2325            Command::from_frame(cmd(&["HINCRBY", "h", "f", "-3"])).unwrap(),
2326            Command::HIncrBy {
2327                key: "h".into(),
2328                field: "f".into(),
2329                delta: -3,
2330            },
2331        );
2332    }
2333
2334    #[test]
2335    fn hincrby_wrong_arity() {
2336        let err = Command::from_frame(cmd(&["HINCRBY", "h", "f"])).unwrap_err();
2337        assert!(matches!(err, ProtocolError::WrongArity(_)));
2338    }
2339
2340    #[test]
2341    fn hkeys_basic() {
2342        assert_eq!(
2343            Command::from_frame(cmd(&["HKEYS", "h"])).unwrap(),
2344            Command::HKeys { key: "h".into() },
2345        );
2346    }
2347
2348    #[test]
2349    fn hvals_basic() {
2350        assert_eq!(
2351            Command::from_frame(cmd(&["HVALS", "h"])).unwrap(),
2352            Command::HVals { key: "h".into() },
2353        );
2354    }
2355
2356    #[test]
2357    fn hmget_basic() {
2358        assert_eq!(
2359            Command::from_frame(cmd(&["HMGET", "h", "f1", "f2"])).unwrap(),
2360            Command::HMGet {
2361                key: "h".into(),
2362                fields: vec!["f1".into(), "f2".into()],
2363            },
2364        );
2365    }
2366
2367    #[test]
2368    fn hmget_wrong_arity() {
2369        let err = Command::from_frame(cmd(&["HMGET", "h"])).unwrap_err();
2370        assert!(matches!(err, ProtocolError::WrongArity(_)));
2371    }
2372
2373    #[test]
2374    fn hash_commands_case_insensitive() {
2375        assert!(matches!(
2376            Command::from_frame(cmd(&["hset", "h", "f", "v"])).unwrap(),
2377            Command::HSet { .. }
2378        ));
2379        assert!(matches!(
2380            Command::from_frame(cmd(&["hget", "h", "f"])).unwrap(),
2381            Command::HGet { .. }
2382        ));
2383        assert!(matches!(
2384            Command::from_frame(cmd(&["hgetall", "h"])).unwrap(),
2385            Command::HGetAll { .. }
2386        ));
2387    }
2388
2389    // --- set commands ---
2390
2391    #[test]
2392    fn sadd_single_member() {
2393        assert_eq!(
2394            Command::from_frame(cmd(&["SADD", "s", "member"])).unwrap(),
2395            Command::SAdd {
2396                key: "s".into(),
2397                members: vec!["member".into()],
2398            },
2399        );
2400    }
2401
2402    #[test]
2403    fn sadd_multiple_members() {
2404        let parsed = Command::from_frame(cmd(&["SADD", "s", "a", "b", "c"])).unwrap();
2405        match parsed {
2406            Command::SAdd { key, members } => {
2407                assert_eq!(key, "s");
2408                assert_eq!(members.len(), 3);
2409            }
2410            other => panic!("expected SAdd, got {other:?}"),
2411        }
2412    }
2413
2414    #[test]
2415    fn sadd_wrong_arity() {
2416        let err = Command::from_frame(cmd(&["SADD", "s"])).unwrap_err();
2417        assert!(matches!(err, ProtocolError::WrongArity(_)));
2418    }
2419
2420    #[test]
2421    fn srem_single_member() {
2422        assert_eq!(
2423            Command::from_frame(cmd(&["SREM", "s", "member"])).unwrap(),
2424            Command::SRem {
2425                key: "s".into(),
2426                members: vec!["member".into()],
2427            },
2428        );
2429    }
2430
2431    #[test]
2432    fn srem_multiple_members() {
2433        let parsed = Command::from_frame(cmd(&["SREM", "s", "a", "b"])).unwrap();
2434        match parsed {
2435            Command::SRem { key, members } => {
2436                assert_eq!(key, "s");
2437                assert_eq!(members.len(), 2);
2438            }
2439            other => panic!("expected SRem, got {other:?}"),
2440        }
2441    }
2442
2443    #[test]
2444    fn srem_wrong_arity() {
2445        let err = Command::from_frame(cmd(&["SREM", "s"])).unwrap_err();
2446        assert!(matches!(err, ProtocolError::WrongArity(_)));
2447    }
2448
2449    #[test]
2450    fn smembers_basic() {
2451        assert_eq!(
2452            Command::from_frame(cmd(&["SMEMBERS", "s"])).unwrap(),
2453            Command::SMembers { key: "s".into() },
2454        );
2455    }
2456
2457    #[test]
2458    fn smembers_wrong_arity() {
2459        let err = Command::from_frame(cmd(&["SMEMBERS"])).unwrap_err();
2460        assert!(matches!(err, ProtocolError::WrongArity(_)));
2461    }
2462
2463    #[test]
2464    fn sismember_basic() {
2465        assert_eq!(
2466            Command::from_frame(cmd(&["SISMEMBER", "s", "member"])).unwrap(),
2467            Command::SIsMember {
2468                key: "s".into(),
2469                member: "member".into(),
2470            },
2471        );
2472    }
2473
2474    #[test]
2475    fn sismember_wrong_arity() {
2476        let err = Command::from_frame(cmd(&["SISMEMBER", "s"])).unwrap_err();
2477        assert!(matches!(err, ProtocolError::WrongArity(_)));
2478    }
2479
2480    #[test]
2481    fn scard_basic() {
2482        assert_eq!(
2483            Command::from_frame(cmd(&["SCARD", "s"])).unwrap(),
2484            Command::SCard { key: "s".into() },
2485        );
2486    }
2487
2488    #[test]
2489    fn scard_wrong_arity() {
2490        let err = Command::from_frame(cmd(&["SCARD"])).unwrap_err();
2491        assert!(matches!(err, ProtocolError::WrongArity(_)));
2492    }
2493
2494    #[test]
2495    fn set_commands_case_insensitive() {
2496        assert!(matches!(
2497            Command::from_frame(cmd(&["sadd", "s", "m"])).unwrap(),
2498            Command::SAdd { .. }
2499        ));
2500        assert!(matches!(
2501            Command::from_frame(cmd(&["srem", "s", "m"])).unwrap(),
2502            Command::SRem { .. }
2503        ));
2504        assert!(matches!(
2505            Command::from_frame(cmd(&["smembers", "s"])).unwrap(),
2506            Command::SMembers { .. }
2507        ));
2508    }
2509}