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