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