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