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/// Maximum number of dimensions in a vector. 65,536 is generous for any
13/// real-world embedding model (OpenAI: 1536, Cohere: 4096) while preventing
14/// memory abuse from absurdly large vectors.
15const MAX_VECTOR_DIMS: usize = 65_536;
16
17/// Maximum value for HNSW connectivity (M) and expansion parameters.
18/// Values above 1024 give no practical benefit and waste memory.
19const MAX_HNSW_PARAM: u64 = 1024;
20
21/// Maximum number of results for VSIM. 10,000 is generous for any practical
22/// similarity search while preventing OOM from unbounded result allocation.
23const MAX_VSIM_COUNT: u64 = 10_000;
24
25/// Maximum search beam width for VSIM. Same cap as MAX_HNSW_PARAM —
26/// larger values cause worst-case O(n) graph traversal with no accuracy gain.
27const MAX_VSIM_EF: u64 = MAX_HNSW_PARAM;
28
29/// Maximum number of vectors in a single VADD_BATCH command. 10,000 keeps
30/// per-command latency bounded while still being large enough to amortize
31/// round-trip overhead for bulk inserts.
32const MAX_VADD_BATCH_SIZE: usize = 10_000;
33
34/// Maximum value for SCAN COUNT. Prevents clients from requesting a scan
35/// hint so large it causes pre-allocation issues.
36const MAX_SCAN_COUNT: u64 = 10_000_000;
37
38/// Expiration option for the SET command.
39#[derive(Debug, Clone, PartialEq, Eq)]
40pub enum SetExpire {
41    /// EX seconds — expire after N seconds.
42    Ex(u64),
43    /// PX milliseconds — expire after N milliseconds.
44    Px(u64),
45}
46
47/// A parsed client command, ready for execution.
48#[derive(Debug, Clone, PartialEq)]
49pub enum Command {
50    /// PING with an optional message. Returns PONG or echoes the message.
51    Ping(Option<Bytes>),
52
53    /// ECHO `message`. Returns the message back to the client.
54    Echo(Bytes),
55
56    /// GET `key`. Returns the value or nil.
57    Get { key: String },
58
59    /// SET `key` `value` \[EX seconds | PX milliseconds\] \[NX | XX\].
60    Set {
61        key: String,
62        value: Bytes,
63        expire: Option<SetExpire>,
64        /// Only set the key if it does not already exist.
65        nx: bool,
66        /// Only set the key if it already exists.
67        xx: bool,
68    },
69
70    /// INCR `key`. Increments the integer value of a key by 1.
71    Incr { key: String },
72
73    /// DECR `key`. Decrements the integer value of a key by 1.
74    Decr { key: String },
75
76    /// INCRBY `key` `increment`. Increments the integer value of a key by the given amount.
77    IncrBy { key: String, delta: i64 },
78
79    /// DECRBY `key` `decrement`. Decrements the integer value of a key by the given amount.
80    DecrBy { key: String, delta: i64 },
81
82    /// INCRBYFLOAT `key` `increment`. Increments the float value of a key by the given amount.
83    IncrByFloat { key: String, delta: f64 },
84
85    /// APPEND `key` `value`. Appends a value to a string key. Returns the new length.
86    Append { key: String, value: Bytes },
87
88    /// STRLEN `key`. Returns the length of the string value stored at key.
89    Strlen { key: String },
90
91    /// KEYS `pattern`. Returns all keys matching a glob pattern.
92    Keys { pattern: String },
93
94    /// RENAME `key` `newkey`. Renames a key.
95    Rename { key: String, newkey: String },
96
97    /// DEL `key` \[key ...\]. Returns the number of keys removed.
98    Del { keys: Vec<String> },
99
100    /// UNLINK `key` \[key ...\]. Like DEL but frees memory in the background.
101    Unlink { keys: Vec<String> },
102
103    /// EXISTS `key` \[key ...\]. Returns the number of keys that exist.
104    Exists { keys: Vec<String> },
105
106    /// MGET `key` \[key ...\]. Returns the values for all specified keys.
107    MGet { keys: Vec<String> },
108
109    /// MSET `key` `value` \[key value ...\]. Sets multiple key-value pairs.
110    MSet { pairs: Vec<(String, Bytes)> },
111
112    /// EXPIRE `key` `seconds`. Sets a TTL on an existing key.
113    Expire { key: String, seconds: u64 },
114
115    /// TTL `key`. Returns remaining time-to-live in seconds.
116    Ttl { key: String },
117
118    /// PERSIST `key`. Removes the expiration from a key.
119    Persist { key: String },
120
121    /// PTTL `key`. Returns remaining time-to-live in milliseconds.
122    Pttl { key: String },
123
124    /// PEXPIRE `key` `milliseconds`. Sets a TTL in milliseconds on an existing key.
125    Pexpire { key: String, milliseconds: u64 },
126
127    /// DBSIZE. Returns the number of keys in the database.
128    DbSize,
129
130    /// INFO \[section\]. Returns server info. Currently only supports "keyspace".
131    Info { section: Option<String> },
132
133    /// BGSAVE. Triggers a background snapshot.
134    BgSave,
135
136    /// BGREWRITEAOF. Triggers an AOF rewrite (snapshot + truncate).
137    BgRewriteAof,
138
139    /// FLUSHDB \[ASYNC\]. Removes all keys from the database.
140    FlushDb { async_mode: bool },
141
142    /// SCAN `cursor` \[MATCH pattern\] \[COUNT count\]. Iterates keys.
143    Scan {
144        cursor: u64,
145        pattern: Option<String>,
146        count: Option<usize>,
147    },
148
149    /// LPUSH `key` `value` \[value ...\]. Pushes values to the head of a list.
150    LPush { key: String, values: Vec<Bytes> },
151
152    /// RPUSH `key` `value` \[value ...\]. Pushes values to the tail of a list.
153    RPush { key: String, values: Vec<Bytes> },
154
155    /// LPOP `key`. Pops a value from the head of a list.
156    LPop { key: String },
157
158    /// RPOP `key`. Pops a value from the tail of a list.
159    RPop { key: String },
160
161    /// LRANGE `key` `start` `stop`. Returns a range of elements by index.
162    LRange { key: String, start: i64, stop: i64 },
163
164    /// LLEN `key`. Returns the length of a list.
165    LLen { key: String },
166
167    /// TYPE `key`. Returns the type of the value stored at key.
168    Type { key: String },
169
170    /// ZADD `key` \[NX|XX\] \[GT|LT\] \[CH\] `score` `member` \[score member ...\].
171    ZAdd {
172        key: String,
173        flags: ZAddFlags,
174        members: Vec<(f64, String)>,
175    },
176
177    /// ZREM `key` `member` \[member ...\]. Removes members from a sorted set.
178    ZRem { key: String, members: Vec<String> },
179
180    /// ZSCORE `key` `member`. Returns the score of a member.
181    ZScore { key: String, member: String },
182
183    /// ZRANK `key` `member`. Returns the rank of a member (0-based).
184    ZRank { key: String, member: String },
185
186    /// ZCARD `key`. Returns the cardinality (number of members) of a sorted set.
187    ZCard { key: String },
188
189    /// ZRANGE `key` `start` `stop` \[WITHSCORES\]. Returns a range by rank.
190    ZRange {
191        key: String,
192        start: i64,
193        stop: i64,
194        with_scores: bool,
195    },
196
197    /// HSET `key` `field` `value` \[field value ...\]. Sets field-value pairs in a hash.
198    HSet {
199        key: String,
200        fields: Vec<(String, Bytes)>,
201    },
202
203    /// HGET `key` `field`. Gets a field's value from a hash.
204    HGet { key: String, field: String },
205
206    /// HGETALL `key`. Gets all field-value pairs from a hash.
207    HGetAll { key: String },
208
209    /// HDEL `key` `field` \[field ...\]. Deletes fields from a hash.
210    HDel { key: String, fields: Vec<String> },
211
212    /// HEXISTS `key` `field`. Checks if a field exists in a hash.
213    HExists { key: String, field: String },
214
215    /// HLEN `key`. Returns the number of fields in a hash.
216    HLen { key: String },
217
218    /// HINCRBY `key` `field` `increment`. Increments a hash field's integer value.
219    HIncrBy {
220        key: String,
221        field: String,
222        delta: i64,
223    },
224
225    /// HKEYS `key`. Returns all field names in a hash.
226    HKeys { key: String },
227
228    /// HVALS `key`. Returns all values in a hash.
229    HVals { key: String },
230
231    /// HMGET `key` `field` \[field ...\]. Gets multiple field values from a hash.
232    HMGet { key: String, fields: Vec<String> },
233
234    /// SADD `key` `member` \[member ...\]. Adds members to a set.
235    SAdd { key: String, members: Vec<String> },
236
237    /// SREM `key` `member` \[member ...\]. Removes members from a set.
238    SRem { key: String, members: Vec<String> },
239
240    /// SMEMBERS `key`. Returns all members of a set.
241    SMembers { key: String },
242
243    /// SISMEMBER `key` `member`. Checks if a member exists in a set.
244    SIsMember { key: String, member: String },
245
246    /// SCARD `key`. Returns the cardinality (number of members) of a set.
247    SCard { key: String },
248
249    // --- cluster commands ---
250    /// CLUSTER INFO. Returns cluster state and configuration information.
251    ClusterInfo,
252
253    /// CLUSTER NODES. Returns the list of cluster nodes.
254    ClusterNodes,
255
256    /// CLUSTER SLOTS. Returns the slot distribution across nodes.
257    ClusterSlots,
258
259    /// CLUSTER KEYSLOT `key`. Returns the hash slot for a key.
260    ClusterKeySlot { key: String },
261
262    /// CLUSTER MYID. Returns the node's ID.
263    ClusterMyId,
264
265    /// CLUSTER SETSLOT `slot` IMPORTING `node-id`. Mark slot as importing from node.
266    ClusterSetSlotImporting { slot: u16, node_id: String },
267
268    /// CLUSTER SETSLOT `slot` MIGRATING `node-id`. Mark slot as migrating to node.
269    ClusterSetSlotMigrating { slot: u16, node_id: String },
270
271    /// CLUSTER SETSLOT `slot` NODE `node-id`. Assign slot to node.
272    ClusterSetSlotNode { slot: u16, node_id: String },
273
274    /// CLUSTER SETSLOT `slot` STABLE. Clear importing/migrating state.
275    ClusterSetSlotStable { slot: u16 },
276
277    /// CLUSTER MEET `ip` `port`. Add a node to the cluster.
278    ClusterMeet { ip: String, port: u16 },
279
280    /// CLUSTER ADDSLOTS `slot` \[slot...\]. Assign slots to the local node.
281    ClusterAddSlots { slots: Vec<u16> },
282
283    /// CLUSTER DELSLOTS `slot` \[slot...\]. Remove slots from the local node.
284    ClusterDelSlots { slots: Vec<u16> },
285
286    /// CLUSTER FORGET `node-id`. Remove a node from the cluster.
287    ClusterForget { node_id: String },
288
289    /// CLUSTER REPLICATE `node-id`. Make this node a replica of another.
290    ClusterReplicate { node_id: String },
291
292    /// CLUSTER FAILOVER [FORCE|TAKEOVER]. Trigger a manual failover.
293    ClusterFailover { force: bool, takeover: bool },
294
295    /// CLUSTER COUNTKEYSINSLOT `slot`. Return the number of keys in a slot.
296    ClusterCountKeysInSlot { slot: u16 },
297
298    /// CLUSTER GETKEYSINSLOT `slot` `count`. Return keys in a slot.
299    ClusterGetKeysInSlot { slot: u16, count: u32 },
300
301    /// MIGRATE `host` `port` `key` `db` `timeout` \[COPY\] \[REPLACE\] \[KEYS key...\].
302    /// Migrate a key to another node.
303    Migrate {
304        host: String,
305        port: u16,
306        key: String,
307        db: u32,
308        timeout_ms: u64,
309        copy: bool,
310        replace: bool,
311    },
312
313    /// ASKING. Signals that the next command is for a migrating slot.
314    Asking,
315
316    /// SLOWLOG GET [count]. Returns recent slow log entries.
317    SlowLogGet { count: Option<usize> },
318
319    /// SLOWLOG LEN. Returns the number of entries in the slow log.
320    SlowLogLen,
321
322    /// SLOWLOG RESET. Clears the slow log.
323    SlowLogReset,
324
325    // --- pub/sub commands ---
326    /// SUBSCRIBE `channel` \[channel ...\]. Subscribe to one or more channels.
327    Subscribe { channels: Vec<String> },
328
329    /// UNSUBSCRIBE \[channel ...\]. Unsubscribe from channels (all if none given).
330    Unsubscribe { channels: Vec<String> },
331
332    /// PSUBSCRIBE `pattern` \[pattern ...\]. Subscribe to channels matching patterns.
333    PSubscribe { patterns: Vec<String> },
334
335    /// PUNSUBSCRIBE \[pattern ...\]. Unsubscribe from patterns (all if none given).
336    PUnsubscribe { patterns: Vec<String> },
337
338    /// PUBLISH `channel` `message`. Publish a message to a channel.
339    Publish { channel: String, message: Bytes },
340
341    /// PUBSUB CHANNELS \[pattern\]. List active channels, optionally matching a glob.
342    PubSubChannels { pattern: Option<String> },
343
344    /// PUBSUB NUMSUB \[channel ...\]. Returns subscriber counts for given channels.
345    PubSubNumSub { channels: Vec<String> },
346
347    /// PUBSUB NUMPAT. Returns the number of active pattern subscriptions.
348    PubSubNumPat,
349
350    // --- vector commands ---
351    /// VADD key element f32 [f32 ...] [METRIC COSINE|L2|IP] [QUANT F32|F16|I8]
352    /// [M n] [EF n]. Adds a vector to a vector set.
353    VAdd {
354        key: String,
355        element: String,
356        vector: Vec<f32>,
357        /// 0 = cosine (default), 1 = l2, 2 = inner product
358        metric: u8,
359        /// 0 = f32 (default), 1 = f16, 2 = i8
360        quantization: u8,
361        /// HNSW connectivity parameter (default 16)
362        connectivity: u32,
363        /// HNSW construction beam width (default 64)
364        expansion_add: u32,
365    },
366
367    /// VADD_BATCH key DIM n element1 f32... element2 f32... [METRIC COSINE|L2|IP]
368    /// [QUANT F32|F16|I8] [M n] [EF n]. Adds multiple vectors in a single command.
369    VAddBatch {
370        key: String,
371        entries: Vec<(String, Vec<f32>)>,
372        dim: usize,
373        /// 0 = cosine (default), 1 = l2, 2 = inner product
374        metric: u8,
375        /// 0 = f32 (default), 1 = f16, 2 = i8
376        quantization: u8,
377        /// HNSW connectivity parameter (default 16)
378        connectivity: u32,
379        /// HNSW construction beam width (default 64)
380        expansion_add: u32,
381    },
382
383    /// VSIM key f32 [f32 ...] COUNT k [EF n] [WITHSCORES].
384    /// Searches for k nearest neighbors.
385    VSim {
386        key: String,
387        query: Vec<f32>,
388        count: usize,
389        ef_search: usize,
390        with_scores: bool,
391    },
392
393    /// VREM key element. Removes a vector from a vector set.
394    VRem { key: String, element: String },
395
396    /// VGET key element. Retrieves the stored vector for an element.
397    VGet { key: String, element: String },
398
399    /// VCARD key. Returns the number of elements in a vector set.
400    VCard { key: String },
401
402    /// VDIM key. Returns the dimensionality of a vector set.
403    VDim { key: String },
404
405    /// VINFO key. Returns metadata about a vector set.
406    VInfo { key: String },
407
408    // --- protobuf commands ---
409    /// PROTO.REGISTER `name` `descriptor_bytes`. Registers a protobuf schema
410    /// (pre-compiled FileDescriptorSet) under the given name.
411    ProtoRegister { name: String, descriptor: Bytes },
412
413    /// PROTO.SET `key` `type_name` `data` \[EX s | PX ms\] \[NX | XX\].
414    /// Stores a validated protobuf value.
415    ProtoSet {
416        key: String,
417        type_name: String,
418        data: Bytes,
419        expire: Option<SetExpire>,
420        /// Only set the key if it does not already exist.
421        nx: bool,
422        /// Only set the key if it already exists.
423        xx: bool,
424    },
425
426    /// PROTO.GET `key`. Returns \[type_name, data\] or nil.
427    ProtoGet { key: String },
428
429    /// PROTO.TYPE `key`. Returns the message type name or nil.
430    ProtoType { key: String },
431
432    /// PROTO.SCHEMAS. Lists all registered schema names.
433    ProtoSchemas,
434
435    /// PROTO.DESCRIBE `name`. Lists message types in a registered schema.
436    ProtoDescribe { name: String },
437
438    /// PROTO.GETFIELD `key` `field_path`. Reads a single field from a
439    /// protobuf value, returning it as a native RESP3 type.
440    ProtoGetField { key: String, field_path: String },
441
442    /// PROTO.SETFIELD `key` `field_path` `value`. Updates a single scalar
443    /// field in a stored protobuf value.
444    ProtoSetField {
445        key: String,
446        field_path: String,
447        value: String,
448    },
449
450    /// PROTO.DELFIELD `key` `field_path`. Clears a field to its default value.
451    ProtoDelField { key: String, field_path: String },
452
453    /// AUTH \[username\] password. Authenticate the connection.
454    Auth {
455        /// Username for ACL-style auth. None for legacy AUTH.
456        username: Option<String>,
457        /// The password to validate.
458        password: String,
459    },
460
461    /// QUIT. Requests the server to close the connection.
462    Quit,
463
464    /// A command we don't recognize (yet).
465    Unknown(String),
466}
467
468/// Flags for the ZADD command.
469#[derive(Debug, Clone, Default, PartialEq)]
470pub struct ZAddFlags {
471    /// Only add new members, don't update existing scores.
472    pub nx: bool,
473    /// Only update existing members, don't add new ones.
474    pub xx: bool,
475    /// Only update when new score > current score.
476    pub gt: bool,
477    /// Only update when new score < current score.
478    pub lt: bool,
479    /// Return count of changed members (added + updated) instead of just added.
480    pub ch: bool,
481}
482
483impl Eq for ZAddFlags {}
484
485impl Command {
486    /// Returns the lowercase command name as a static string.
487    ///
488    /// Used for metrics labels and slow log entries. Zero allocation —
489    /// returns a `&'static str` for every known variant.
490    pub fn command_name(&self) -> &'static str {
491        match self {
492            Command::Ping(_) => "ping",
493            Command::Echo(_) => "echo",
494            Command::Get { .. } => "get",
495            Command::Set { .. } => "set",
496            Command::Incr { .. } => "incr",
497            Command::Decr { .. } => "decr",
498            Command::IncrBy { .. } => "incrby",
499            Command::DecrBy { .. } => "decrby",
500            Command::IncrByFloat { .. } => "incrbyfloat",
501            Command::Append { .. } => "append",
502            Command::Strlen { .. } => "strlen",
503            Command::Keys { .. } => "keys",
504            Command::Rename { .. } => "rename",
505            Command::Del { .. } => "del",
506            Command::Unlink { .. } => "unlink",
507            Command::Exists { .. } => "exists",
508            Command::MGet { .. } => "mget",
509            Command::MSet { .. } => "mset",
510            Command::Expire { .. } => "expire",
511            Command::Ttl { .. } => "ttl",
512            Command::Persist { .. } => "persist",
513            Command::Pttl { .. } => "pttl",
514            Command::Pexpire { .. } => "pexpire",
515            Command::DbSize => "dbsize",
516            Command::Info { .. } => "info",
517            Command::BgSave => "bgsave",
518            Command::BgRewriteAof => "bgrewriteaof",
519            Command::FlushDb { .. } => "flushdb",
520            Command::Scan { .. } => "scan",
521            Command::LPush { .. } => "lpush",
522            Command::RPush { .. } => "rpush",
523            Command::LPop { .. } => "lpop",
524            Command::RPop { .. } => "rpop",
525            Command::LRange { .. } => "lrange",
526            Command::LLen { .. } => "llen",
527            Command::Type { .. } => "type",
528            Command::ZAdd { .. } => "zadd",
529            Command::ZRem { .. } => "zrem",
530            Command::ZScore { .. } => "zscore",
531            Command::ZRank { .. } => "zrank",
532            Command::ZCard { .. } => "zcard",
533            Command::ZRange { .. } => "zrange",
534            Command::HSet { .. } => "hset",
535            Command::HGet { .. } => "hget",
536            Command::HGetAll { .. } => "hgetall",
537            Command::HDel { .. } => "hdel",
538            Command::HExists { .. } => "hexists",
539            Command::HLen { .. } => "hlen",
540            Command::HIncrBy { .. } => "hincrby",
541            Command::HKeys { .. } => "hkeys",
542            Command::HVals { .. } => "hvals",
543            Command::HMGet { .. } => "hmget",
544            Command::SAdd { .. } => "sadd",
545            Command::SRem { .. } => "srem",
546            Command::SMembers { .. } => "smembers",
547            Command::SIsMember { .. } => "sismember",
548            Command::SCard { .. } => "scard",
549            Command::ClusterInfo => "cluster_info",
550            Command::ClusterNodes => "cluster_nodes",
551            Command::ClusterSlots => "cluster_slots",
552            Command::ClusterKeySlot { .. } => "cluster_keyslot",
553            Command::ClusterMyId => "cluster_myid",
554            Command::ClusterSetSlotImporting { .. } => "cluster_setslot",
555            Command::ClusterSetSlotMigrating { .. } => "cluster_setslot",
556            Command::ClusterSetSlotNode { .. } => "cluster_setslot",
557            Command::ClusterSetSlotStable { .. } => "cluster_setslot",
558            Command::ClusterMeet { .. } => "cluster_meet",
559            Command::ClusterAddSlots { .. } => "cluster_addslots",
560            Command::ClusterDelSlots { .. } => "cluster_delslots",
561            Command::ClusterForget { .. } => "cluster_forget",
562            Command::ClusterReplicate { .. } => "cluster_replicate",
563            Command::ClusterFailover { .. } => "cluster_failover",
564            Command::ClusterCountKeysInSlot { .. } => "cluster_countkeysinslot",
565            Command::ClusterGetKeysInSlot { .. } => "cluster_getkeysinslot",
566            Command::Migrate { .. } => "migrate",
567            Command::Asking => "asking",
568            Command::SlowLogGet { .. } => "slowlog",
569            Command::SlowLogLen => "slowlog",
570            Command::SlowLogReset => "slowlog",
571            Command::Subscribe { .. } => "subscribe",
572            Command::Unsubscribe { .. } => "unsubscribe",
573            Command::PSubscribe { .. } => "psubscribe",
574            Command::PUnsubscribe { .. } => "punsubscribe",
575            Command::Publish { .. } => "publish",
576            Command::PubSubChannels { .. } => "pubsub",
577            Command::PubSubNumSub { .. } => "pubsub",
578            Command::PubSubNumPat => "pubsub",
579            Command::VAdd { .. } => "vadd",
580            Command::VAddBatch { .. } => "vadd_batch",
581            Command::VSim { .. } => "vsim",
582            Command::VRem { .. } => "vrem",
583            Command::VGet { .. } => "vget",
584            Command::VCard { .. } => "vcard",
585            Command::VDim { .. } => "vdim",
586            Command::VInfo { .. } => "vinfo",
587            Command::ProtoRegister { .. } => "proto.register",
588            Command::ProtoSet { .. } => "proto.set",
589            Command::ProtoGet { .. } => "proto.get",
590            Command::ProtoType { .. } => "proto.type",
591            Command::ProtoSchemas => "proto.schemas",
592            Command::ProtoDescribe { .. } => "proto.describe",
593            Command::ProtoGetField { .. } => "proto.getfield",
594            Command::ProtoSetField { .. } => "proto.setfield",
595            Command::ProtoDelField { .. } => "proto.delfield",
596            Command::Auth { .. } => "auth",
597            Command::Quit => "quit",
598            Command::Unknown(_) => "unknown",
599        }
600    }
601
602    /// Parses a [`Frame`] into a [`Command`].
603    ///
604    /// Expects an array frame where the first element is the command name
605    /// (as a bulk or simple string) and the rest are arguments.
606    pub fn from_frame(frame: Frame) -> Result<Command, ProtocolError> {
607        let frames = match frame {
608            Frame::Array(frames) => frames,
609            _ => {
610                return Err(ProtocolError::InvalidCommandFrame(
611                    "expected array frame".into(),
612                ));
613            }
614        };
615
616        if frames.is_empty() {
617            return Err(ProtocolError::InvalidCommandFrame(
618                "empty command array".into(),
619            ));
620        }
621
622        let name = extract_string(&frames[0])?;
623        let name_upper = name.to_ascii_uppercase();
624
625        match name_upper.as_str() {
626            "PING" => parse_ping(&frames[1..]),
627            "ECHO" => parse_echo(&frames[1..]),
628            "GET" => parse_get(&frames[1..]),
629            "SET" => parse_set(&frames[1..]),
630            "INCR" => parse_incr(&frames[1..]),
631            "DECR" => parse_decr(&frames[1..]),
632            "INCRBY" => parse_incrby(&frames[1..]),
633            "DECRBY" => parse_decrby(&frames[1..]),
634            "INCRBYFLOAT" => parse_incrbyfloat(&frames[1..]),
635            "APPEND" => parse_append(&frames[1..]),
636            "STRLEN" => parse_strlen(&frames[1..]),
637            "KEYS" => parse_keys(&frames[1..]),
638            "RENAME" => parse_rename(&frames[1..]),
639            "DEL" => parse_del(&frames[1..]),
640            "UNLINK" => parse_unlink(&frames[1..]),
641            "EXISTS" => parse_exists(&frames[1..]),
642            "MGET" => parse_mget(&frames[1..]),
643            "MSET" => parse_mset(&frames[1..]),
644            "EXPIRE" => parse_expire(&frames[1..]),
645            "TTL" => parse_ttl(&frames[1..]),
646            "PERSIST" => parse_persist(&frames[1..]),
647            "PTTL" => parse_pttl(&frames[1..]),
648            "PEXPIRE" => parse_pexpire(&frames[1..]),
649            "DBSIZE" => parse_dbsize(&frames[1..]),
650            "INFO" => parse_info(&frames[1..]),
651            "BGSAVE" => parse_bgsave(&frames[1..]),
652            "BGREWRITEAOF" => parse_bgrewriteaof(&frames[1..]),
653            "FLUSHDB" => parse_flushdb(&frames[1..]),
654            "SCAN" => parse_scan(&frames[1..]),
655            "LPUSH" => parse_lpush(&frames[1..]),
656            "RPUSH" => parse_rpush(&frames[1..]),
657            "LPOP" => parse_lpop(&frames[1..]),
658            "RPOP" => parse_rpop(&frames[1..]),
659            "LRANGE" => parse_lrange(&frames[1..]),
660            "LLEN" => parse_llen(&frames[1..]),
661            "TYPE" => parse_type(&frames[1..]),
662            "ZADD" => parse_zadd(&frames[1..]),
663            "ZREM" => parse_zrem(&frames[1..]),
664            "ZSCORE" => parse_zscore(&frames[1..]),
665            "ZRANK" => parse_zrank(&frames[1..]),
666            "ZCARD" => parse_zcard(&frames[1..]),
667            "ZRANGE" => parse_zrange(&frames[1..]),
668            "HSET" => parse_hset(&frames[1..]),
669            "HGET" => parse_hget(&frames[1..]),
670            "HGETALL" => parse_hgetall(&frames[1..]),
671            "HDEL" => parse_hdel(&frames[1..]),
672            "HEXISTS" => parse_hexists(&frames[1..]),
673            "HLEN" => parse_hlen(&frames[1..]),
674            "HINCRBY" => parse_hincrby(&frames[1..]),
675            "HKEYS" => parse_hkeys(&frames[1..]),
676            "HVALS" => parse_hvals(&frames[1..]),
677            "HMGET" => parse_hmget(&frames[1..]),
678            "SADD" => parse_sadd(&frames[1..]),
679            "SREM" => parse_srem(&frames[1..]),
680            "SMEMBERS" => parse_smembers(&frames[1..]),
681            "SISMEMBER" => parse_sismember(&frames[1..]),
682            "SCARD" => parse_scard(&frames[1..]),
683            "CLUSTER" => parse_cluster(&frames[1..]),
684            "ASKING" => parse_asking(&frames[1..]),
685            "MIGRATE" => parse_migrate(&frames[1..]),
686            "SLOWLOG" => parse_slowlog(&frames[1..]),
687            "SUBSCRIBE" => parse_subscribe(&frames[1..]),
688            "UNSUBSCRIBE" => parse_unsubscribe(&frames[1..]),
689            "PSUBSCRIBE" => parse_psubscribe(&frames[1..]),
690            "PUNSUBSCRIBE" => parse_punsubscribe(&frames[1..]),
691            "PUBLISH" => parse_publish(&frames[1..]),
692            "PUBSUB" => parse_pubsub(&frames[1..]),
693            "VADD" => parse_vadd(&frames[1..]),
694            "VADD_BATCH" => parse_vadd_batch(&frames[1..]),
695            "VSIM" => parse_vsim(&frames[1..]),
696            "VREM" => parse_vrem(&frames[1..]),
697            "VGET" => parse_vget(&frames[1..]),
698            "VCARD" => parse_vcard(&frames[1..]),
699            "VDIM" => parse_vdim(&frames[1..]),
700            "VINFO" => parse_vinfo(&frames[1..]),
701            "PROTO.REGISTER" => parse_proto_register(&frames[1..]),
702            "PROTO.SET" => parse_proto_set(&frames[1..]),
703            "PROTO.GET" => parse_proto_get(&frames[1..]),
704            "PROTO.TYPE" => parse_proto_type(&frames[1..]),
705            "PROTO.SCHEMAS" => parse_proto_schemas(&frames[1..]),
706            "PROTO.DESCRIBE" => parse_proto_describe(&frames[1..]),
707            "PROTO.GETFIELD" => parse_proto_getfield(&frames[1..]),
708            "PROTO.SETFIELD" => parse_proto_setfield(&frames[1..]),
709            "PROTO.DELFIELD" => parse_proto_delfield(&frames[1..]),
710            "AUTH" => parse_auth(&frames[1..]),
711            "QUIT" => parse_quit(&frames[1..]),
712            _ => Ok(Command::Unknown(name)),
713        }
714    }
715}
716
717/// Extracts a UTF-8 string from a Bulk or Simple frame.
718///
719/// Validates UTF-8 in-place on the Bytes buffer to avoid an
720/// intermediate Vec<u8> allocation from `to_vec()`.
721fn extract_string(frame: &Frame) -> Result<String, ProtocolError> {
722    match frame {
723        Frame::Bulk(data) => {
724            let s = std::str::from_utf8(data).map_err(|_| {
725                ProtocolError::InvalidCommandFrame("command name is not valid utf-8".into())
726            })?;
727            Ok(s.to_owned())
728        }
729        Frame::Simple(s) => Ok(s.clone()),
730        _ => Err(ProtocolError::InvalidCommandFrame(
731            "expected bulk or simple string for command name".into(),
732        )),
733    }
734}
735
736/// Extracts raw bytes from a Bulk or Simple frame.
737fn extract_bytes(frame: &Frame) -> Result<Bytes, ProtocolError> {
738    match frame {
739        Frame::Bulk(data) => Ok(data.clone()),
740        Frame::Simple(s) => Ok(Bytes::copy_from_slice(s.as_bytes())),
741        _ => Err(ProtocolError::InvalidCommandFrame(
742            "expected bulk or simple string argument".into(),
743        )),
744    }
745}
746
747/// Extracts all frames in a slice as UTF-8 strings.
748fn extract_strings(frames: &[Frame]) -> Result<Vec<String>, ProtocolError> {
749    frames.iter().map(extract_string).collect()
750}
751
752/// Extracts all frames in a slice as raw byte buffers.
753fn extract_bytes_vec(frames: &[Frame]) -> Result<Vec<Bytes>, ProtocolError> {
754    frames.iter().map(extract_bytes).collect()
755}
756
757/// Parses a string argument as a positive u64.
758fn parse_u64(frame: &Frame, cmd: &str) -> Result<u64, ProtocolError> {
759    let s = extract_string(frame)?;
760    s.parse::<u64>().map_err(|_| {
761        ProtocolError::InvalidCommandFrame(format!("value is not a valid integer for '{cmd}'"))
762    })
763}
764
765fn parse_ping(args: &[Frame]) -> Result<Command, ProtocolError> {
766    match args.len() {
767        0 => Ok(Command::Ping(None)),
768        1 => {
769            let msg = extract_bytes(&args[0])?;
770            Ok(Command::Ping(Some(msg)))
771        }
772        _ => Err(ProtocolError::WrongArity("PING".into())),
773    }
774}
775
776fn parse_echo(args: &[Frame]) -> Result<Command, ProtocolError> {
777    if args.len() != 1 {
778        return Err(ProtocolError::WrongArity("ECHO".into()));
779    }
780    let msg = extract_bytes(&args[0])?;
781    Ok(Command::Echo(msg))
782}
783
784fn parse_get(args: &[Frame]) -> Result<Command, ProtocolError> {
785    if args.len() != 1 {
786        return Err(ProtocolError::WrongArity("GET".into()));
787    }
788    let key = extract_string(&args[0])?;
789    Ok(Command::Get { key })
790}
791
792fn parse_set(args: &[Frame]) -> Result<Command, ProtocolError> {
793    if args.len() < 2 {
794        return Err(ProtocolError::WrongArity("SET".into()));
795    }
796
797    let key = extract_string(&args[0])?;
798    let value = extract_bytes(&args[1])?;
799
800    let mut expire = None;
801    let mut nx = false;
802    let mut xx = false;
803    let mut idx = 2;
804
805    while idx < args.len() {
806        let flag = extract_string(&args[idx])?.to_ascii_uppercase();
807        match flag.as_str() {
808            "NX" => {
809                nx = true;
810                idx += 1;
811            }
812            "XX" => {
813                xx = true;
814                idx += 1;
815            }
816            "EX" => {
817                idx += 1;
818                if idx >= args.len() {
819                    return Err(ProtocolError::WrongArity("SET".into()));
820                }
821                let amount = parse_u64(&args[idx], "SET")?;
822                if amount == 0 {
823                    return Err(ProtocolError::InvalidCommandFrame(
824                        "invalid expire time in 'SET' command".into(),
825                    ));
826                }
827                expire = Some(SetExpire::Ex(amount));
828                idx += 1;
829            }
830            "PX" => {
831                idx += 1;
832                if idx >= args.len() {
833                    return Err(ProtocolError::WrongArity("SET".into()));
834                }
835                let amount = parse_u64(&args[idx], "SET")?;
836                if amount == 0 {
837                    return Err(ProtocolError::InvalidCommandFrame(
838                        "invalid expire time in 'SET' command".into(),
839                    ));
840                }
841                expire = Some(SetExpire::Px(amount));
842                idx += 1;
843            }
844            _ => {
845                return Err(ProtocolError::InvalidCommandFrame(format!(
846                    "unsupported SET option '{flag}'"
847                )));
848            }
849        }
850    }
851
852    if nx && xx {
853        return Err(ProtocolError::InvalidCommandFrame(
854            "XX and NX options at the same time are not compatible".into(),
855        ));
856    }
857
858    Ok(Command::Set {
859        key,
860        value,
861        expire,
862        nx,
863        xx,
864    })
865}
866
867fn parse_incr(args: &[Frame]) -> Result<Command, ProtocolError> {
868    if args.len() != 1 {
869        return Err(ProtocolError::WrongArity("INCR".into()));
870    }
871    let key = extract_string(&args[0])?;
872    Ok(Command::Incr { key })
873}
874
875fn parse_decr(args: &[Frame]) -> Result<Command, ProtocolError> {
876    if args.len() != 1 {
877        return Err(ProtocolError::WrongArity("DECR".into()));
878    }
879    let key = extract_string(&args[0])?;
880    Ok(Command::Decr { key })
881}
882
883fn parse_incrby(args: &[Frame]) -> Result<Command, ProtocolError> {
884    if args.len() != 2 {
885        return Err(ProtocolError::WrongArity("INCRBY".into()));
886    }
887    let key = extract_string(&args[0])?;
888    let delta = parse_i64(&args[1], "INCRBY")?;
889    Ok(Command::IncrBy { key, delta })
890}
891
892fn parse_decrby(args: &[Frame]) -> Result<Command, ProtocolError> {
893    if args.len() != 2 {
894        return Err(ProtocolError::WrongArity("DECRBY".into()));
895    }
896    let key = extract_string(&args[0])?;
897    let delta = parse_i64(&args[1], "DECRBY")?;
898    Ok(Command::DecrBy { key, delta })
899}
900
901fn parse_incrbyfloat(args: &[Frame]) -> Result<Command, ProtocolError> {
902    if args.len() != 2 {
903        return Err(ProtocolError::WrongArity("INCRBYFLOAT".into()));
904    }
905    let key = extract_string(&args[0])?;
906    let s = extract_string(&args[1])?;
907    let delta: f64 = s.parse().map_err(|_| {
908        ProtocolError::InvalidCommandFrame("value is not a valid float for 'INCRBYFLOAT'".into())
909    })?;
910    if delta.is_nan() || delta.is_infinite() {
911        return Err(ProtocolError::InvalidCommandFrame(
912            "increment would produce NaN or Infinity".into(),
913        ));
914    }
915    Ok(Command::IncrByFloat { key, delta })
916}
917
918fn parse_append(args: &[Frame]) -> Result<Command, ProtocolError> {
919    if args.len() != 2 {
920        return Err(ProtocolError::WrongArity("APPEND".into()));
921    }
922    let key = extract_string(&args[0])?;
923    let value = extract_bytes(&args[1])?;
924    Ok(Command::Append { key, value })
925}
926
927fn parse_strlen(args: &[Frame]) -> Result<Command, ProtocolError> {
928    if args.len() != 1 {
929        return Err(ProtocolError::WrongArity("STRLEN".into()));
930    }
931    let key = extract_string(&args[0])?;
932    Ok(Command::Strlen { key })
933}
934
935fn parse_keys(args: &[Frame]) -> Result<Command, ProtocolError> {
936    if args.len() != 1 {
937        return Err(ProtocolError::WrongArity("KEYS".into()));
938    }
939    let pattern = extract_string(&args[0])?;
940    Ok(Command::Keys { pattern })
941}
942
943fn parse_rename(args: &[Frame]) -> Result<Command, ProtocolError> {
944    if args.len() != 2 {
945        return Err(ProtocolError::WrongArity("RENAME".into()));
946    }
947    let key = extract_string(&args[0])?;
948    let newkey = extract_string(&args[1])?;
949    Ok(Command::Rename { key, newkey })
950}
951
952fn parse_del(args: &[Frame]) -> Result<Command, ProtocolError> {
953    if args.is_empty() {
954        return Err(ProtocolError::WrongArity("DEL".into()));
955    }
956    let keys = extract_strings(args)?;
957    Ok(Command::Del { keys })
958}
959
960fn parse_exists(args: &[Frame]) -> Result<Command, ProtocolError> {
961    if args.is_empty() {
962        return Err(ProtocolError::WrongArity("EXISTS".into()));
963    }
964    let keys = extract_strings(args)?;
965    Ok(Command::Exists { keys })
966}
967
968fn parse_mget(args: &[Frame]) -> Result<Command, ProtocolError> {
969    if args.is_empty() {
970        return Err(ProtocolError::WrongArity("MGET".into()));
971    }
972    let keys = extract_strings(args)?;
973    Ok(Command::MGet { keys })
974}
975
976fn parse_mset(args: &[Frame]) -> Result<Command, ProtocolError> {
977    if args.is_empty() || !args.len().is_multiple_of(2) {
978        return Err(ProtocolError::WrongArity("MSET".into()));
979    }
980    let mut pairs = Vec::with_capacity(args.len() / 2);
981    for chunk in args.chunks(2) {
982        let key = extract_string(&chunk[0])?;
983        let value = extract_bytes(&chunk[1])?;
984        pairs.push((key, value));
985    }
986    Ok(Command::MSet { pairs })
987}
988
989fn parse_expire(args: &[Frame]) -> Result<Command, ProtocolError> {
990    if args.len() != 2 {
991        return Err(ProtocolError::WrongArity("EXPIRE".into()));
992    }
993    let key = extract_string(&args[0])?;
994    let seconds = parse_u64(&args[1], "EXPIRE")?;
995
996    if seconds == 0 {
997        return Err(ProtocolError::InvalidCommandFrame(
998            "invalid expire time in 'EXPIRE' command".into(),
999        ));
1000    }
1001
1002    Ok(Command::Expire { key, seconds })
1003}
1004
1005fn parse_ttl(args: &[Frame]) -> Result<Command, ProtocolError> {
1006    if args.len() != 1 {
1007        return Err(ProtocolError::WrongArity("TTL".into()));
1008    }
1009    let key = extract_string(&args[0])?;
1010    Ok(Command::Ttl { key })
1011}
1012
1013fn parse_persist(args: &[Frame]) -> Result<Command, ProtocolError> {
1014    if args.len() != 1 {
1015        return Err(ProtocolError::WrongArity("PERSIST".into()));
1016    }
1017    let key = extract_string(&args[0])?;
1018    Ok(Command::Persist { key })
1019}
1020
1021fn parse_pttl(args: &[Frame]) -> Result<Command, ProtocolError> {
1022    if args.len() != 1 {
1023        return Err(ProtocolError::WrongArity("PTTL".into()));
1024    }
1025    let key = extract_string(&args[0])?;
1026    Ok(Command::Pttl { key })
1027}
1028
1029fn parse_pexpire(args: &[Frame]) -> Result<Command, ProtocolError> {
1030    if args.len() != 2 {
1031        return Err(ProtocolError::WrongArity("PEXPIRE".into()));
1032    }
1033    let key = extract_string(&args[0])?;
1034    let milliseconds = parse_u64(&args[1], "PEXPIRE")?;
1035
1036    if milliseconds == 0 {
1037        return Err(ProtocolError::InvalidCommandFrame(
1038            "invalid expire time in 'PEXPIRE' command".into(),
1039        ));
1040    }
1041
1042    Ok(Command::Pexpire { key, milliseconds })
1043}
1044
1045fn parse_dbsize(args: &[Frame]) -> Result<Command, ProtocolError> {
1046    if !args.is_empty() {
1047        return Err(ProtocolError::WrongArity("DBSIZE".into()));
1048    }
1049    Ok(Command::DbSize)
1050}
1051
1052fn parse_info(args: &[Frame]) -> Result<Command, ProtocolError> {
1053    match args.len() {
1054        0 => Ok(Command::Info { section: None }),
1055        1 => {
1056            let section = extract_string(&args[0])?;
1057            Ok(Command::Info {
1058                section: Some(section),
1059            })
1060        }
1061        _ => Err(ProtocolError::WrongArity("INFO".into())),
1062    }
1063}
1064
1065fn parse_bgsave(args: &[Frame]) -> Result<Command, ProtocolError> {
1066    if !args.is_empty() {
1067        return Err(ProtocolError::WrongArity("BGSAVE".into()));
1068    }
1069    Ok(Command::BgSave)
1070}
1071
1072fn parse_bgrewriteaof(args: &[Frame]) -> Result<Command, ProtocolError> {
1073    if !args.is_empty() {
1074        return Err(ProtocolError::WrongArity("BGREWRITEAOF".into()));
1075    }
1076    Ok(Command::BgRewriteAof)
1077}
1078
1079fn parse_flushdb(args: &[Frame]) -> Result<Command, ProtocolError> {
1080    if args.is_empty() {
1081        return Ok(Command::FlushDb { async_mode: false });
1082    }
1083    if args.len() == 1 {
1084        let arg = extract_string(&args[0])?;
1085        if arg.eq_ignore_ascii_case("ASYNC") {
1086            return Ok(Command::FlushDb { async_mode: true });
1087        }
1088    }
1089    Err(ProtocolError::WrongArity("FLUSHDB".into()))
1090}
1091
1092fn parse_unlink(args: &[Frame]) -> Result<Command, ProtocolError> {
1093    if args.is_empty() {
1094        return Err(ProtocolError::WrongArity("UNLINK".into()));
1095    }
1096    let keys = extract_strings(args)?;
1097    Ok(Command::Unlink { keys })
1098}
1099
1100fn parse_scan(args: &[Frame]) -> Result<Command, ProtocolError> {
1101    if args.is_empty() {
1102        return Err(ProtocolError::WrongArity("SCAN".into()));
1103    }
1104
1105    let cursor = parse_u64(&args[0], "SCAN")?;
1106    let mut pattern = None;
1107    let mut count = None;
1108    let mut idx = 1;
1109
1110    while idx < args.len() {
1111        let flag = extract_string(&args[idx])?.to_ascii_uppercase();
1112        match flag.as_str() {
1113            "MATCH" => {
1114                idx += 1;
1115                if idx >= args.len() {
1116                    return Err(ProtocolError::WrongArity("SCAN".into()));
1117                }
1118                pattern = Some(extract_string(&args[idx])?);
1119                idx += 1;
1120            }
1121            "COUNT" => {
1122                idx += 1;
1123                if idx >= args.len() {
1124                    return Err(ProtocolError::WrongArity("SCAN".into()));
1125                }
1126                let n = parse_u64(&args[idx], "SCAN")?;
1127                if n > MAX_SCAN_COUNT {
1128                    return Err(ProtocolError::InvalidCommandFrame(format!(
1129                        "SCAN COUNT {n} exceeds max {MAX_SCAN_COUNT}"
1130                    )));
1131                }
1132                count = Some(n as usize);
1133                idx += 1;
1134            }
1135            _ => {
1136                return Err(ProtocolError::InvalidCommandFrame(format!(
1137                    "unsupported SCAN option '{flag}'"
1138                )));
1139            }
1140        }
1141    }
1142
1143    Ok(Command::Scan {
1144        cursor,
1145        pattern,
1146        count,
1147    })
1148}
1149
1150/// Parses a string argument as an i64. Used by LRANGE for start/stop indices.
1151fn parse_i64(frame: &Frame, cmd: &str) -> Result<i64, ProtocolError> {
1152    let s = extract_string(frame)?;
1153    s.parse::<i64>().map_err(|_| {
1154        ProtocolError::InvalidCommandFrame(format!("value is not a valid integer for '{cmd}'"))
1155    })
1156}
1157
1158fn parse_lpush(args: &[Frame]) -> Result<Command, ProtocolError> {
1159    if args.len() < 2 {
1160        return Err(ProtocolError::WrongArity("LPUSH".into()));
1161    }
1162    let key = extract_string(&args[0])?;
1163    let values = extract_bytes_vec(&args[1..])?;
1164    Ok(Command::LPush { key, values })
1165}
1166
1167fn parse_rpush(args: &[Frame]) -> Result<Command, ProtocolError> {
1168    if args.len() < 2 {
1169        return Err(ProtocolError::WrongArity("RPUSH".into()));
1170    }
1171    let key = extract_string(&args[0])?;
1172    let values = extract_bytes_vec(&args[1..])?;
1173    Ok(Command::RPush { key, values })
1174}
1175
1176fn parse_lpop(args: &[Frame]) -> Result<Command, ProtocolError> {
1177    if args.len() != 1 {
1178        return Err(ProtocolError::WrongArity("LPOP".into()));
1179    }
1180    let key = extract_string(&args[0])?;
1181    Ok(Command::LPop { key })
1182}
1183
1184fn parse_rpop(args: &[Frame]) -> Result<Command, ProtocolError> {
1185    if args.len() != 1 {
1186        return Err(ProtocolError::WrongArity("RPOP".into()));
1187    }
1188    let key = extract_string(&args[0])?;
1189    Ok(Command::RPop { key })
1190}
1191
1192fn parse_lrange(args: &[Frame]) -> Result<Command, ProtocolError> {
1193    if args.len() != 3 {
1194        return Err(ProtocolError::WrongArity("LRANGE".into()));
1195    }
1196    let key = extract_string(&args[0])?;
1197    let start = parse_i64(&args[1], "LRANGE")?;
1198    let stop = parse_i64(&args[2], "LRANGE")?;
1199    Ok(Command::LRange { key, start, stop })
1200}
1201
1202fn parse_llen(args: &[Frame]) -> Result<Command, ProtocolError> {
1203    if args.len() != 1 {
1204        return Err(ProtocolError::WrongArity("LLEN".into()));
1205    }
1206    let key = extract_string(&args[0])?;
1207    Ok(Command::LLen { key })
1208}
1209
1210fn parse_type(args: &[Frame]) -> Result<Command, ProtocolError> {
1211    if args.len() != 1 {
1212        return Err(ProtocolError::WrongArity("TYPE".into()));
1213    }
1214    let key = extract_string(&args[0])?;
1215    Ok(Command::Type { key })
1216}
1217
1218/// Parses a string argument as an f64 score.
1219fn parse_f64(frame: &Frame, cmd: &str) -> Result<f64, ProtocolError> {
1220    let s = extract_string(frame)?;
1221    let v = s.parse::<f64>().map_err(|_| {
1222        ProtocolError::InvalidCommandFrame(format!("value is not a valid float for '{cmd}'"))
1223    })?;
1224    if v.is_nan() || v.is_infinite() {
1225        return Err(ProtocolError::InvalidCommandFrame(format!(
1226            "value is not a valid finite float for '{cmd}'"
1227        )));
1228    }
1229    Ok(v)
1230}
1231
1232fn parse_zadd(args: &[Frame]) -> Result<Command, ProtocolError> {
1233    // ZADD key [NX|XX] [GT|LT] [CH] score member [score member ...]
1234    if args.len() < 3 {
1235        return Err(ProtocolError::WrongArity("ZADD".into()));
1236    }
1237
1238    let key = extract_string(&args[0])?;
1239    let mut flags = ZAddFlags::default();
1240    let mut idx = 1;
1241
1242    // parse optional flags before score/member pairs
1243    while idx < args.len() {
1244        let s = extract_string(&args[idx])?.to_ascii_uppercase();
1245        match s.as_str() {
1246            "NX" => {
1247                flags.nx = true;
1248                idx += 1;
1249            }
1250            "XX" => {
1251                flags.xx = true;
1252                idx += 1;
1253            }
1254            "GT" => {
1255                flags.gt = true;
1256                idx += 1;
1257            }
1258            "LT" => {
1259                flags.lt = true;
1260                idx += 1;
1261            }
1262            "CH" => {
1263                flags.ch = true;
1264                idx += 1;
1265            }
1266            _ => break,
1267        }
1268    }
1269
1270    // NX and XX are mutually exclusive
1271    if flags.nx && flags.xx {
1272        return Err(ProtocolError::InvalidCommandFrame(
1273            "XX and NX options at the same time are not compatible".into(),
1274        ));
1275    }
1276    // GT and LT are mutually exclusive
1277    if flags.gt && flags.lt {
1278        return Err(ProtocolError::InvalidCommandFrame(
1279            "GT and LT options at the same time are not compatible".into(),
1280        ));
1281    }
1282
1283    // remaining args must be score/member pairs
1284    let remaining = &args[idx..];
1285    if remaining.is_empty() || !remaining.len().is_multiple_of(2) {
1286        return Err(ProtocolError::WrongArity("ZADD".into()));
1287    }
1288
1289    let mut members = Vec::with_capacity(remaining.len() / 2);
1290    for pair in remaining.chunks(2) {
1291        let score = parse_f64(&pair[0], "ZADD")?;
1292        let member = extract_string(&pair[1])?;
1293        members.push((score, member));
1294    }
1295
1296    Ok(Command::ZAdd {
1297        key,
1298        flags,
1299        members,
1300    })
1301}
1302
1303fn parse_zcard(args: &[Frame]) -> Result<Command, ProtocolError> {
1304    if args.len() != 1 {
1305        return Err(ProtocolError::WrongArity("ZCARD".into()));
1306    }
1307    let key = extract_string(&args[0])?;
1308    Ok(Command::ZCard { key })
1309}
1310
1311fn parse_zrem(args: &[Frame]) -> Result<Command, ProtocolError> {
1312    if args.len() < 2 {
1313        return Err(ProtocolError::WrongArity("ZREM".into()));
1314    }
1315    let key = extract_string(&args[0])?;
1316    let members = extract_strings(&args[1..])?;
1317    Ok(Command::ZRem { key, members })
1318}
1319
1320fn parse_zscore(args: &[Frame]) -> Result<Command, ProtocolError> {
1321    if args.len() != 2 {
1322        return Err(ProtocolError::WrongArity("ZSCORE".into()));
1323    }
1324    let key = extract_string(&args[0])?;
1325    let member = extract_string(&args[1])?;
1326    Ok(Command::ZScore { key, member })
1327}
1328
1329fn parse_zrank(args: &[Frame]) -> Result<Command, ProtocolError> {
1330    if args.len() != 2 {
1331        return Err(ProtocolError::WrongArity("ZRANK".into()));
1332    }
1333    let key = extract_string(&args[0])?;
1334    let member = extract_string(&args[1])?;
1335    Ok(Command::ZRank { key, member })
1336}
1337
1338fn parse_zrange(args: &[Frame]) -> Result<Command, ProtocolError> {
1339    if args.len() < 3 || args.len() > 4 {
1340        return Err(ProtocolError::WrongArity("ZRANGE".into()));
1341    }
1342    let key = extract_string(&args[0])?;
1343    let start = parse_i64(&args[1], "ZRANGE")?;
1344    let stop = parse_i64(&args[2], "ZRANGE")?;
1345
1346    let with_scores = if args.len() == 4 {
1347        let opt = extract_string(&args[3])?.to_ascii_uppercase();
1348        if opt != "WITHSCORES" {
1349            return Err(ProtocolError::InvalidCommandFrame(format!(
1350                "unsupported ZRANGE option '{opt}'"
1351            )));
1352        }
1353        true
1354    } else {
1355        false
1356    };
1357
1358    Ok(Command::ZRange {
1359        key,
1360        start,
1361        stop,
1362        with_scores,
1363    })
1364}
1365
1366// --- hash commands ---
1367
1368fn parse_hset(args: &[Frame]) -> Result<Command, ProtocolError> {
1369    // HSET key field value [field value ...]
1370    // args = [key, field, value, ...]
1371    // Need at least 3 args, and after key we need pairs (so remaining count must be even)
1372    if args.len() < 3 || !(args.len() - 1).is_multiple_of(2) {
1373        return Err(ProtocolError::WrongArity("HSET".into()));
1374    }
1375
1376    let key = extract_string(&args[0])?;
1377    let mut fields = Vec::with_capacity((args.len() - 1) / 2);
1378
1379    for chunk in args[1..].chunks(2) {
1380        let field = extract_string(&chunk[0])?;
1381        let value = extract_bytes(&chunk[1])?;
1382        fields.push((field, value));
1383    }
1384
1385    Ok(Command::HSet { key, fields })
1386}
1387
1388fn parse_hget(args: &[Frame]) -> Result<Command, ProtocolError> {
1389    if args.len() != 2 {
1390        return Err(ProtocolError::WrongArity("HGET".into()));
1391    }
1392    let key = extract_string(&args[0])?;
1393    let field = extract_string(&args[1])?;
1394    Ok(Command::HGet { key, field })
1395}
1396
1397fn parse_hgetall(args: &[Frame]) -> Result<Command, ProtocolError> {
1398    if args.len() != 1 {
1399        return Err(ProtocolError::WrongArity("HGETALL".into()));
1400    }
1401    let key = extract_string(&args[0])?;
1402    Ok(Command::HGetAll { key })
1403}
1404
1405fn parse_hdel(args: &[Frame]) -> Result<Command, ProtocolError> {
1406    if args.len() < 2 {
1407        return Err(ProtocolError::WrongArity("HDEL".into()));
1408    }
1409    let key = extract_string(&args[0])?;
1410    let fields = extract_strings(&args[1..])?;
1411    Ok(Command::HDel { key, fields })
1412}
1413
1414fn parse_hexists(args: &[Frame]) -> Result<Command, ProtocolError> {
1415    if args.len() != 2 {
1416        return Err(ProtocolError::WrongArity("HEXISTS".into()));
1417    }
1418    let key = extract_string(&args[0])?;
1419    let field = extract_string(&args[1])?;
1420    Ok(Command::HExists { key, field })
1421}
1422
1423fn parse_hlen(args: &[Frame]) -> Result<Command, ProtocolError> {
1424    if args.len() != 1 {
1425        return Err(ProtocolError::WrongArity("HLEN".into()));
1426    }
1427    let key = extract_string(&args[0])?;
1428    Ok(Command::HLen { key })
1429}
1430
1431fn parse_hincrby(args: &[Frame]) -> Result<Command, ProtocolError> {
1432    if args.len() != 3 {
1433        return Err(ProtocolError::WrongArity("HINCRBY".into()));
1434    }
1435    let key = extract_string(&args[0])?;
1436    let field = extract_string(&args[1])?;
1437    let delta = parse_i64(&args[2], "HINCRBY")?;
1438    Ok(Command::HIncrBy { key, field, delta })
1439}
1440
1441fn parse_hkeys(args: &[Frame]) -> Result<Command, ProtocolError> {
1442    if args.len() != 1 {
1443        return Err(ProtocolError::WrongArity("HKEYS".into()));
1444    }
1445    let key = extract_string(&args[0])?;
1446    Ok(Command::HKeys { key })
1447}
1448
1449fn parse_hvals(args: &[Frame]) -> Result<Command, ProtocolError> {
1450    if args.len() != 1 {
1451        return Err(ProtocolError::WrongArity("HVALS".into()));
1452    }
1453    let key = extract_string(&args[0])?;
1454    Ok(Command::HVals { key })
1455}
1456
1457fn parse_hmget(args: &[Frame]) -> Result<Command, ProtocolError> {
1458    if args.len() < 2 {
1459        return Err(ProtocolError::WrongArity("HMGET".into()));
1460    }
1461    let key = extract_string(&args[0])?;
1462    let fields = extract_strings(&args[1..])?;
1463    Ok(Command::HMGet { key, fields })
1464}
1465
1466// --- set commands ---
1467
1468fn parse_sadd(args: &[Frame]) -> Result<Command, ProtocolError> {
1469    if args.len() < 2 {
1470        return Err(ProtocolError::WrongArity("SADD".into()));
1471    }
1472    let key = extract_string(&args[0])?;
1473    let members = extract_strings(&args[1..])?;
1474    Ok(Command::SAdd { key, members })
1475}
1476
1477fn parse_srem(args: &[Frame]) -> Result<Command, ProtocolError> {
1478    if args.len() < 2 {
1479        return Err(ProtocolError::WrongArity("SREM".into()));
1480    }
1481    let key = extract_string(&args[0])?;
1482    let members = extract_strings(&args[1..])?;
1483    Ok(Command::SRem { key, members })
1484}
1485
1486fn parse_smembers(args: &[Frame]) -> Result<Command, ProtocolError> {
1487    if args.len() != 1 {
1488        return Err(ProtocolError::WrongArity("SMEMBERS".into()));
1489    }
1490    let key = extract_string(&args[0])?;
1491    Ok(Command::SMembers { key })
1492}
1493
1494fn parse_sismember(args: &[Frame]) -> Result<Command, ProtocolError> {
1495    if args.len() != 2 {
1496        return Err(ProtocolError::WrongArity("SISMEMBER".into()));
1497    }
1498    let key = extract_string(&args[0])?;
1499    let member = extract_string(&args[1])?;
1500    Ok(Command::SIsMember { key, member })
1501}
1502
1503fn parse_scard(args: &[Frame]) -> Result<Command, ProtocolError> {
1504    if args.len() != 1 {
1505        return Err(ProtocolError::WrongArity("SCARD".into()));
1506    }
1507    let key = extract_string(&args[0])?;
1508    Ok(Command::SCard { key })
1509}
1510
1511// --- cluster commands ---
1512
1513fn parse_cluster(args: &[Frame]) -> Result<Command, ProtocolError> {
1514    if args.is_empty() {
1515        return Err(ProtocolError::WrongArity("CLUSTER".into()));
1516    }
1517
1518    let subcommand = extract_string(&args[0])?.to_ascii_uppercase();
1519    match subcommand.as_str() {
1520        "INFO" => {
1521            if args.len() != 1 {
1522                return Err(ProtocolError::WrongArity("CLUSTER INFO".into()));
1523            }
1524            Ok(Command::ClusterInfo)
1525        }
1526        "NODES" => {
1527            if args.len() != 1 {
1528                return Err(ProtocolError::WrongArity("CLUSTER NODES".into()));
1529            }
1530            Ok(Command::ClusterNodes)
1531        }
1532        "SLOTS" => {
1533            if args.len() != 1 {
1534                return Err(ProtocolError::WrongArity("CLUSTER SLOTS".into()));
1535            }
1536            Ok(Command::ClusterSlots)
1537        }
1538        "KEYSLOT" => {
1539            if args.len() != 2 {
1540                return Err(ProtocolError::WrongArity("CLUSTER KEYSLOT".into()));
1541            }
1542            let key = extract_string(&args[1])?;
1543            Ok(Command::ClusterKeySlot { key })
1544        }
1545        "MYID" => {
1546            if args.len() != 1 {
1547                return Err(ProtocolError::WrongArity("CLUSTER MYID".into()));
1548            }
1549            Ok(Command::ClusterMyId)
1550        }
1551        "SETSLOT" => parse_cluster_setslot(&args[1..]),
1552        "MEET" => {
1553            if args.len() != 3 {
1554                return Err(ProtocolError::WrongArity("CLUSTER MEET".into()));
1555            }
1556            let ip = extract_string(&args[1])?;
1557            let port_str = extract_string(&args[2])?;
1558            let port: u16 = port_str
1559                .parse()
1560                .map_err(|_| ProtocolError::InvalidCommandFrame("invalid port number".into()))?;
1561            Ok(Command::ClusterMeet { ip, port })
1562        }
1563        "ADDSLOTS" => {
1564            if args.len() < 2 {
1565                return Err(ProtocolError::WrongArity("CLUSTER ADDSLOTS".into()));
1566            }
1567            let slots = parse_slot_list(&args[1..])?;
1568            Ok(Command::ClusterAddSlots { slots })
1569        }
1570        "DELSLOTS" => {
1571            if args.len() < 2 {
1572                return Err(ProtocolError::WrongArity("CLUSTER DELSLOTS".into()));
1573            }
1574            let slots = parse_slot_list(&args[1..])?;
1575            Ok(Command::ClusterDelSlots { slots })
1576        }
1577        "FORGET" => {
1578            if args.len() != 2 {
1579                return Err(ProtocolError::WrongArity("CLUSTER FORGET".into()));
1580            }
1581            let node_id = extract_string(&args[1])?;
1582            Ok(Command::ClusterForget { node_id })
1583        }
1584        "REPLICATE" => {
1585            if args.len() != 2 {
1586                return Err(ProtocolError::WrongArity("CLUSTER REPLICATE".into()));
1587            }
1588            let node_id = extract_string(&args[1])?;
1589            Ok(Command::ClusterReplicate { node_id })
1590        }
1591        "FAILOVER" => {
1592            let mut force = false;
1593            let mut takeover = false;
1594            for arg in &args[1..] {
1595                let opt = extract_string(arg)?.to_ascii_uppercase();
1596                match opt.as_str() {
1597                    "FORCE" => force = true,
1598                    "TAKEOVER" => takeover = true,
1599                    _ => {
1600                        return Err(ProtocolError::InvalidCommandFrame(format!(
1601                            "unknown CLUSTER FAILOVER option '{opt}'"
1602                        )))
1603                    }
1604                }
1605            }
1606            Ok(Command::ClusterFailover { force, takeover })
1607        }
1608        "COUNTKEYSINSLOT" => {
1609            if args.len() != 2 {
1610                return Err(ProtocolError::WrongArity("CLUSTER COUNTKEYSINSLOT".into()));
1611            }
1612            let slot_str = extract_string(&args[1])?;
1613            let slot: u16 = slot_str
1614                .parse()
1615                .map_err(|_| ProtocolError::InvalidCommandFrame("invalid slot number".into()))?;
1616            Ok(Command::ClusterCountKeysInSlot { slot })
1617        }
1618        "GETKEYSINSLOT" => {
1619            if args.len() != 3 {
1620                return Err(ProtocolError::WrongArity("CLUSTER GETKEYSINSLOT".into()));
1621            }
1622            let slot_str = extract_string(&args[1])?;
1623            let slot: u16 = slot_str
1624                .parse()
1625                .map_err(|_| ProtocolError::InvalidCommandFrame("invalid slot number".into()))?;
1626            let count_str = extract_string(&args[2])?;
1627            let count: u32 = count_str
1628                .parse()
1629                .map_err(|_| ProtocolError::InvalidCommandFrame("invalid count".into()))?;
1630            Ok(Command::ClusterGetKeysInSlot { slot, count })
1631        }
1632        _ => Err(ProtocolError::InvalidCommandFrame(format!(
1633            "unknown CLUSTER subcommand '{subcommand}'"
1634        ))),
1635    }
1636}
1637
1638fn parse_asking(args: &[Frame]) -> Result<Command, ProtocolError> {
1639    if !args.is_empty() {
1640        return Err(ProtocolError::WrongArity("ASKING".into()));
1641    }
1642    Ok(Command::Asking)
1643}
1644
1645fn parse_slowlog(args: &[Frame]) -> Result<Command, ProtocolError> {
1646    if args.is_empty() {
1647        return Err(ProtocolError::WrongArity("SLOWLOG".into()));
1648    }
1649
1650    let subcmd = extract_string(&args[0])?.to_ascii_uppercase();
1651    match subcmd.as_str() {
1652        "GET" => {
1653            let count = if args.len() > 1 {
1654                let n: usize = extract_string(&args[1])?.parse().map_err(|_| {
1655                    ProtocolError::InvalidCommandFrame("invalid count for SLOWLOG GET".into())
1656                })?;
1657                Some(n)
1658            } else {
1659                None
1660            };
1661            Ok(Command::SlowLogGet { count })
1662        }
1663        "LEN" => Ok(Command::SlowLogLen),
1664        "RESET" => Ok(Command::SlowLogReset),
1665        other => Err(ProtocolError::InvalidCommandFrame(format!(
1666            "unknown SLOWLOG subcommand '{other}'"
1667        ))),
1668    }
1669}
1670
1671fn parse_slot_list(args: &[Frame]) -> Result<Vec<u16>, ProtocolError> {
1672    let mut slots = Vec::with_capacity(args.len());
1673    for arg in args {
1674        let slot_str = extract_string(arg)?;
1675        let slot: u16 = slot_str
1676            .parse()
1677            .map_err(|_| ProtocolError::InvalidCommandFrame("invalid slot number".into()))?;
1678        if slot >= 16384 {
1679            return Err(ProtocolError::InvalidCommandFrame(format!(
1680                "invalid slot {slot}: must be 0-16383"
1681            )));
1682        }
1683        slots.push(slot);
1684    }
1685    Ok(slots)
1686}
1687
1688fn parse_cluster_setslot(args: &[Frame]) -> Result<Command, ProtocolError> {
1689    if args.is_empty() {
1690        return Err(ProtocolError::WrongArity("CLUSTER SETSLOT".into()));
1691    }
1692
1693    let slot_str = extract_string(&args[0])?;
1694    let slot: u16 = slot_str
1695        .parse()
1696        .map_err(|_| ProtocolError::InvalidCommandFrame("invalid slot number".into()))?;
1697    if slot >= 16384 {
1698        return Err(ProtocolError::InvalidCommandFrame(format!(
1699            "invalid slot {slot}: must be 0-16383"
1700        )));
1701    }
1702
1703    if args.len() < 2 {
1704        return Err(ProtocolError::WrongArity("CLUSTER SETSLOT".into()));
1705    }
1706
1707    let action = extract_string(&args[1])?.to_ascii_uppercase();
1708    match action.as_str() {
1709        "IMPORTING" => {
1710            if args.len() != 3 {
1711                return Err(ProtocolError::WrongArity(
1712                    "CLUSTER SETSLOT IMPORTING".into(),
1713                ));
1714            }
1715            let node_id = extract_string(&args[2])?;
1716            Ok(Command::ClusterSetSlotImporting { slot, node_id })
1717        }
1718        "MIGRATING" => {
1719            if args.len() != 3 {
1720                return Err(ProtocolError::WrongArity(
1721                    "CLUSTER SETSLOT MIGRATING".into(),
1722                ));
1723            }
1724            let node_id = extract_string(&args[2])?;
1725            Ok(Command::ClusterSetSlotMigrating { slot, node_id })
1726        }
1727        "NODE" => {
1728            if args.len() != 3 {
1729                return Err(ProtocolError::WrongArity("CLUSTER SETSLOT NODE".into()));
1730            }
1731            let node_id = extract_string(&args[2])?;
1732            Ok(Command::ClusterSetSlotNode { slot, node_id })
1733        }
1734        "STABLE" => {
1735            if args.len() != 2 {
1736                return Err(ProtocolError::WrongArity("CLUSTER SETSLOT STABLE".into()));
1737            }
1738            Ok(Command::ClusterSetSlotStable { slot })
1739        }
1740        _ => Err(ProtocolError::InvalidCommandFrame(format!(
1741            "unknown CLUSTER SETSLOT action '{action}'"
1742        ))),
1743    }
1744}
1745
1746fn parse_migrate(args: &[Frame]) -> Result<Command, ProtocolError> {
1747    // MIGRATE host port key db timeout [COPY] [REPLACE]
1748    if args.len() < 5 {
1749        return Err(ProtocolError::WrongArity("MIGRATE".into()));
1750    }
1751
1752    let host = extract_string(&args[0])?;
1753    let port_str = extract_string(&args[1])?;
1754    let port: u16 = port_str
1755        .parse()
1756        .map_err(|_| ProtocolError::InvalidCommandFrame("invalid port number".into()))?;
1757    let key = extract_string(&args[2])?;
1758    let db_str = extract_string(&args[3])?;
1759    let db: u32 = db_str
1760        .parse()
1761        .map_err(|_| ProtocolError::InvalidCommandFrame("invalid db number".into()))?;
1762    let timeout_str = extract_string(&args[4])?;
1763    let timeout_ms: u64 = timeout_str
1764        .parse()
1765        .map_err(|_| ProtocolError::InvalidCommandFrame("invalid timeout".into()))?;
1766
1767    let mut copy = false;
1768    let mut replace = false;
1769
1770    for arg in &args[5..] {
1771        let opt = extract_string(arg)?.to_ascii_uppercase();
1772        match opt.as_str() {
1773            "COPY" => copy = true,
1774            "REPLACE" => replace = true,
1775            _ => {
1776                return Err(ProtocolError::InvalidCommandFrame(format!(
1777                    "unknown MIGRATE option '{opt}'"
1778                )))
1779            }
1780        }
1781    }
1782
1783    Ok(Command::Migrate {
1784        host,
1785        port,
1786        key,
1787        db,
1788        timeout_ms,
1789        copy,
1790        replace,
1791    })
1792}
1793
1794fn parse_subscribe(args: &[Frame]) -> Result<Command, ProtocolError> {
1795    if args.is_empty() {
1796        return Err(ProtocolError::WrongArity("SUBSCRIBE".into()));
1797    }
1798    let channels: Vec<String> = args.iter().map(extract_string).collect::<Result<_, _>>()?;
1799    Ok(Command::Subscribe { channels })
1800}
1801
1802fn parse_unsubscribe(args: &[Frame]) -> Result<Command, ProtocolError> {
1803    let channels: Vec<String> = args.iter().map(extract_string).collect::<Result<_, _>>()?;
1804    Ok(Command::Unsubscribe { channels })
1805}
1806
1807fn parse_psubscribe(args: &[Frame]) -> Result<Command, ProtocolError> {
1808    if args.is_empty() {
1809        return Err(ProtocolError::WrongArity("PSUBSCRIBE".into()));
1810    }
1811    let patterns: Vec<String> = args.iter().map(extract_string).collect::<Result<_, _>>()?;
1812    Ok(Command::PSubscribe { patterns })
1813}
1814
1815fn parse_punsubscribe(args: &[Frame]) -> Result<Command, ProtocolError> {
1816    let patterns: Vec<String> = args.iter().map(extract_string).collect::<Result<_, _>>()?;
1817    Ok(Command::PUnsubscribe { patterns })
1818}
1819
1820fn parse_publish(args: &[Frame]) -> Result<Command, ProtocolError> {
1821    if args.len() != 2 {
1822        return Err(ProtocolError::WrongArity("PUBLISH".into()));
1823    }
1824    let channel = extract_string(&args[0])?;
1825    let message = extract_bytes(&args[1])?;
1826    Ok(Command::Publish { channel, message })
1827}
1828
1829fn parse_pubsub(args: &[Frame]) -> Result<Command, ProtocolError> {
1830    if args.is_empty() {
1831        return Err(ProtocolError::WrongArity("PUBSUB".into()));
1832    }
1833
1834    let subcmd = extract_string(&args[0])?.to_ascii_uppercase();
1835    match subcmd.as_str() {
1836        "CHANNELS" => {
1837            let pattern = if args.len() > 1 {
1838                Some(extract_string(&args[1])?)
1839            } else {
1840                None
1841            };
1842            Ok(Command::PubSubChannels { pattern })
1843        }
1844        "NUMSUB" => {
1845            let channels: Vec<String> = args[1..]
1846                .iter()
1847                .map(extract_string)
1848                .collect::<Result<_, _>>()?;
1849            Ok(Command::PubSubNumSub { channels })
1850        }
1851        "NUMPAT" => Ok(Command::PubSubNumPat),
1852        other => Err(ProtocolError::InvalidCommandFrame(format!(
1853            "unknown PUBSUB subcommand '{other}'"
1854        ))),
1855    }
1856}
1857
1858// --- vector command parsers ---
1859
1860/// VADD key element f32 [f32 ...] [METRIC COSINE|L2|IP] [QUANT F32|F16|I8] [M n] [EF n]
1861fn parse_vadd(args: &[Frame]) -> Result<Command, ProtocolError> {
1862    // minimum: key + element + at least one float
1863    if args.len() < 3 {
1864        return Err(ProtocolError::WrongArity("VADD".into()));
1865    }
1866
1867    let key = extract_string(&args[0])?;
1868    let element = extract_string(&args[1])?;
1869
1870    // parse vector values until we hit a non-numeric argument, end, or dim limit
1871    let mut idx = 2;
1872    let mut vector = Vec::new();
1873    while idx < args.len() {
1874        if vector.len() >= MAX_VECTOR_DIMS {
1875            return Err(ProtocolError::InvalidCommandFrame(format!(
1876                "VADD: vector exceeds {MAX_VECTOR_DIMS} dimensions"
1877            )));
1878        }
1879        let s = extract_string(&args[idx])?;
1880        if let Ok(v) = s.parse::<f32>() {
1881            if v.is_nan() || v.is_infinite() {
1882                return Err(ProtocolError::InvalidCommandFrame(
1883                    "VADD: vector components must be finite (no NaN/infinity)".into(),
1884                ));
1885            }
1886            vector.push(v);
1887            idx += 1;
1888        } else {
1889            break;
1890        }
1891    }
1892
1893    if vector.is_empty() {
1894        return Err(ProtocolError::InvalidCommandFrame(
1895            "VADD: at least one vector dimension required".into(),
1896        ));
1897    }
1898
1899    // parse optional flags
1900    let mut metric: u8 = 0; // cosine default
1901    let mut quantization: u8 = 0; // f32 default
1902    let mut connectivity: u32 = 16;
1903    let mut expansion_add: u32 = 64;
1904
1905    while idx < args.len() {
1906        let flag = extract_string(&args[idx])?.to_ascii_uppercase();
1907        match flag.as_str() {
1908            "METRIC" => {
1909                idx += 1;
1910                if idx >= args.len() {
1911                    return Err(ProtocolError::InvalidCommandFrame(
1912                        "VADD: METRIC requires a value".into(),
1913                    ));
1914                }
1915                let val = extract_string(&args[idx])?.to_ascii_uppercase();
1916                metric = match val.as_str() {
1917                    "COSINE" => 0,
1918                    "L2" => 1,
1919                    "IP" => 2,
1920                    _ => {
1921                        return Err(ProtocolError::InvalidCommandFrame(format!(
1922                            "VADD: unknown metric '{val}'"
1923                        )))
1924                    }
1925                };
1926                idx += 1;
1927            }
1928            "QUANT" => {
1929                idx += 1;
1930                if idx >= args.len() {
1931                    return Err(ProtocolError::InvalidCommandFrame(
1932                        "VADD: QUANT requires a value".into(),
1933                    ));
1934                }
1935                let val = extract_string(&args[idx])?.to_ascii_uppercase();
1936                quantization = match val.as_str() {
1937                    "F32" => 0,
1938                    "F16" => 1,
1939                    "I8" | "Q8" => 2,
1940                    _ => {
1941                        return Err(ProtocolError::InvalidCommandFrame(format!(
1942                            "VADD: unknown quantization '{val}'"
1943                        )))
1944                    }
1945                };
1946                idx += 1;
1947            }
1948            "M" => {
1949                idx += 1;
1950                if idx >= args.len() {
1951                    return Err(ProtocolError::InvalidCommandFrame(
1952                        "VADD: M requires a value".into(),
1953                    ));
1954                }
1955                let m = parse_u64(&args[idx], "VADD")?;
1956                if m > MAX_HNSW_PARAM {
1957                    return Err(ProtocolError::InvalidCommandFrame(format!(
1958                        "VADD: M value {m} exceeds max {MAX_HNSW_PARAM}"
1959                    )));
1960                }
1961                connectivity = m as u32;
1962                idx += 1;
1963            }
1964            "EF" => {
1965                idx += 1;
1966                if idx >= args.len() {
1967                    return Err(ProtocolError::InvalidCommandFrame(
1968                        "VADD: EF requires a value".into(),
1969                    ));
1970                }
1971                let ef = parse_u64(&args[idx], "VADD")?;
1972                if ef > MAX_HNSW_PARAM {
1973                    return Err(ProtocolError::InvalidCommandFrame(format!(
1974                        "VADD: EF value {ef} exceeds max {MAX_HNSW_PARAM}"
1975                    )));
1976                }
1977                expansion_add = ef as u32;
1978                idx += 1;
1979            }
1980            _ => {
1981                return Err(ProtocolError::InvalidCommandFrame(format!(
1982                    "VADD: unexpected argument '{flag}'"
1983                )));
1984            }
1985        }
1986    }
1987
1988    Ok(Command::VAdd {
1989        key,
1990        element,
1991        vector,
1992        metric,
1993        quantization,
1994        connectivity,
1995        expansion_add,
1996    })
1997}
1998
1999/// VADD_BATCH key DIM n element1 f32... element2 f32... [METRIC COSINE|L2|IP]
2000/// [QUANT F32|F16|I8] [M n] [EF n]
2001fn parse_vadd_batch(args: &[Frame]) -> Result<Command, ProtocolError> {
2002    // minimum: key + DIM + n (even an empty batch needs the DIM declaration)
2003    if args.len() < 3 {
2004        return Err(ProtocolError::WrongArity("VADD_BATCH".into()));
2005    }
2006
2007    let key = extract_string(&args[0])?;
2008
2009    // require DIM keyword
2010    let dim_kw = extract_string(&args[1])?.to_ascii_uppercase();
2011    if dim_kw != "DIM" {
2012        return Err(ProtocolError::InvalidCommandFrame(
2013            "VADD_BATCH: expected DIM keyword".into(),
2014        ));
2015    }
2016
2017    let dim = parse_u64(&args[2], "VADD_BATCH")? as usize;
2018    if dim == 0 {
2019        return Err(ProtocolError::InvalidCommandFrame(
2020            "VADD_BATCH: DIM must be at least 1".into(),
2021        ));
2022    }
2023    if dim > MAX_VECTOR_DIMS {
2024        return Err(ProtocolError::InvalidCommandFrame(format!(
2025            "VADD_BATCH: DIM {dim} exceeds max {MAX_VECTOR_DIMS}"
2026        )));
2027    }
2028
2029    // parse entries: each is element_name followed by exactly `dim` floats.
2030    // we detect the end of entries by checking whether enough args remain
2031    // for a full entry (1 name + dim floats). this avoids misinterpreting
2032    // element names like "metric" as flags.
2033    let mut idx = 3;
2034    let mut entries: Vec<(String, Vec<f32>)> = Vec::new();
2035    let entry_len = 1 + dim; // element name + dim floats
2036
2037    while idx < args.len() {
2038        // not enough remaining args for a full entry — must be flags
2039        if idx + entry_len > args.len() {
2040            break;
2041        }
2042
2043        // peek: if the token after the element name isn't a valid float,
2044        // we've reached the flags section
2045        if dim > 0 {
2046            let peek = extract_string(&args[idx + 1])?;
2047            if peek.parse::<f32>().is_err() {
2048                break;
2049            }
2050        }
2051
2052        let element = extract_string(&args[idx])?;
2053        idx += 1;
2054
2055        let mut vector = Vec::with_capacity(dim);
2056        for _ in 0..dim {
2057            let s = extract_string(&args[idx])?;
2058            let v = s.parse::<f32>().map_err(|_| {
2059                ProtocolError::InvalidCommandFrame(format!("VADD_BATCH: expected float, got '{s}'"))
2060            })?;
2061            if v.is_nan() || v.is_infinite() {
2062                return Err(ProtocolError::InvalidCommandFrame(
2063                    "VADD_BATCH: vector components must be finite (no NaN/infinity)".into(),
2064                ));
2065            }
2066            vector.push(v);
2067            idx += 1;
2068        }
2069
2070        entries.push((element, vector));
2071
2072        if entries.len() >= MAX_VADD_BATCH_SIZE {
2073            return Err(ProtocolError::InvalidCommandFrame(format!(
2074                "VADD_BATCH: batch size exceeds max {MAX_VADD_BATCH_SIZE}"
2075            )));
2076        }
2077    }
2078
2079    // parse optional flags (same logic as parse_vadd)
2080    let mut metric: u8 = 0;
2081    let mut quantization: u8 = 0;
2082    let mut connectivity: u32 = 16;
2083    let mut expansion_add: u32 = 64;
2084
2085    while idx < args.len() {
2086        let flag = extract_string(&args[idx])?.to_ascii_uppercase();
2087        match flag.as_str() {
2088            "METRIC" => {
2089                idx += 1;
2090                if idx >= args.len() {
2091                    return Err(ProtocolError::InvalidCommandFrame(
2092                        "VADD_BATCH: METRIC requires a value".into(),
2093                    ));
2094                }
2095                let val = extract_string(&args[idx])?.to_ascii_uppercase();
2096                metric = match val.as_str() {
2097                    "COSINE" => 0,
2098                    "L2" => 1,
2099                    "IP" => 2,
2100                    _ => {
2101                        return Err(ProtocolError::InvalidCommandFrame(format!(
2102                            "VADD_BATCH: unknown metric '{val}'"
2103                        )))
2104                    }
2105                };
2106                idx += 1;
2107            }
2108            "QUANT" => {
2109                idx += 1;
2110                if idx >= args.len() {
2111                    return Err(ProtocolError::InvalidCommandFrame(
2112                        "VADD_BATCH: QUANT requires a value".into(),
2113                    ));
2114                }
2115                let val = extract_string(&args[idx])?.to_ascii_uppercase();
2116                quantization = match val.as_str() {
2117                    "F32" => 0,
2118                    "F16" => 1,
2119                    "I8" | "Q8" => 2,
2120                    _ => {
2121                        return Err(ProtocolError::InvalidCommandFrame(format!(
2122                            "VADD_BATCH: unknown quantization '{val}'"
2123                        )))
2124                    }
2125                };
2126                idx += 1;
2127            }
2128            "M" => {
2129                idx += 1;
2130                if idx >= args.len() {
2131                    return Err(ProtocolError::InvalidCommandFrame(
2132                        "VADD_BATCH: M requires a value".into(),
2133                    ));
2134                }
2135                let m = parse_u64(&args[idx], "VADD_BATCH")?;
2136                if m > MAX_HNSW_PARAM {
2137                    return Err(ProtocolError::InvalidCommandFrame(format!(
2138                        "VADD_BATCH: M value {m} exceeds max {MAX_HNSW_PARAM}"
2139                    )));
2140                }
2141                connectivity = m as u32;
2142                idx += 1;
2143            }
2144            "EF" => {
2145                idx += 1;
2146                if idx >= args.len() {
2147                    return Err(ProtocolError::InvalidCommandFrame(
2148                        "VADD_BATCH: EF requires a value".into(),
2149                    ));
2150                }
2151                let ef = parse_u64(&args[idx], "VADD_BATCH")?;
2152                if ef > MAX_HNSW_PARAM {
2153                    return Err(ProtocolError::InvalidCommandFrame(format!(
2154                        "VADD_BATCH: EF value {ef} exceeds max {MAX_HNSW_PARAM}"
2155                    )));
2156                }
2157                expansion_add = ef as u32;
2158                idx += 1;
2159            }
2160            _ => {
2161                return Err(ProtocolError::InvalidCommandFrame(format!(
2162                    "VADD_BATCH: unexpected argument '{flag}'"
2163                )));
2164            }
2165        }
2166    }
2167
2168    Ok(Command::VAddBatch {
2169        key,
2170        entries,
2171        dim,
2172        metric,
2173        quantization,
2174        connectivity,
2175        expansion_add,
2176    })
2177}
2178
2179/// VSIM key f32 [f32 ...] COUNT k [EF n] [WITHSCORES]
2180fn parse_vsim(args: &[Frame]) -> Result<Command, ProtocolError> {
2181    // minimum: key + at least one float + COUNT + k
2182    if args.len() < 4 {
2183        return Err(ProtocolError::WrongArity("VSIM".into()));
2184    }
2185
2186    let key = extract_string(&args[0])?;
2187
2188    // parse query vector until we hit a non-numeric argument, end, or dim limit
2189    let mut idx = 1;
2190    let mut query = Vec::new();
2191    while idx < args.len() {
2192        if query.len() >= MAX_VECTOR_DIMS {
2193            return Err(ProtocolError::InvalidCommandFrame(format!(
2194                "VSIM: query exceeds {MAX_VECTOR_DIMS} dimensions"
2195            )));
2196        }
2197        let s = extract_string(&args[idx])?;
2198        if let Ok(v) = s.parse::<f32>() {
2199            if v.is_nan() || v.is_infinite() {
2200                return Err(ProtocolError::InvalidCommandFrame(
2201                    "VSIM: query components must be finite (no NaN/infinity)".into(),
2202                ));
2203            }
2204            query.push(v);
2205            idx += 1;
2206        } else {
2207            break;
2208        }
2209    }
2210
2211    if query.is_empty() {
2212        return Err(ProtocolError::InvalidCommandFrame(
2213            "VSIM: at least one query dimension required".into(),
2214        ));
2215    }
2216
2217    // COUNT k is required
2218    let mut count: Option<usize> = None;
2219    let mut ef_search: usize = 0;
2220    let mut with_scores = false;
2221
2222    while idx < args.len() {
2223        let flag = extract_string(&args[idx])?.to_ascii_uppercase();
2224        match flag.as_str() {
2225            "COUNT" => {
2226                idx += 1;
2227                if idx >= args.len() {
2228                    return Err(ProtocolError::InvalidCommandFrame(
2229                        "VSIM: COUNT requires a value".into(),
2230                    ));
2231                }
2232                let c = parse_u64(&args[idx], "VSIM")?;
2233                if c > MAX_VSIM_COUNT {
2234                    return Err(ProtocolError::InvalidCommandFrame(format!(
2235                        "VSIM: COUNT {c} exceeds max {MAX_VSIM_COUNT}"
2236                    )));
2237                }
2238                count = Some(c as usize);
2239                idx += 1;
2240            }
2241            "EF" => {
2242                idx += 1;
2243                if idx >= args.len() {
2244                    return Err(ProtocolError::InvalidCommandFrame(
2245                        "VSIM: EF requires a value".into(),
2246                    ));
2247                }
2248                let ef = parse_u64(&args[idx], "VSIM")?;
2249                if ef > MAX_VSIM_EF {
2250                    return Err(ProtocolError::InvalidCommandFrame(format!(
2251                        "VSIM: EF {ef} exceeds max {MAX_VSIM_EF}"
2252                    )));
2253                }
2254                ef_search = ef as usize;
2255                idx += 1;
2256            }
2257            "WITHSCORES" => {
2258                with_scores = true;
2259                idx += 1;
2260            }
2261            _ => {
2262                return Err(ProtocolError::InvalidCommandFrame(format!(
2263                    "VSIM: unexpected argument '{flag}'"
2264                )));
2265            }
2266        }
2267    }
2268
2269    let count = count
2270        .ok_or_else(|| ProtocolError::InvalidCommandFrame("VSIM: COUNT is required".into()))?;
2271
2272    Ok(Command::VSim {
2273        key,
2274        query,
2275        count,
2276        ef_search,
2277        with_scores,
2278    })
2279}
2280
2281/// VREM key element
2282fn parse_vrem(args: &[Frame]) -> Result<Command, ProtocolError> {
2283    if args.len() != 2 {
2284        return Err(ProtocolError::WrongArity("VREM".into()));
2285    }
2286    let key = extract_string(&args[0])?;
2287    let element = extract_string(&args[1])?;
2288    Ok(Command::VRem { key, element })
2289}
2290
2291/// VGET key element
2292fn parse_vget(args: &[Frame]) -> Result<Command, ProtocolError> {
2293    if args.len() != 2 {
2294        return Err(ProtocolError::WrongArity("VGET".into()));
2295    }
2296    let key = extract_string(&args[0])?;
2297    let element = extract_string(&args[1])?;
2298    Ok(Command::VGet { key, element })
2299}
2300
2301/// VCARD key
2302fn parse_vcard(args: &[Frame]) -> Result<Command, ProtocolError> {
2303    if args.len() != 1 {
2304        return Err(ProtocolError::WrongArity("VCARD".into()));
2305    }
2306    let key = extract_string(&args[0])?;
2307    Ok(Command::VCard { key })
2308}
2309
2310/// VDIM key
2311fn parse_vdim(args: &[Frame]) -> Result<Command, ProtocolError> {
2312    if args.len() != 1 {
2313        return Err(ProtocolError::WrongArity("VDIM".into()));
2314    }
2315    let key = extract_string(&args[0])?;
2316    Ok(Command::VDim { key })
2317}
2318
2319/// VINFO key
2320fn parse_vinfo(args: &[Frame]) -> Result<Command, ProtocolError> {
2321    if args.len() != 1 {
2322        return Err(ProtocolError::WrongArity("VINFO".into()));
2323    }
2324    let key = extract_string(&args[0])?;
2325    Ok(Command::VInfo { key })
2326}
2327
2328// --- proto command parsers ---
2329
2330fn parse_proto_register(args: &[Frame]) -> Result<Command, ProtocolError> {
2331    if args.len() != 2 {
2332        return Err(ProtocolError::WrongArity("PROTO.REGISTER".into()));
2333    }
2334    let name = extract_string(&args[0])?;
2335    let descriptor = extract_bytes(&args[1])?;
2336    Ok(Command::ProtoRegister { name, descriptor })
2337}
2338
2339fn parse_proto_set(args: &[Frame]) -> Result<Command, ProtocolError> {
2340    if args.len() < 3 {
2341        return Err(ProtocolError::WrongArity("PROTO.SET".into()));
2342    }
2343
2344    let key = extract_string(&args[0])?;
2345    let type_name = extract_string(&args[1])?;
2346    let data = extract_bytes(&args[2])?;
2347
2348    let mut expire = None;
2349    let mut nx = false;
2350    let mut xx = false;
2351    let mut idx = 3;
2352
2353    while idx < args.len() {
2354        let flag = extract_string(&args[idx])?.to_ascii_uppercase();
2355        match flag.as_str() {
2356            "NX" => {
2357                nx = true;
2358                idx += 1;
2359            }
2360            "XX" => {
2361                xx = true;
2362                idx += 1;
2363            }
2364            "EX" => {
2365                idx += 1;
2366                if idx >= args.len() {
2367                    return Err(ProtocolError::WrongArity("PROTO.SET".into()));
2368                }
2369                let amount = parse_u64(&args[idx], "PROTO.SET")?;
2370                if amount == 0 {
2371                    return Err(ProtocolError::InvalidCommandFrame(
2372                        "invalid expire time in 'PROTO.SET' command".into(),
2373                    ));
2374                }
2375                expire = Some(SetExpire::Ex(amount));
2376                idx += 1;
2377            }
2378            "PX" => {
2379                idx += 1;
2380                if idx >= args.len() {
2381                    return Err(ProtocolError::WrongArity("PROTO.SET".into()));
2382                }
2383                let amount = parse_u64(&args[idx], "PROTO.SET")?;
2384                if amount == 0 {
2385                    return Err(ProtocolError::InvalidCommandFrame(
2386                        "invalid expire time in 'PROTO.SET' command".into(),
2387                    ));
2388                }
2389                expire = Some(SetExpire::Px(amount));
2390                idx += 1;
2391            }
2392            _ => {
2393                return Err(ProtocolError::InvalidCommandFrame(format!(
2394                    "unsupported PROTO.SET option '{flag}'"
2395                )));
2396            }
2397        }
2398    }
2399
2400    if nx && xx {
2401        return Err(ProtocolError::InvalidCommandFrame(
2402            "XX and NX options at the same time are not compatible".into(),
2403        ));
2404    }
2405
2406    Ok(Command::ProtoSet {
2407        key,
2408        type_name,
2409        data,
2410        expire,
2411        nx,
2412        xx,
2413    })
2414}
2415
2416fn parse_proto_get(args: &[Frame]) -> Result<Command, ProtocolError> {
2417    if args.len() != 1 {
2418        return Err(ProtocolError::WrongArity("PROTO.GET".into()));
2419    }
2420    let key = extract_string(&args[0])?;
2421    Ok(Command::ProtoGet { key })
2422}
2423
2424fn parse_proto_type(args: &[Frame]) -> Result<Command, ProtocolError> {
2425    if args.len() != 1 {
2426        return Err(ProtocolError::WrongArity("PROTO.TYPE".into()));
2427    }
2428    let key = extract_string(&args[0])?;
2429    Ok(Command::ProtoType { key })
2430}
2431
2432fn parse_proto_schemas(args: &[Frame]) -> Result<Command, ProtocolError> {
2433    if !args.is_empty() {
2434        return Err(ProtocolError::WrongArity("PROTO.SCHEMAS".into()));
2435    }
2436    Ok(Command::ProtoSchemas)
2437}
2438
2439fn parse_proto_describe(args: &[Frame]) -> Result<Command, ProtocolError> {
2440    if args.len() != 1 {
2441        return Err(ProtocolError::WrongArity("PROTO.DESCRIBE".into()));
2442    }
2443    let name = extract_string(&args[0])?;
2444    Ok(Command::ProtoDescribe { name })
2445}
2446
2447fn parse_proto_getfield(args: &[Frame]) -> Result<Command, ProtocolError> {
2448    if args.len() != 2 {
2449        return Err(ProtocolError::WrongArity("PROTO.GETFIELD".into()));
2450    }
2451    let key = extract_string(&args[0])?;
2452    let field_path = extract_string(&args[1])?;
2453    Ok(Command::ProtoGetField { key, field_path })
2454}
2455
2456fn parse_proto_setfield(args: &[Frame]) -> Result<Command, ProtocolError> {
2457    if args.len() != 3 {
2458        return Err(ProtocolError::WrongArity("PROTO.SETFIELD".into()));
2459    }
2460    let key = extract_string(&args[0])?;
2461    let field_path = extract_string(&args[1])?;
2462    let value = extract_string(&args[2])?;
2463    Ok(Command::ProtoSetField {
2464        key,
2465        field_path,
2466        value,
2467    })
2468}
2469
2470fn parse_proto_delfield(args: &[Frame]) -> Result<Command, ProtocolError> {
2471    if args.len() != 2 {
2472        return Err(ProtocolError::WrongArity("PROTO.DELFIELD".into()));
2473    }
2474    let key = extract_string(&args[0])?;
2475    let field_path = extract_string(&args[1])?;
2476    Ok(Command::ProtoDelField { key, field_path })
2477}
2478
2479fn parse_auth(args: &[Frame]) -> Result<Command, ProtocolError> {
2480    match args.len() {
2481        1 => {
2482            let password = extract_string(&args[0])?;
2483            Ok(Command::Auth {
2484                username: None,
2485                password,
2486            })
2487        }
2488        2 => {
2489            let username = extract_string(&args[0])?;
2490            let password = extract_string(&args[1])?;
2491            Ok(Command::Auth {
2492                username: Some(username),
2493                password,
2494            })
2495        }
2496        _ => Err(ProtocolError::WrongArity("AUTH".into())),
2497    }
2498}
2499
2500fn parse_quit(args: &[Frame]) -> Result<Command, ProtocolError> {
2501    if !args.is_empty() {
2502        return Err(ProtocolError::WrongArity("QUIT".into()));
2503    }
2504    Ok(Command::Quit)
2505}
2506
2507#[cfg(test)]
2508mod tests {
2509    use super::*;
2510
2511    /// Helper: build an array frame from bulk strings.
2512    fn cmd(parts: &[&str]) -> Frame {
2513        Frame::Array(
2514            parts
2515                .iter()
2516                .map(|s| Frame::Bulk(Bytes::from(s.to_string())))
2517                .collect(),
2518        )
2519    }
2520
2521    // --- ping ---
2522
2523    #[test]
2524    fn ping_no_args() {
2525        assert_eq!(
2526            Command::from_frame(cmd(&["PING"])).unwrap(),
2527            Command::Ping(None),
2528        );
2529    }
2530
2531    #[test]
2532    fn ping_with_message() {
2533        assert_eq!(
2534            Command::from_frame(cmd(&["PING", "hello"])).unwrap(),
2535            Command::Ping(Some(Bytes::from("hello"))),
2536        );
2537    }
2538
2539    #[test]
2540    fn ping_case_insensitive() {
2541        assert_eq!(
2542            Command::from_frame(cmd(&["ping"])).unwrap(),
2543            Command::Ping(None),
2544        );
2545        assert_eq!(
2546            Command::from_frame(cmd(&["Ping"])).unwrap(),
2547            Command::Ping(None),
2548        );
2549    }
2550
2551    #[test]
2552    fn ping_too_many_args() {
2553        let err = Command::from_frame(cmd(&["PING", "a", "b"])).unwrap_err();
2554        assert!(matches!(err, ProtocolError::WrongArity(_)));
2555    }
2556
2557    // --- echo ---
2558
2559    #[test]
2560    fn echo() {
2561        assert_eq!(
2562            Command::from_frame(cmd(&["ECHO", "test"])).unwrap(),
2563            Command::Echo(Bytes::from("test")),
2564        );
2565    }
2566
2567    #[test]
2568    fn echo_missing_arg() {
2569        let err = Command::from_frame(cmd(&["ECHO"])).unwrap_err();
2570        assert!(matches!(err, ProtocolError::WrongArity(_)));
2571    }
2572
2573    // --- get ---
2574
2575    #[test]
2576    fn get_basic() {
2577        assert_eq!(
2578            Command::from_frame(cmd(&["GET", "mykey"])).unwrap(),
2579            Command::Get {
2580                key: "mykey".into()
2581            },
2582        );
2583    }
2584
2585    #[test]
2586    fn get_no_args() {
2587        let err = Command::from_frame(cmd(&["GET"])).unwrap_err();
2588        assert!(matches!(err, ProtocolError::WrongArity(_)));
2589    }
2590
2591    #[test]
2592    fn get_too_many_args() {
2593        let err = Command::from_frame(cmd(&["GET", "a", "b"])).unwrap_err();
2594        assert!(matches!(err, ProtocolError::WrongArity(_)));
2595    }
2596
2597    #[test]
2598    fn get_case_insensitive() {
2599        assert_eq!(
2600            Command::from_frame(cmd(&["get", "k"])).unwrap(),
2601            Command::Get { key: "k".into() },
2602        );
2603    }
2604
2605    // --- set ---
2606
2607    #[test]
2608    fn set_basic() {
2609        assert_eq!(
2610            Command::from_frame(cmd(&["SET", "key", "value"])).unwrap(),
2611            Command::Set {
2612                key: "key".into(),
2613                value: Bytes::from("value"),
2614                expire: None,
2615                nx: false,
2616                xx: false,
2617            },
2618        );
2619    }
2620
2621    #[test]
2622    fn set_with_ex() {
2623        assert_eq!(
2624            Command::from_frame(cmd(&["SET", "key", "val", "EX", "10"])).unwrap(),
2625            Command::Set {
2626                key: "key".into(),
2627                value: Bytes::from("val"),
2628                expire: Some(SetExpire::Ex(10)),
2629                nx: false,
2630                xx: false,
2631            },
2632        );
2633    }
2634
2635    #[test]
2636    fn set_with_px() {
2637        assert_eq!(
2638            Command::from_frame(cmd(&["SET", "key", "val", "PX", "5000"])).unwrap(),
2639            Command::Set {
2640                key: "key".into(),
2641                value: Bytes::from("val"),
2642                expire: Some(SetExpire::Px(5000)),
2643                nx: false,
2644                xx: false,
2645            },
2646        );
2647    }
2648
2649    #[test]
2650    fn set_ex_case_insensitive() {
2651        assert_eq!(
2652            Command::from_frame(cmd(&["set", "k", "v", "ex", "5"])).unwrap(),
2653            Command::Set {
2654                key: "k".into(),
2655                value: Bytes::from("v"),
2656                expire: Some(SetExpire::Ex(5)),
2657                nx: false,
2658                xx: false,
2659            },
2660        );
2661    }
2662
2663    #[test]
2664    fn set_nx_flag() {
2665        assert_eq!(
2666            Command::from_frame(cmd(&["SET", "key", "val", "NX"])).unwrap(),
2667            Command::Set {
2668                key: "key".into(),
2669                value: Bytes::from("val"),
2670                expire: None,
2671                nx: true,
2672                xx: false,
2673            },
2674        );
2675    }
2676
2677    #[test]
2678    fn set_xx_flag() {
2679        assert_eq!(
2680            Command::from_frame(cmd(&["SET", "key", "val", "XX"])).unwrap(),
2681            Command::Set {
2682                key: "key".into(),
2683                value: Bytes::from("val"),
2684                expire: None,
2685                nx: false,
2686                xx: true,
2687            },
2688        );
2689    }
2690
2691    #[test]
2692    fn set_nx_with_ex() {
2693        assert_eq!(
2694            Command::from_frame(cmd(&["SET", "key", "val", "EX", "10", "NX"])).unwrap(),
2695            Command::Set {
2696                key: "key".into(),
2697                value: Bytes::from("val"),
2698                expire: Some(SetExpire::Ex(10)),
2699                nx: true,
2700                xx: false,
2701            },
2702        );
2703    }
2704
2705    #[test]
2706    fn set_nx_before_ex() {
2707        assert_eq!(
2708            Command::from_frame(cmd(&["SET", "key", "val", "NX", "PX", "5000"])).unwrap(),
2709            Command::Set {
2710                key: "key".into(),
2711                value: Bytes::from("val"),
2712                expire: Some(SetExpire::Px(5000)),
2713                nx: true,
2714                xx: false,
2715            },
2716        );
2717    }
2718
2719    #[test]
2720    fn set_nx_xx_conflict() {
2721        let err = Command::from_frame(cmd(&["SET", "k", "v", "NX", "XX"])).unwrap_err();
2722        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
2723    }
2724
2725    #[test]
2726    fn set_nx_case_insensitive() {
2727        assert_eq!(
2728            Command::from_frame(cmd(&["set", "k", "v", "nx"])).unwrap(),
2729            Command::Set {
2730                key: "k".into(),
2731                value: Bytes::from("v"),
2732                expire: None,
2733                nx: true,
2734                xx: false,
2735            },
2736        );
2737    }
2738
2739    #[test]
2740    fn set_missing_value() {
2741        let err = Command::from_frame(cmd(&["SET", "key"])).unwrap_err();
2742        assert!(matches!(err, ProtocolError::WrongArity(_)));
2743    }
2744
2745    #[test]
2746    fn set_invalid_expire_value() {
2747        let err = Command::from_frame(cmd(&["SET", "k", "v", "EX", "notanum"])).unwrap_err();
2748        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
2749    }
2750
2751    #[test]
2752    fn set_zero_expire() {
2753        let err = Command::from_frame(cmd(&["SET", "k", "v", "EX", "0"])).unwrap_err();
2754        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
2755    }
2756
2757    #[test]
2758    fn set_unknown_flag() {
2759        let err = Command::from_frame(cmd(&["SET", "k", "v", "ZZ", "10"])).unwrap_err();
2760        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
2761    }
2762
2763    #[test]
2764    fn set_incomplete_expire() {
2765        // EX without a value
2766        let err = Command::from_frame(cmd(&["SET", "k", "v", "EX"])).unwrap_err();
2767        assert!(matches!(err, ProtocolError::WrongArity(_)));
2768    }
2769
2770    // --- del ---
2771
2772    #[test]
2773    fn del_single() {
2774        assert_eq!(
2775            Command::from_frame(cmd(&["DEL", "key"])).unwrap(),
2776            Command::Del {
2777                keys: vec!["key".into()]
2778            },
2779        );
2780    }
2781
2782    #[test]
2783    fn del_multiple() {
2784        assert_eq!(
2785            Command::from_frame(cmd(&["DEL", "a", "b", "c"])).unwrap(),
2786            Command::Del {
2787                keys: vec!["a".into(), "b".into(), "c".into()]
2788            },
2789        );
2790    }
2791
2792    #[test]
2793    fn del_no_args() {
2794        let err = Command::from_frame(cmd(&["DEL"])).unwrap_err();
2795        assert!(matches!(err, ProtocolError::WrongArity(_)));
2796    }
2797
2798    // --- exists ---
2799
2800    #[test]
2801    fn exists_single() {
2802        assert_eq!(
2803            Command::from_frame(cmd(&["EXISTS", "key"])).unwrap(),
2804            Command::Exists {
2805                keys: vec!["key".into()]
2806            },
2807        );
2808    }
2809
2810    #[test]
2811    fn exists_multiple() {
2812        assert_eq!(
2813            Command::from_frame(cmd(&["EXISTS", "a", "b"])).unwrap(),
2814            Command::Exists {
2815                keys: vec!["a".into(), "b".into()]
2816            },
2817        );
2818    }
2819
2820    #[test]
2821    fn exists_no_args() {
2822        let err = Command::from_frame(cmd(&["EXISTS"])).unwrap_err();
2823        assert!(matches!(err, ProtocolError::WrongArity(_)));
2824    }
2825
2826    // --- mget ---
2827
2828    #[test]
2829    fn mget_single() {
2830        assert_eq!(
2831            Command::from_frame(cmd(&["MGET", "key"])).unwrap(),
2832            Command::MGet {
2833                keys: vec!["key".into()]
2834            },
2835        );
2836    }
2837
2838    #[test]
2839    fn mget_multiple() {
2840        assert_eq!(
2841            Command::from_frame(cmd(&["MGET", "a", "b", "c"])).unwrap(),
2842            Command::MGet {
2843                keys: vec!["a".into(), "b".into(), "c".into()]
2844            },
2845        );
2846    }
2847
2848    #[test]
2849    fn mget_no_args() {
2850        let err = Command::from_frame(cmd(&["MGET"])).unwrap_err();
2851        assert!(matches!(err, ProtocolError::WrongArity(_)));
2852    }
2853
2854    // --- mset ---
2855
2856    #[test]
2857    fn mset_single_pair() {
2858        assert_eq!(
2859            Command::from_frame(cmd(&["MSET", "key", "val"])).unwrap(),
2860            Command::MSet {
2861                pairs: vec![("key".into(), Bytes::from("val"))]
2862            },
2863        );
2864    }
2865
2866    #[test]
2867    fn mset_multiple_pairs() {
2868        assert_eq!(
2869            Command::from_frame(cmd(&["MSET", "a", "1", "b", "2"])).unwrap(),
2870            Command::MSet {
2871                pairs: vec![
2872                    ("a".into(), Bytes::from("1")),
2873                    ("b".into(), Bytes::from("2")),
2874                ]
2875            },
2876        );
2877    }
2878
2879    #[test]
2880    fn mset_no_args() {
2881        let err = Command::from_frame(cmd(&["MSET"])).unwrap_err();
2882        assert!(matches!(err, ProtocolError::WrongArity(_)));
2883    }
2884
2885    #[test]
2886    fn mset_odd_args() {
2887        // missing value for second key
2888        let err = Command::from_frame(cmd(&["MSET", "a", "1", "b"])).unwrap_err();
2889        assert!(matches!(err, ProtocolError::WrongArity(_)));
2890    }
2891
2892    // --- expire ---
2893
2894    #[test]
2895    fn expire_basic() {
2896        assert_eq!(
2897            Command::from_frame(cmd(&["EXPIRE", "key", "60"])).unwrap(),
2898            Command::Expire {
2899                key: "key".into(),
2900                seconds: 60,
2901            },
2902        );
2903    }
2904
2905    #[test]
2906    fn expire_wrong_arity() {
2907        let err = Command::from_frame(cmd(&["EXPIRE", "key"])).unwrap_err();
2908        assert!(matches!(err, ProtocolError::WrongArity(_)));
2909    }
2910
2911    #[test]
2912    fn expire_invalid_seconds() {
2913        let err = Command::from_frame(cmd(&["EXPIRE", "key", "abc"])).unwrap_err();
2914        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
2915    }
2916
2917    #[test]
2918    fn expire_zero_seconds() {
2919        let err = Command::from_frame(cmd(&["EXPIRE", "key", "0"])).unwrap_err();
2920        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
2921    }
2922
2923    // --- ttl ---
2924
2925    #[test]
2926    fn ttl_basic() {
2927        assert_eq!(
2928            Command::from_frame(cmd(&["TTL", "key"])).unwrap(),
2929            Command::Ttl { key: "key".into() },
2930        );
2931    }
2932
2933    #[test]
2934    fn ttl_wrong_arity() {
2935        let err = Command::from_frame(cmd(&["TTL"])).unwrap_err();
2936        assert!(matches!(err, ProtocolError::WrongArity(_)));
2937    }
2938
2939    // --- dbsize ---
2940
2941    #[test]
2942    fn dbsize_basic() {
2943        assert_eq!(
2944            Command::from_frame(cmd(&["DBSIZE"])).unwrap(),
2945            Command::DbSize,
2946        );
2947    }
2948
2949    #[test]
2950    fn dbsize_case_insensitive() {
2951        assert_eq!(
2952            Command::from_frame(cmd(&["dbsize"])).unwrap(),
2953            Command::DbSize,
2954        );
2955    }
2956
2957    #[test]
2958    fn dbsize_extra_args() {
2959        let err = Command::from_frame(cmd(&["DBSIZE", "extra"])).unwrap_err();
2960        assert!(matches!(err, ProtocolError::WrongArity(_)));
2961    }
2962
2963    // --- info ---
2964
2965    #[test]
2966    fn info_no_section() {
2967        assert_eq!(
2968            Command::from_frame(cmd(&["INFO"])).unwrap(),
2969            Command::Info { section: None },
2970        );
2971    }
2972
2973    #[test]
2974    fn info_with_section() {
2975        assert_eq!(
2976            Command::from_frame(cmd(&["INFO", "keyspace"])).unwrap(),
2977            Command::Info {
2978                section: Some("keyspace".into())
2979            },
2980        );
2981    }
2982
2983    #[test]
2984    fn info_too_many_args() {
2985        let err = Command::from_frame(cmd(&["INFO", "a", "b"])).unwrap_err();
2986        assert!(matches!(err, ProtocolError::WrongArity(_)));
2987    }
2988
2989    // --- bgsave ---
2990
2991    #[test]
2992    fn bgsave_basic() {
2993        assert_eq!(
2994            Command::from_frame(cmd(&["BGSAVE"])).unwrap(),
2995            Command::BgSave,
2996        );
2997    }
2998
2999    #[test]
3000    fn bgsave_case_insensitive() {
3001        assert_eq!(
3002            Command::from_frame(cmd(&["bgsave"])).unwrap(),
3003            Command::BgSave,
3004        );
3005    }
3006
3007    #[test]
3008    fn bgsave_extra_args() {
3009        let err = Command::from_frame(cmd(&["BGSAVE", "extra"])).unwrap_err();
3010        assert!(matches!(err, ProtocolError::WrongArity(_)));
3011    }
3012
3013    // --- bgrewriteaof ---
3014
3015    #[test]
3016    fn bgrewriteaof_basic() {
3017        assert_eq!(
3018            Command::from_frame(cmd(&["BGREWRITEAOF"])).unwrap(),
3019            Command::BgRewriteAof,
3020        );
3021    }
3022
3023    #[test]
3024    fn bgrewriteaof_case_insensitive() {
3025        assert_eq!(
3026            Command::from_frame(cmd(&["bgrewriteaof"])).unwrap(),
3027            Command::BgRewriteAof,
3028        );
3029    }
3030
3031    #[test]
3032    fn bgrewriteaof_extra_args() {
3033        let err = Command::from_frame(cmd(&["BGREWRITEAOF", "extra"])).unwrap_err();
3034        assert!(matches!(err, ProtocolError::WrongArity(_)));
3035    }
3036
3037    // --- flushdb ---
3038
3039    #[test]
3040    fn flushdb_basic() {
3041        assert_eq!(
3042            Command::from_frame(cmd(&["FLUSHDB"])).unwrap(),
3043            Command::FlushDb { async_mode: false },
3044        );
3045    }
3046
3047    #[test]
3048    fn flushdb_case_insensitive() {
3049        assert_eq!(
3050            Command::from_frame(cmd(&["flushdb"])).unwrap(),
3051            Command::FlushDb { async_mode: false },
3052        );
3053    }
3054
3055    #[test]
3056    fn flushdb_async() {
3057        assert_eq!(
3058            Command::from_frame(cmd(&["FLUSHDB", "ASYNC"])).unwrap(),
3059            Command::FlushDb { async_mode: true },
3060        );
3061    }
3062
3063    #[test]
3064    fn flushdb_async_case_insensitive() {
3065        assert_eq!(
3066            Command::from_frame(cmd(&["flushdb", "async"])).unwrap(),
3067            Command::FlushDb { async_mode: true },
3068        );
3069    }
3070
3071    #[test]
3072    fn flushdb_extra_args() {
3073        let err = Command::from_frame(cmd(&["FLUSHDB", "extra"])).unwrap_err();
3074        assert!(matches!(err, ProtocolError::WrongArity(_)));
3075    }
3076
3077    // --- unlink ---
3078
3079    #[test]
3080    fn unlink_single() {
3081        assert_eq!(
3082            Command::from_frame(cmd(&["UNLINK", "mykey"])).unwrap(),
3083            Command::Unlink {
3084                keys: vec!["mykey".into()]
3085            },
3086        );
3087    }
3088
3089    #[test]
3090    fn unlink_multiple() {
3091        assert_eq!(
3092            Command::from_frame(cmd(&["UNLINK", "a", "b", "c"])).unwrap(),
3093            Command::Unlink {
3094                keys: vec!["a".into(), "b".into(), "c".into()]
3095            },
3096        );
3097    }
3098
3099    #[test]
3100    fn unlink_no_args() {
3101        let err = Command::from_frame(cmd(&["UNLINK"])).unwrap_err();
3102        assert!(matches!(err, ProtocolError::WrongArity(_)));
3103    }
3104
3105    // --- lpush ---
3106
3107    #[test]
3108    fn lpush_single() {
3109        assert_eq!(
3110            Command::from_frame(cmd(&["LPUSH", "list", "val"])).unwrap(),
3111            Command::LPush {
3112                key: "list".into(),
3113                values: vec![Bytes::from("val")],
3114            },
3115        );
3116    }
3117
3118    #[test]
3119    fn lpush_multiple() {
3120        assert_eq!(
3121            Command::from_frame(cmd(&["LPUSH", "list", "a", "b", "c"])).unwrap(),
3122            Command::LPush {
3123                key: "list".into(),
3124                values: vec![Bytes::from("a"), Bytes::from("b"), Bytes::from("c")],
3125            },
3126        );
3127    }
3128
3129    #[test]
3130    fn lpush_no_value() {
3131        let err = Command::from_frame(cmd(&["LPUSH", "key"])).unwrap_err();
3132        assert!(matches!(err, ProtocolError::WrongArity(_)));
3133    }
3134
3135    #[test]
3136    fn lpush_case_insensitive() {
3137        assert!(matches!(
3138            Command::from_frame(cmd(&["lpush", "k", "v"])).unwrap(),
3139            Command::LPush { .. }
3140        ));
3141    }
3142
3143    // --- rpush ---
3144
3145    #[test]
3146    fn rpush_single() {
3147        assert_eq!(
3148            Command::from_frame(cmd(&["RPUSH", "list", "val"])).unwrap(),
3149            Command::RPush {
3150                key: "list".into(),
3151                values: vec![Bytes::from("val")],
3152            },
3153        );
3154    }
3155
3156    #[test]
3157    fn rpush_no_value() {
3158        let err = Command::from_frame(cmd(&["RPUSH", "key"])).unwrap_err();
3159        assert!(matches!(err, ProtocolError::WrongArity(_)));
3160    }
3161
3162    // --- lpop ---
3163
3164    #[test]
3165    fn lpop_basic() {
3166        assert_eq!(
3167            Command::from_frame(cmd(&["LPOP", "list"])).unwrap(),
3168            Command::LPop { key: "list".into() },
3169        );
3170    }
3171
3172    #[test]
3173    fn lpop_wrong_arity() {
3174        let err = Command::from_frame(cmd(&["LPOP"])).unwrap_err();
3175        assert!(matches!(err, ProtocolError::WrongArity(_)));
3176    }
3177
3178    // --- rpop ---
3179
3180    #[test]
3181    fn rpop_basic() {
3182        assert_eq!(
3183            Command::from_frame(cmd(&["RPOP", "list"])).unwrap(),
3184            Command::RPop { key: "list".into() },
3185        );
3186    }
3187
3188    // --- lrange ---
3189
3190    #[test]
3191    fn lrange_basic() {
3192        assert_eq!(
3193            Command::from_frame(cmd(&["LRANGE", "list", "0", "-1"])).unwrap(),
3194            Command::LRange {
3195                key: "list".into(),
3196                start: 0,
3197                stop: -1,
3198            },
3199        );
3200    }
3201
3202    #[test]
3203    fn lrange_wrong_arity() {
3204        let err = Command::from_frame(cmd(&["LRANGE", "list", "0"])).unwrap_err();
3205        assert!(matches!(err, ProtocolError::WrongArity(_)));
3206    }
3207
3208    #[test]
3209    fn lrange_invalid_index() {
3210        let err = Command::from_frame(cmd(&["LRANGE", "list", "abc", "0"])).unwrap_err();
3211        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
3212    }
3213
3214    // --- llen ---
3215
3216    #[test]
3217    fn llen_basic() {
3218        assert_eq!(
3219            Command::from_frame(cmd(&["LLEN", "list"])).unwrap(),
3220            Command::LLen { key: "list".into() },
3221        );
3222    }
3223
3224    #[test]
3225    fn llen_wrong_arity() {
3226        let err = Command::from_frame(cmd(&["LLEN"])).unwrap_err();
3227        assert!(matches!(err, ProtocolError::WrongArity(_)));
3228    }
3229
3230    // --- type ---
3231
3232    #[test]
3233    fn type_basic() {
3234        assert_eq!(
3235            Command::from_frame(cmd(&["TYPE", "key"])).unwrap(),
3236            Command::Type { key: "key".into() },
3237        );
3238    }
3239
3240    #[test]
3241    fn type_wrong_arity() {
3242        let err = Command::from_frame(cmd(&["TYPE"])).unwrap_err();
3243        assert!(matches!(err, ProtocolError::WrongArity(_)));
3244    }
3245
3246    #[test]
3247    fn type_case_insensitive() {
3248        assert!(matches!(
3249            Command::from_frame(cmd(&["type", "k"])).unwrap(),
3250            Command::Type { .. }
3251        ));
3252    }
3253
3254    // --- general ---
3255
3256    #[test]
3257    fn unknown_command() {
3258        assert_eq!(
3259            Command::from_frame(cmd(&["FOOBAR", "arg"])).unwrap(),
3260            Command::Unknown("FOOBAR".into()),
3261        );
3262    }
3263
3264    #[test]
3265    fn non_array_frame() {
3266        let err = Command::from_frame(Frame::Simple("PING".into())).unwrap_err();
3267        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
3268    }
3269
3270    #[test]
3271    fn empty_array() {
3272        let err = Command::from_frame(Frame::Array(vec![])).unwrap_err();
3273        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
3274    }
3275
3276    // --- zadd ---
3277
3278    #[test]
3279    fn zadd_basic() {
3280        let parsed = Command::from_frame(cmd(&["ZADD", "board", "100", "alice"])).unwrap();
3281        match parsed {
3282            Command::ZAdd {
3283                key,
3284                flags,
3285                members,
3286            } => {
3287                assert_eq!(key, "board");
3288                assert_eq!(flags, ZAddFlags::default());
3289                assert_eq!(members, vec![(100.0, "alice".into())]);
3290            }
3291            other => panic!("expected ZAdd, got {other:?}"),
3292        }
3293    }
3294
3295    #[test]
3296    fn zadd_multiple_members() {
3297        let parsed =
3298            Command::from_frame(cmd(&["ZADD", "board", "100", "alice", "200", "bob"])).unwrap();
3299        match parsed {
3300            Command::ZAdd { members, .. } => {
3301                assert_eq!(members.len(), 2);
3302                assert_eq!(members[0], (100.0, "alice".into()));
3303                assert_eq!(members[1], (200.0, "bob".into()));
3304            }
3305            other => panic!("expected ZAdd, got {other:?}"),
3306        }
3307    }
3308
3309    #[test]
3310    fn zadd_with_flags() {
3311        let parsed = Command::from_frame(cmd(&["ZADD", "z", "NX", "CH", "100", "alice"])).unwrap();
3312        match parsed {
3313            Command::ZAdd { flags, .. } => {
3314                assert!(flags.nx);
3315                assert!(flags.ch);
3316                assert!(!flags.xx);
3317                assert!(!flags.gt);
3318                assert!(!flags.lt);
3319            }
3320            other => panic!("expected ZAdd, got {other:?}"),
3321        }
3322    }
3323
3324    #[test]
3325    fn zadd_gt_flag() {
3326        let parsed = Command::from_frame(cmd(&["zadd", "z", "gt", "100", "alice"])).unwrap();
3327        match parsed {
3328            Command::ZAdd { flags, .. } => assert!(flags.gt),
3329            other => panic!("expected ZAdd, got {other:?}"),
3330        }
3331    }
3332
3333    #[test]
3334    fn zadd_nx_xx_conflict() {
3335        let err = Command::from_frame(cmd(&["ZADD", "z", "NX", "XX", "100", "alice"])).unwrap_err();
3336        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
3337    }
3338
3339    #[test]
3340    fn zadd_gt_lt_conflict() {
3341        let err = Command::from_frame(cmd(&["ZADD", "z", "GT", "LT", "100", "alice"])).unwrap_err();
3342        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
3343    }
3344
3345    #[test]
3346    fn zadd_wrong_arity() {
3347        let err = Command::from_frame(cmd(&["ZADD", "z"])).unwrap_err();
3348        assert!(matches!(err, ProtocolError::WrongArity(_)));
3349    }
3350
3351    #[test]
3352    fn zadd_odd_score_member_count() {
3353        // one score without a member
3354        let err = Command::from_frame(cmd(&["ZADD", "z", "100"])).unwrap_err();
3355        assert!(matches!(err, ProtocolError::WrongArity(_)));
3356    }
3357
3358    #[test]
3359    fn zadd_invalid_score() {
3360        let err = Command::from_frame(cmd(&["ZADD", "z", "notanum", "alice"])).unwrap_err();
3361        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
3362    }
3363
3364    #[test]
3365    fn zadd_nan_score() {
3366        let err = Command::from_frame(cmd(&["ZADD", "z", "nan", "alice"])).unwrap_err();
3367        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
3368    }
3369
3370    #[test]
3371    fn zadd_negative_score() {
3372        let parsed = Command::from_frame(cmd(&["ZADD", "z", "-50.5", "alice"])).unwrap();
3373        match parsed {
3374            Command::ZAdd { members, .. } => {
3375                assert_eq!(members[0].0, -50.5);
3376            }
3377            other => panic!("expected ZAdd, got {other:?}"),
3378        }
3379    }
3380
3381    // --- zrem ---
3382
3383    #[test]
3384    fn zrem_basic() {
3385        assert_eq!(
3386            Command::from_frame(cmd(&["ZREM", "z", "alice"])).unwrap(),
3387            Command::ZRem {
3388                key: "z".into(),
3389                members: vec!["alice".into()],
3390            },
3391        );
3392    }
3393
3394    #[test]
3395    fn zrem_multiple() {
3396        let parsed = Command::from_frame(cmd(&["ZREM", "z", "a", "b", "c"])).unwrap();
3397        match parsed {
3398            Command::ZRem { members, .. } => assert_eq!(members.len(), 3),
3399            other => panic!("expected ZRem, got {other:?}"),
3400        }
3401    }
3402
3403    #[test]
3404    fn zrem_wrong_arity() {
3405        let err = Command::from_frame(cmd(&["ZREM", "z"])).unwrap_err();
3406        assert!(matches!(err, ProtocolError::WrongArity(_)));
3407    }
3408
3409    // --- zscore ---
3410
3411    #[test]
3412    fn zscore_basic() {
3413        assert_eq!(
3414            Command::from_frame(cmd(&["ZSCORE", "z", "alice"])).unwrap(),
3415            Command::ZScore {
3416                key: "z".into(),
3417                member: "alice".into(),
3418            },
3419        );
3420    }
3421
3422    #[test]
3423    fn zscore_wrong_arity() {
3424        let err = Command::from_frame(cmd(&["ZSCORE", "z"])).unwrap_err();
3425        assert!(matches!(err, ProtocolError::WrongArity(_)));
3426    }
3427
3428    // --- zrank ---
3429
3430    #[test]
3431    fn zrank_basic() {
3432        assert_eq!(
3433            Command::from_frame(cmd(&["ZRANK", "z", "alice"])).unwrap(),
3434            Command::ZRank {
3435                key: "z".into(),
3436                member: "alice".into(),
3437            },
3438        );
3439    }
3440
3441    #[test]
3442    fn zrank_wrong_arity() {
3443        let err = Command::from_frame(cmd(&["ZRANK", "z"])).unwrap_err();
3444        assert!(matches!(err, ProtocolError::WrongArity(_)));
3445    }
3446
3447    // --- zcard ---
3448
3449    #[test]
3450    fn zcard_basic() {
3451        assert_eq!(
3452            Command::from_frame(cmd(&["ZCARD", "z"])).unwrap(),
3453            Command::ZCard { key: "z".into() },
3454        );
3455    }
3456
3457    #[test]
3458    fn zcard_wrong_arity() {
3459        let err = Command::from_frame(cmd(&["ZCARD"])).unwrap_err();
3460        assert!(matches!(err, ProtocolError::WrongArity(_)));
3461    }
3462
3463    // --- zrange ---
3464
3465    #[test]
3466    fn zrange_basic() {
3467        assert_eq!(
3468            Command::from_frame(cmd(&["ZRANGE", "z", "0", "-1"])).unwrap(),
3469            Command::ZRange {
3470                key: "z".into(),
3471                start: 0,
3472                stop: -1,
3473                with_scores: false,
3474            },
3475        );
3476    }
3477
3478    #[test]
3479    fn zrange_with_scores() {
3480        assert_eq!(
3481            Command::from_frame(cmd(&["ZRANGE", "z", "0", "-1", "WITHSCORES"])).unwrap(),
3482            Command::ZRange {
3483                key: "z".into(),
3484                start: 0,
3485                stop: -1,
3486                with_scores: true,
3487            },
3488        );
3489    }
3490
3491    #[test]
3492    fn zrange_withscores_case_insensitive() {
3493        assert_eq!(
3494            Command::from_frame(cmd(&["zrange", "z", "0", "-1", "withscores"])).unwrap(),
3495            Command::ZRange {
3496                key: "z".into(),
3497                start: 0,
3498                stop: -1,
3499                with_scores: true,
3500            },
3501        );
3502    }
3503
3504    #[test]
3505    fn zrange_invalid_option() {
3506        let err = Command::from_frame(cmd(&["ZRANGE", "z", "0", "-1", "BADOPT"])).unwrap_err();
3507        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
3508    }
3509
3510    #[test]
3511    fn zrange_wrong_arity() {
3512        let err = Command::from_frame(cmd(&["ZRANGE", "z", "0"])).unwrap_err();
3513        assert!(matches!(err, ProtocolError::WrongArity(_)));
3514    }
3515
3516    // --- incr ---
3517
3518    #[test]
3519    fn incr_basic() {
3520        assert_eq!(
3521            Command::from_frame(cmd(&["INCR", "counter"])).unwrap(),
3522            Command::Incr {
3523                key: "counter".into()
3524            },
3525        );
3526    }
3527
3528    #[test]
3529    fn incr_wrong_arity() {
3530        let err = Command::from_frame(cmd(&["INCR"])).unwrap_err();
3531        assert!(matches!(err, ProtocolError::WrongArity(_)));
3532    }
3533
3534    // --- decr ---
3535
3536    #[test]
3537    fn decr_basic() {
3538        assert_eq!(
3539            Command::from_frame(cmd(&["DECR", "counter"])).unwrap(),
3540            Command::Decr {
3541                key: "counter".into()
3542            },
3543        );
3544    }
3545
3546    #[test]
3547    fn decr_wrong_arity() {
3548        let err = Command::from_frame(cmd(&["DECR"])).unwrap_err();
3549        assert!(matches!(err, ProtocolError::WrongArity(_)));
3550    }
3551
3552    // --- persist ---
3553
3554    #[test]
3555    fn persist_basic() {
3556        assert_eq!(
3557            Command::from_frame(cmd(&["PERSIST", "key"])).unwrap(),
3558            Command::Persist { key: "key".into() },
3559        );
3560    }
3561
3562    #[test]
3563    fn persist_case_insensitive() {
3564        assert_eq!(
3565            Command::from_frame(cmd(&["persist", "key"])).unwrap(),
3566            Command::Persist { key: "key".into() },
3567        );
3568    }
3569
3570    #[test]
3571    fn persist_wrong_arity() {
3572        let err = Command::from_frame(cmd(&["PERSIST"])).unwrap_err();
3573        assert!(matches!(err, ProtocolError::WrongArity(_)));
3574    }
3575
3576    // --- pttl ---
3577
3578    #[test]
3579    fn pttl_basic() {
3580        assert_eq!(
3581            Command::from_frame(cmd(&["PTTL", "key"])).unwrap(),
3582            Command::Pttl { key: "key".into() },
3583        );
3584    }
3585
3586    #[test]
3587    fn pttl_wrong_arity() {
3588        let err = Command::from_frame(cmd(&["PTTL"])).unwrap_err();
3589        assert!(matches!(err, ProtocolError::WrongArity(_)));
3590    }
3591
3592    // --- pexpire ---
3593
3594    #[test]
3595    fn pexpire_basic() {
3596        assert_eq!(
3597            Command::from_frame(cmd(&["PEXPIRE", "key", "5000"])).unwrap(),
3598            Command::Pexpire {
3599                key: "key".into(),
3600                milliseconds: 5000,
3601            },
3602        );
3603    }
3604
3605    #[test]
3606    fn pexpire_wrong_arity() {
3607        let err = Command::from_frame(cmd(&["PEXPIRE", "key"])).unwrap_err();
3608        assert!(matches!(err, ProtocolError::WrongArity(_)));
3609    }
3610
3611    #[test]
3612    fn pexpire_zero_millis() {
3613        let err = Command::from_frame(cmd(&["PEXPIRE", "key", "0"])).unwrap_err();
3614        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
3615    }
3616
3617    #[test]
3618    fn pexpire_invalid_millis() {
3619        let err = Command::from_frame(cmd(&["PEXPIRE", "key", "notanum"])).unwrap_err();
3620        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
3621    }
3622
3623    // --- scan ---
3624
3625    #[test]
3626    fn scan_basic() {
3627        assert_eq!(
3628            Command::from_frame(cmd(&["SCAN", "0"])).unwrap(),
3629            Command::Scan {
3630                cursor: 0,
3631                pattern: None,
3632                count: None,
3633            },
3634        );
3635    }
3636
3637    #[test]
3638    fn scan_with_match() {
3639        assert_eq!(
3640            Command::from_frame(cmd(&["SCAN", "0", "MATCH", "user:*"])).unwrap(),
3641            Command::Scan {
3642                cursor: 0,
3643                pattern: Some("user:*".into()),
3644                count: None,
3645            },
3646        );
3647    }
3648
3649    #[test]
3650    fn scan_with_count() {
3651        assert_eq!(
3652            Command::from_frame(cmd(&["SCAN", "42", "COUNT", "100"])).unwrap(),
3653            Command::Scan {
3654                cursor: 42,
3655                pattern: None,
3656                count: Some(100),
3657            },
3658        );
3659    }
3660
3661    #[test]
3662    fn scan_with_match_and_count() {
3663        assert_eq!(
3664            Command::from_frame(cmd(&["SCAN", "0", "MATCH", "*:data", "COUNT", "50"])).unwrap(),
3665            Command::Scan {
3666                cursor: 0,
3667                pattern: Some("*:data".into()),
3668                count: Some(50),
3669            },
3670        );
3671    }
3672
3673    #[test]
3674    fn scan_count_before_match() {
3675        assert_eq!(
3676            Command::from_frame(cmd(&["SCAN", "0", "COUNT", "10", "MATCH", "foo*"])).unwrap(),
3677            Command::Scan {
3678                cursor: 0,
3679                pattern: Some("foo*".into()),
3680                count: Some(10),
3681            },
3682        );
3683    }
3684
3685    #[test]
3686    fn scan_case_insensitive() {
3687        assert_eq!(
3688            Command::from_frame(cmd(&["scan", "0", "match", "x*", "count", "5"])).unwrap(),
3689            Command::Scan {
3690                cursor: 0,
3691                pattern: Some("x*".into()),
3692                count: Some(5),
3693            },
3694        );
3695    }
3696
3697    #[test]
3698    fn scan_wrong_arity() {
3699        let err = Command::from_frame(cmd(&["SCAN"])).unwrap_err();
3700        assert!(matches!(err, ProtocolError::WrongArity(_)));
3701    }
3702
3703    #[test]
3704    fn scan_invalid_cursor() {
3705        let err = Command::from_frame(cmd(&["SCAN", "notanum"])).unwrap_err();
3706        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
3707    }
3708
3709    #[test]
3710    fn scan_invalid_count() {
3711        let err = Command::from_frame(cmd(&["SCAN", "0", "COUNT", "bad"])).unwrap_err();
3712        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
3713    }
3714
3715    #[test]
3716    fn scan_unknown_flag() {
3717        let err = Command::from_frame(cmd(&["SCAN", "0", "BADOPT", "val"])).unwrap_err();
3718        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
3719    }
3720
3721    #[test]
3722    fn scan_match_missing_pattern() {
3723        let err = Command::from_frame(cmd(&["SCAN", "0", "MATCH"])).unwrap_err();
3724        assert!(matches!(err, ProtocolError::WrongArity(_)));
3725    }
3726
3727    #[test]
3728    fn scan_count_missing_value() {
3729        let err = Command::from_frame(cmd(&["SCAN", "0", "COUNT"])).unwrap_err();
3730        assert!(matches!(err, ProtocolError::WrongArity(_)));
3731    }
3732
3733    // --- hash commands ---
3734
3735    #[test]
3736    fn hset_single_field() {
3737        assert_eq!(
3738            Command::from_frame(cmd(&["HSET", "h", "field", "value"])).unwrap(),
3739            Command::HSet {
3740                key: "h".into(),
3741                fields: vec![("field".into(), Bytes::from("value"))],
3742            },
3743        );
3744    }
3745
3746    #[test]
3747    fn hset_multiple_fields() {
3748        let parsed = Command::from_frame(cmd(&["HSET", "h", "f1", "v1", "f2", "v2"])).unwrap();
3749        match parsed {
3750            Command::HSet { key, fields } => {
3751                assert_eq!(key, "h");
3752                assert_eq!(fields.len(), 2);
3753            }
3754            other => panic!("expected HSet, got {other:?}"),
3755        }
3756    }
3757
3758    #[test]
3759    fn hset_wrong_arity() {
3760        let err = Command::from_frame(cmd(&["HSET", "h"])).unwrap_err();
3761        assert!(matches!(err, ProtocolError::WrongArity(_)));
3762        let err = Command::from_frame(cmd(&["HSET", "h", "f"])).unwrap_err();
3763        assert!(matches!(err, ProtocolError::WrongArity(_)));
3764    }
3765
3766    #[test]
3767    fn hget_basic() {
3768        assert_eq!(
3769            Command::from_frame(cmd(&["HGET", "h", "field"])).unwrap(),
3770            Command::HGet {
3771                key: "h".into(),
3772                field: "field".into(),
3773            },
3774        );
3775    }
3776
3777    #[test]
3778    fn hget_wrong_arity() {
3779        let err = Command::from_frame(cmd(&["HGET", "h"])).unwrap_err();
3780        assert!(matches!(err, ProtocolError::WrongArity(_)));
3781    }
3782
3783    #[test]
3784    fn hgetall_basic() {
3785        assert_eq!(
3786            Command::from_frame(cmd(&["HGETALL", "h"])).unwrap(),
3787            Command::HGetAll { key: "h".into() },
3788        );
3789    }
3790
3791    #[test]
3792    fn hgetall_wrong_arity() {
3793        let err = Command::from_frame(cmd(&["HGETALL"])).unwrap_err();
3794        assert!(matches!(err, ProtocolError::WrongArity(_)));
3795    }
3796
3797    #[test]
3798    fn hdel_single() {
3799        assert_eq!(
3800            Command::from_frame(cmd(&["HDEL", "h", "f"])).unwrap(),
3801            Command::HDel {
3802                key: "h".into(),
3803                fields: vec!["f".into()],
3804            },
3805        );
3806    }
3807
3808    #[test]
3809    fn hdel_multiple() {
3810        let parsed = Command::from_frame(cmd(&["HDEL", "h", "f1", "f2", "f3"])).unwrap();
3811        match parsed {
3812            Command::HDel { fields, .. } => assert_eq!(fields.len(), 3),
3813            other => panic!("expected HDel, got {other:?}"),
3814        }
3815    }
3816
3817    #[test]
3818    fn hdel_wrong_arity() {
3819        let err = Command::from_frame(cmd(&["HDEL", "h"])).unwrap_err();
3820        assert!(matches!(err, ProtocolError::WrongArity(_)));
3821    }
3822
3823    #[test]
3824    fn hexists_basic() {
3825        assert_eq!(
3826            Command::from_frame(cmd(&["HEXISTS", "h", "f"])).unwrap(),
3827            Command::HExists {
3828                key: "h".into(),
3829                field: "f".into(),
3830            },
3831        );
3832    }
3833
3834    #[test]
3835    fn hlen_basic() {
3836        assert_eq!(
3837            Command::from_frame(cmd(&["HLEN", "h"])).unwrap(),
3838            Command::HLen { key: "h".into() },
3839        );
3840    }
3841
3842    #[test]
3843    fn hincrby_basic() {
3844        assert_eq!(
3845            Command::from_frame(cmd(&["HINCRBY", "h", "f", "5"])).unwrap(),
3846            Command::HIncrBy {
3847                key: "h".into(),
3848                field: "f".into(),
3849                delta: 5,
3850            },
3851        );
3852    }
3853
3854    #[test]
3855    fn hincrby_negative() {
3856        assert_eq!(
3857            Command::from_frame(cmd(&["HINCRBY", "h", "f", "-3"])).unwrap(),
3858            Command::HIncrBy {
3859                key: "h".into(),
3860                field: "f".into(),
3861                delta: -3,
3862            },
3863        );
3864    }
3865
3866    #[test]
3867    fn hincrby_wrong_arity() {
3868        let err = Command::from_frame(cmd(&["HINCRBY", "h", "f"])).unwrap_err();
3869        assert!(matches!(err, ProtocolError::WrongArity(_)));
3870    }
3871
3872    #[test]
3873    fn hkeys_basic() {
3874        assert_eq!(
3875            Command::from_frame(cmd(&["HKEYS", "h"])).unwrap(),
3876            Command::HKeys { key: "h".into() },
3877        );
3878    }
3879
3880    #[test]
3881    fn hvals_basic() {
3882        assert_eq!(
3883            Command::from_frame(cmd(&["HVALS", "h"])).unwrap(),
3884            Command::HVals { key: "h".into() },
3885        );
3886    }
3887
3888    #[test]
3889    fn hmget_basic() {
3890        assert_eq!(
3891            Command::from_frame(cmd(&["HMGET", "h", "f1", "f2"])).unwrap(),
3892            Command::HMGet {
3893                key: "h".into(),
3894                fields: vec!["f1".into(), "f2".into()],
3895            },
3896        );
3897    }
3898
3899    #[test]
3900    fn hmget_wrong_arity() {
3901        let err = Command::from_frame(cmd(&["HMGET", "h"])).unwrap_err();
3902        assert!(matches!(err, ProtocolError::WrongArity(_)));
3903    }
3904
3905    #[test]
3906    fn hash_commands_case_insensitive() {
3907        assert!(matches!(
3908            Command::from_frame(cmd(&["hset", "h", "f", "v"])).unwrap(),
3909            Command::HSet { .. }
3910        ));
3911        assert!(matches!(
3912            Command::from_frame(cmd(&["hget", "h", "f"])).unwrap(),
3913            Command::HGet { .. }
3914        ));
3915        assert!(matches!(
3916            Command::from_frame(cmd(&["hgetall", "h"])).unwrap(),
3917            Command::HGetAll { .. }
3918        ));
3919    }
3920
3921    // --- set commands ---
3922
3923    #[test]
3924    fn sadd_single_member() {
3925        assert_eq!(
3926            Command::from_frame(cmd(&["SADD", "s", "member"])).unwrap(),
3927            Command::SAdd {
3928                key: "s".into(),
3929                members: vec!["member".into()],
3930            },
3931        );
3932    }
3933
3934    #[test]
3935    fn sadd_multiple_members() {
3936        let parsed = Command::from_frame(cmd(&["SADD", "s", "a", "b", "c"])).unwrap();
3937        match parsed {
3938            Command::SAdd { key, members } => {
3939                assert_eq!(key, "s");
3940                assert_eq!(members.len(), 3);
3941            }
3942            other => panic!("expected SAdd, got {other:?}"),
3943        }
3944    }
3945
3946    #[test]
3947    fn sadd_wrong_arity() {
3948        let err = Command::from_frame(cmd(&["SADD", "s"])).unwrap_err();
3949        assert!(matches!(err, ProtocolError::WrongArity(_)));
3950    }
3951
3952    #[test]
3953    fn srem_single_member() {
3954        assert_eq!(
3955            Command::from_frame(cmd(&["SREM", "s", "member"])).unwrap(),
3956            Command::SRem {
3957                key: "s".into(),
3958                members: vec!["member".into()],
3959            },
3960        );
3961    }
3962
3963    #[test]
3964    fn srem_multiple_members() {
3965        let parsed = Command::from_frame(cmd(&["SREM", "s", "a", "b"])).unwrap();
3966        match parsed {
3967            Command::SRem { key, members } => {
3968                assert_eq!(key, "s");
3969                assert_eq!(members.len(), 2);
3970            }
3971            other => panic!("expected SRem, got {other:?}"),
3972        }
3973    }
3974
3975    #[test]
3976    fn srem_wrong_arity() {
3977        let err = Command::from_frame(cmd(&["SREM", "s"])).unwrap_err();
3978        assert!(matches!(err, ProtocolError::WrongArity(_)));
3979    }
3980
3981    #[test]
3982    fn smembers_basic() {
3983        assert_eq!(
3984            Command::from_frame(cmd(&["SMEMBERS", "s"])).unwrap(),
3985            Command::SMembers { key: "s".into() },
3986        );
3987    }
3988
3989    #[test]
3990    fn smembers_wrong_arity() {
3991        let err = Command::from_frame(cmd(&["SMEMBERS"])).unwrap_err();
3992        assert!(matches!(err, ProtocolError::WrongArity(_)));
3993    }
3994
3995    #[test]
3996    fn sismember_basic() {
3997        assert_eq!(
3998            Command::from_frame(cmd(&["SISMEMBER", "s", "member"])).unwrap(),
3999            Command::SIsMember {
4000                key: "s".into(),
4001                member: "member".into(),
4002            },
4003        );
4004    }
4005
4006    #[test]
4007    fn sismember_wrong_arity() {
4008        let err = Command::from_frame(cmd(&["SISMEMBER", "s"])).unwrap_err();
4009        assert!(matches!(err, ProtocolError::WrongArity(_)));
4010    }
4011
4012    #[test]
4013    fn scard_basic() {
4014        assert_eq!(
4015            Command::from_frame(cmd(&["SCARD", "s"])).unwrap(),
4016            Command::SCard { key: "s".into() },
4017        );
4018    }
4019
4020    #[test]
4021    fn scard_wrong_arity() {
4022        let err = Command::from_frame(cmd(&["SCARD"])).unwrap_err();
4023        assert!(matches!(err, ProtocolError::WrongArity(_)));
4024    }
4025
4026    #[test]
4027    fn set_commands_case_insensitive() {
4028        assert!(matches!(
4029            Command::from_frame(cmd(&["sadd", "s", "m"])).unwrap(),
4030            Command::SAdd { .. }
4031        ));
4032        assert!(matches!(
4033            Command::from_frame(cmd(&["srem", "s", "m"])).unwrap(),
4034            Command::SRem { .. }
4035        ));
4036        assert!(matches!(
4037            Command::from_frame(cmd(&["smembers", "s"])).unwrap(),
4038            Command::SMembers { .. }
4039        ));
4040    }
4041
4042    // --- cluster commands ---
4043
4044    #[test]
4045    fn cluster_info_basic() {
4046        assert_eq!(
4047            Command::from_frame(cmd(&["CLUSTER", "INFO"])).unwrap(),
4048            Command::ClusterInfo,
4049        );
4050    }
4051
4052    #[test]
4053    fn cluster_nodes_basic() {
4054        assert_eq!(
4055            Command::from_frame(cmd(&["CLUSTER", "NODES"])).unwrap(),
4056            Command::ClusterNodes,
4057        );
4058    }
4059
4060    #[test]
4061    fn cluster_slots_basic() {
4062        assert_eq!(
4063            Command::from_frame(cmd(&["CLUSTER", "SLOTS"])).unwrap(),
4064            Command::ClusterSlots,
4065        );
4066    }
4067
4068    #[test]
4069    fn cluster_keyslot_basic() {
4070        assert_eq!(
4071            Command::from_frame(cmd(&["CLUSTER", "KEYSLOT", "mykey"])).unwrap(),
4072            Command::ClusterKeySlot {
4073                key: "mykey".into()
4074            },
4075        );
4076    }
4077
4078    #[test]
4079    fn cluster_keyslot_wrong_arity() {
4080        let err = Command::from_frame(cmd(&["CLUSTER", "KEYSLOT"])).unwrap_err();
4081        assert!(matches!(err, ProtocolError::WrongArity(_)));
4082    }
4083
4084    #[test]
4085    fn cluster_myid_basic() {
4086        assert_eq!(
4087            Command::from_frame(cmd(&["CLUSTER", "MYID"])).unwrap(),
4088            Command::ClusterMyId,
4089        );
4090    }
4091
4092    #[test]
4093    fn cluster_unknown_subcommand() {
4094        let err = Command::from_frame(cmd(&["CLUSTER", "BADCMD"])).unwrap_err();
4095        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
4096    }
4097
4098    #[test]
4099    fn cluster_no_subcommand() {
4100        let err = Command::from_frame(cmd(&["CLUSTER"])).unwrap_err();
4101        assert!(matches!(err, ProtocolError::WrongArity(_)));
4102    }
4103
4104    #[test]
4105    fn cluster_case_insensitive() {
4106        assert!(matches!(
4107            Command::from_frame(cmd(&["cluster", "info"])).unwrap(),
4108            Command::ClusterInfo
4109        ));
4110        assert!(matches!(
4111            Command::from_frame(cmd(&["cluster", "keyslot", "k"])).unwrap(),
4112            Command::ClusterKeySlot { .. }
4113        ));
4114    }
4115
4116    #[test]
4117    fn asking_basic() {
4118        assert_eq!(
4119            Command::from_frame(cmd(&["ASKING"])).unwrap(),
4120            Command::Asking,
4121        );
4122    }
4123
4124    #[test]
4125    fn asking_wrong_arity() {
4126        let err = Command::from_frame(cmd(&["ASKING", "extra"])).unwrap_err();
4127        assert!(matches!(err, ProtocolError::WrongArity(_)));
4128    }
4129
4130    #[test]
4131    fn cluster_setslot_importing() {
4132        assert_eq!(
4133            Command::from_frame(cmd(&["CLUSTER", "SETSLOT", "100", "IMPORTING", "node123"]))
4134                .unwrap(),
4135            Command::ClusterSetSlotImporting {
4136                slot: 100,
4137                node_id: "node123".into()
4138            },
4139        );
4140    }
4141
4142    #[test]
4143    fn cluster_setslot_migrating() {
4144        assert_eq!(
4145            Command::from_frame(cmd(&["CLUSTER", "SETSLOT", "200", "MIGRATING", "node456"]))
4146                .unwrap(),
4147            Command::ClusterSetSlotMigrating {
4148                slot: 200,
4149                node_id: "node456".into()
4150            },
4151        );
4152    }
4153
4154    #[test]
4155    fn cluster_setslot_node() {
4156        assert_eq!(
4157            Command::from_frame(cmd(&["CLUSTER", "SETSLOT", "300", "NODE", "node789"])).unwrap(),
4158            Command::ClusterSetSlotNode {
4159                slot: 300,
4160                node_id: "node789".into()
4161            },
4162        );
4163    }
4164
4165    #[test]
4166    fn cluster_setslot_stable() {
4167        assert_eq!(
4168            Command::from_frame(cmd(&["CLUSTER", "SETSLOT", "400", "STABLE"])).unwrap(),
4169            Command::ClusterSetSlotStable { slot: 400 },
4170        );
4171    }
4172
4173    #[test]
4174    fn cluster_setslot_invalid_slot() {
4175        let err =
4176            Command::from_frame(cmd(&["CLUSTER", "SETSLOT", "notanumber", "STABLE"])).unwrap_err();
4177        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
4178    }
4179
4180    #[test]
4181    fn cluster_setslot_wrong_arity() {
4182        let err = Command::from_frame(cmd(&["CLUSTER", "SETSLOT", "100"])).unwrap_err();
4183        assert!(matches!(err, ProtocolError::WrongArity(_)));
4184    }
4185
4186    #[test]
4187    fn migrate_basic() {
4188        assert_eq!(
4189            Command::from_frame(cmd(&["MIGRATE", "127.0.0.1", "6379", "mykey", "0", "5000"]))
4190                .unwrap(),
4191            Command::Migrate {
4192                host: "127.0.0.1".into(),
4193                port: 6379,
4194                key: "mykey".into(),
4195                db: 0,
4196                timeout_ms: 5000,
4197                copy: false,
4198                replace: false,
4199            },
4200        );
4201    }
4202
4203    #[test]
4204    fn migrate_with_options() {
4205        assert_eq!(
4206            Command::from_frame(cmd(&[
4207                "MIGRATE",
4208                "192.168.1.1",
4209                "6380",
4210                "testkey",
4211                "1",
4212                "10000",
4213                "COPY",
4214                "REPLACE"
4215            ]))
4216            .unwrap(),
4217            Command::Migrate {
4218                host: "192.168.1.1".into(),
4219                port: 6380,
4220                key: "testkey".into(),
4221                db: 1,
4222                timeout_ms: 10000,
4223                copy: true,
4224                replace: true,
4225            },
4226        );
4227    }
4228
4229    #[test]
4230    fn migrate_wrong_arity() {
4231        let err = Command::from_frame(cmd(&["MIGRATE", "host", "port", "key"])).unwrap_err();
4232        assert!(matches!(err, ProtocolError::WrongArity(_)));
4233    }
4234
4235    #[test]
4236    fn migrate_invalid_port() {
4237        let err = Command::from_frame(cmd(&["MIGRATE", "host", "notaport", "key", "0", "1000"]))
4238            .unwrap_err();
4239        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
4240    }
4241
4242    #[test]
4243    fn cluster_meet_basic() {
4244        assert_eq!(
4245            Command::from_frame(cmd(&["CLUSTER", "MEET", "192.168.1.1", "6379"])).unwrap(),
4246            Command::ClusterMeet {
4247                ip: "192.168.1.1".into(),
4248                port: 6379
4249            },
4250        );
4251    }
4252
4253    #[test]
4254    fn cluster_addslots_basic() {
4255        assert_eq!(
4256            Command::from_frame(cmd(&["CLUSTER", "ADDSLOTS", "0", "1", "2"])).unwrap(),
4257            Command::ClusterAddSlots {
4258                slots: vec![0, 1, 2]
4259            },
4260        );
4261    }
4262
4263    #[test]
4264    fn cluster_delslots_basic() {
4265        assert_eq!(
4266            Command::from_frame(cmd(&["CLUSTER", "DELSLOTS", "100", "101"])).unwrap(),
4267            Command::ClusterDelSlots {
4268                slots: vec![100, 101]
4269            },
4270        );
4271    }
4272
4273    #[test]
4274    fn cluster_forget_basic() {
4275        assert_eq!(
4276            Command::from_frame(cmd(&["CLUSTER", "FORGET", "abc123"])).unwrap(),
4277            Command::ClusterForget {
4278                node_id: "abc123".into()
4279            },
4280        );
4281    }
4282
4283    #[test]
4284    fn cluster_replicate_basic() {
4285        assert_eq!(
4286            Command::from_frame(cmd(&["CLUSTER", "REPLICATE", "master-id"])).unwrap(),
4287            Command::ClusterReplicate {
4288                node_id: "master-id".into()
4289            },
4290        );
4291    }
4292
4293    #[test]
4294    fn cluster_failover_basic() {
4295        assert_eq!(
4296            Command::from_frame(cmd(&["CLUSTER", "FAILOVER"])).unwrap(),
4297            Command::ClusterFailover {
4298                force: false,
4299                takeover: false
4300            },
4301        );
4302    }
4303
4304    #[test]
4305    fn cluster_failover_force() {
4306        assert_eq!(
4307            Command::from_frame(cmd(&["CLUSTER", "FAILOVER", "FORCE"])).unwrap(),
4308            Command::ClusterFailover {
4309                force: true,
4310                takeover: false
4311            },
4312        );
4313    }
4314
4315    #[test]
4316    fn cluster_failover_takeover() {
4317        assert_eq!(
4318            Command::from_frame(cmd(&["CLUSTER", "FAILOVER", "TAKEOVER"])).unwrap(),
4319            Command::ClusterFailover {
4320                force: false,
4321                takeover: true
4322            },
4323        );
4324    }
4325
4326    #[test]
4327    fn cluster_countkeysinslot_basic() {
4328        assert_eq!(
4329            Command::from_frame(cmd(&["CLUSTER", "COUNTKEYSINSLOT", "100"])).unwrap(),
4330            Command::ClusterCountKeysInSlot { slot: 100 },
4331        );
4332    }
4333
4334    #[test]
4335    fn cluster_getkeysinslot_basic() {
4336        assert_eq!(
4337            Command::from_frame(cmd(&["CLUSTER", "GETKEYSINSLOT", "200", "10"])).unwrap(),
4338            Command::ClusterGetKeysInSlot {
4339                slot: 200,
4340                count: 10
4341            },
4342        );
4343    }
4344
4345    // --- pub/sub ---
4346
4347    #[test]
4348    fn subscribe_single_channel() {
4349        assert_eq!(
4350            Command::from_frame(cmd(&["SUBSCRIBE", "news"])).unwrap(),
4351            Command::Subscribe {
4352                channels: vec!["news".into()]
4353            },
4354        );
4355    }
4356
4357    #[test]
4358    fn subscribe_multiple_channels() {
4359        assert_eq!(
4360            Command::from_frame(cmd(&["SUBSCRIBE", "ch1", "ch2", "ch3"])).unwrap(),
4361            Command::Subscribe {
4362                channels: vec!["ch1".into(), "ch2".into(), "ch3".into()]
4363            },
4364        );
4365    }
4366
4367    #[test]
4368    fn subscribe_no_args() {
4369        let err = Command::from_frame(cmd(&["SUBSCRIBE"])).unwrap_err();
4370        assert!(matches!(err, ProtocolError::WrongArity(_)));
4371    }
4372
4373    #[test]
4374    fn unsubscribe_all() {
4375        assert_eq!(
4376            Command::from_frame(cmd(&["UNSUBSCRIBE"])).unwrap(),
4377            Command::Unsubscribe { channels: vec![] },
4378        );
4379    }
4380
4381    #[test]
4382    fn unsubscribe_specific() {
4383        assert_eq!(
4384            Command::from_frame(cmd(&["UNSUBSCRIBE", "news"])).unwrap(),
4385            Command::Unsubscribe {
4386                channels: vec!["news".into()]
4387            },
4388        );
4389    }
4390
4391    #[test]
4392    fn psubscribe_pattern() {
4393        assert_eq!(
4394            Command::from_frame(cmd(&["PSUBSCRIBE", "news.*"])).unwrap(),
4395            Command::PSubscribe {
4396                patterns: vec!["news.*".into()]
4397            },
4398        );
4399    }
4400
4401    #[test]
4402    fn psubscribe_no_args() {
4403        let err = Command::from_frame(cmd(&["PSUBSCRIBE"])).unwrap_err();
4404        assert!(matches!(err, ProtocolError::WrongArity(_)));
4405    }
4406
4407    #[test]
4408    fn punsubscribe_all() {
4409        assert_eq!(
4410            Command::from_frame(cmd(&["PUNSUBSCRIBE"])).unwrap(),
4411            Command::PUnsubscribe { patterns: vec![] },
4412        );
4413    }
4414
4415    #[test]
4416    fn publish_basic() {
4417        assert_eq!(
4418            Command::from_frame(cmd(&["PUBLISH", "news", "hello world"])).unwrap(),
4419            Command::Publish {
4420                channel: "news".into(),
4421                message: Bytes::from("hello world"),
4422            },
4423        );
4424    }
4425
4426    #[test]
4427    fn publish_wrong_arity() {
4428        let err = Command::from_frame(cmd(&["PUBLISH", "news"])).unwrap_err();
4429        assert!(matches!(err, ProtocolError::WrongArity(_)));
4430    }
4431
4432    #[test]
4433    fn subscribe_case_insensitive() {
4434        assert_eq!(
4435            Command::from_frame(cmd(&["subscribe", "ch"])).unwrap(),
4436            Command::Subscribe {
4437                channels: vec!["ch".into()]
4438            },
4439        );
4440    }
4441
4442    #[test]
4443    fn pubsub_channels_no_pattern() {
4444        assert_eq!(
4445            Command::from_frame(cmd(&["PUBSUB", "CHANNELS"])).unwrap(),
4446            Command::PubSubChannels { pattern: None },
4447        );
4448    }
4449
4450    #[test]
4451    fn pubsub_channels_with_pattern() {
4452        assert_eq!(
4453            Command::from_frame(cmd(&["PUBSUB", "CHANNELS", "news.*"])).unwrap(),
4454            Command::PubSubChannels {
4455                pattern: Some("news.*".into())
4456            },
4457        );
4458    }
4459
4460    #[test]
4461    fn pubsub_numsub_no_args() {
4462        assert_eq!(
4463            Command::from_frame(cmd(&["PUBSUB", "NUMSUB"])).unwrap(),
4464            Command::PubSubNumSub { channels: vec![] },
4465        );
4466    }
4467
4468    #[test]
4469    fn pubsub_numsub_with_channels() {
4470        assert_eq!(
4471            Command::from_frame(cmd(&["PUBSUB", "NUMSUB", "ch1", "ch2"])).unwrap(),
4472            Command::PubSubNumSub {
4473                channels: vec!["ch1".into(), "ch2".into()]
4474            },
4475        );
4476    }
4477
4478    #[test]
4479    fn pubsub_numpat() {
4480        assert_eq!(
4481            Command::from_frame(cmd(&["PUBSUB", "NUMPAT"])).unwrap(),
4482            Command::PubSubNumPat,
4483        );
4484    }
4485
4486    #[test]
4487    fn pubsub_no_subcommand() {
4488        let err = Command::from_frame(cmd(&["PUBSUB"])).unwrap_err();
4489        assert!(matches!(err, ProtocolError::WrongArity(_)));
4490    }
4491
4492    #[test]
4493    fn pubsub_unknown_subcommand() {
4494        let err = Command::from_frame(cmd(&["PUBSUB", "BOGUS"])).unwrap_err();
4495        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
4496    }
4497
4498    // --- INCRBY / DECRBY ---
4499
4500    #[test]
4501    fn incrby_basic() {
4502        assert_eq!(
4503            Command::from_frame(cmd(&["INCRBY", "counter", "5"])).unwrap(),
4504            Command::IncrBy {
4505                key: "counter".into(),
4506                delta: 5
4507            },
4508        );
4509    }
4510
4511    #[test]
4512    fn incrby_negative() {
4513        assert_eq!(
4514            Command::from_frame(cmd(&["INCRBY", "counter", "-3"])).unwrap(),
4515            Command::IncrBy {
4516                key: "counter".into(),
4517                delta: -3
4518            },
4519        );
4520    }
4521
4522    #[test]
4523    fn incrby_wrong_arity() {
4524        let err = Command::from_frame(cmd(&["INCRBY", "key"])).unwrap_err();
4525        assert!(matches!(err, ProtocolError::WrongArity(_)));
4526    }
4527
4528    #[test]
4529    fn incrby_not_integer() {
4530        let err = Command::from_frame(cmd(&["INCRBY", "key", "abc"])).unwrap_err();
4531        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
4532    }
4533
4534    #[test]
4535    fn decrby_basic() {
4536        assert_eq!(
4537            Command::from_frame(cmd(&["DECRBY", "counter", "10"])).unwrap(),
4538            Command::DecrBy {
4539                key: "counter".into(),
4540                delta: 10
4541            },
4542        );
4543    }
4544
4545    #[test]
4546    fn decrby_wrong_arity() {
4547        let err = Command::from_frame(cmd(&["DECRBY"])).unwrap_err();
4548        assert!(matches!(err, ProtocolError::WrongArity(_)));
4549    }
4550
4551    // --- INCRBYFLOAT ---
4552
4553    #[test]
4554    fn incrbyfloat_basic() {
4555        let cmd = Command::from_frame(cmd(&["INCRBYFLOAT", "key", "2.5"])).unwrap();
4556        match cmd {
4557            Command::IncrByFloat { key, delta } => {
4558                assert_eq!(key, "key");
4559                assert!((delta - 2.5).abs() < f64::EPSILON);
4560            }
4561            other => panic!("expected IncrByFloat, got {other:?}"),
4562        }
4563    }
4564
4565    #[test]
4566    fn incrbyfloat_negative() {
4567        let cmd = Command::from_frame(cmd(&["INCRBYFLOAT", "key", "-1.5"])).unwrap();
4568        match cmd {
4569            Command::IncrByFloat { key, delta } => {
4570                assert_eq!(key, "key");
4571                assert!((delta - (-1.5)).abs() < f64::EPSILON);
4572            }
4573            other => panic!("expected IncrByFloat, got {other:?}"),
4574        }
4575    }
4576
4577    #[test]
4578    fn incrbyfloat_wrong_arity() {
4579        let err = Command::from_frame(cmd(&["INCRBYFLOAT", "key"])).unwrap_err();
4580        assert!(matches!(err, ProtocolError::WrongArity(_)));
4581    }
4582
4583    #[test]
4584    fn incrbyfloat_not_a_float() {
4585        let err = Command::from_frame(cmd(&["INCRBYFLOAT", "key", "abc"])).unwrap_err();
4586        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
4587    }
4588
4589    // --- APPEND / STRLEN ---
4590
4591    #[test]
4592    fn append_basic() {
4593        assert_eq!(
4594            Command::from_frame(cmd(&["APPEND", "key", "value"])).unwrap(),
4595            Command::Append {
4596                key: "key".into(),
4597                value: Bytes::from("value")
4598            },
4599        );
4600    }
4601
4602    #[test]
4603    fn append_wrong_arity() {
4604        let err = Command::from_frame(cmd(&["APPEND", "key"])).unwrap_err();
4605        assert!(matches!(err, ProtocolError::WrongArity(_)));
4606    }
4607
4608    #[test]
4609    fn strlen_basic() {
4610        assert_eq!(
4611            Command::from_frame(cmd(&["STRLEN", "key"])).unwrap(),
4612            Command::Strlen { key: "key".into() },
4613        );
4614    }
4615
4616    #[test]
4617    fn strlen_wrong_arity() {
4618        let err = Command::from_frame(cmd(&["STRLEN"])).unwrap_err();
4619        assert!(matches!(err, ProtocolError::WrongArity(_)));
4620    }
4621
4622    // --- KEYS ---
4623
4624    #[test]
4625    fn keys_basic() {
4626        assert_eq!(
4627            Command::from_frame(cmd(&["KEYS", "user:*"])).unwrap(),
4628            Command::Keys {
4629                pattern: "user:*".into()
4630            },
4631        );
4632    }
4633
4634    #[test]
4635    fn keys_wrong_arity() {
4636        let err = Command::from_frame(cmd(&["KEYS"])).unwrap_err();
4637        assert!(matches!(err, ProtocolError::WrongArity(_)));
4638    }
4639
4640    // --- RENAME ---
4641
4642    #[test]
4643    fn rename_basic() {
4644        assert_eq!(
4645            Command::from_frame(cmd(&["RENAME", "old", "new"])).unwrap(),
4646            Command::Rename {
4647                key: "old".into(),
4648                newkey: "new".into()
4649            },
4650        );
4651    }
4652
4653    #[test]
4654    fn rename_wrong_arity() {
4655        let err = Command::from_frame(cmd(&["RENAME", "only"])).unwrap_err();
4656        assert!(matches!(err, ProtocolError::WrongArity(_)));
4657    }
4658
4659    // --- AUTH ---
4660
4661    #[test]
4662    fn auth_legacy() {
4663        assert_eq!(
4664            Command::from_frame(cmd(&["AUTH", "secret"])).unwrap(),
4665            Command::Auth {
4666                username: None,
4667                password: "secret".into()
4668            },
4669        );
4670    }
4671
4672    #[test]
4673    fn auth_with_username() {
4674        assert_eq!(
4675            Command::from_frame(cmd(&["AUTH", "default", "secret"])).unwrap(),
4676            Command::Auth {
4677                username: Some("default".into()),
4678                password: "secret".into()
4679            },
4680        );
4681    }
4682
4683    #[test]
4684    fn auth_no_args() {
4685        let err = Command::from_frame(cmd(&["AUTH"])).unwrap_err();
4686        assert!(matches!(err, ProtocolError::WrongArity(_)));
4687    }
4688
4689    #[test]
4690    fn auth_too_many_args() {
4691        let err = Command::from_frame(cmd(&["AUTH", "a", "b", "c"])).unwrap_err();
4692        assert!(matches!(err, ProtocolError::WrongArity(_)));
4693    }
4694
4695    // --- QUIT ---
4696
4697    #[test]
4698    fn quit_basic() {
4699        assert_eq!(Command::from_frame(cmd(&["QUIT"])).unwrap(), Command::Quit,);
4700    }
4701
4702    #[test]
4703    fn quit_wrong_arity() {
4704        let err = Command::from_frame(cmd(&["QUIT", "extra"])).unwrap_err();
4705        assert!(matches!(err, ProtocolError::WrongArity(_)));
4706    }
4707
4708    // --- PROTO.REGISTER ---
4709
4710    #[test]
4711    fn proto_register_basic() {
4712        assert_eq!(
4713            Command::from_frame(cmd(&["PROTO.REGISTER", "myschema", "descriptor"])).unwrap(),
4714            Command::ProtoRegister {
4715                name: "myschema".into(),
4716                descriptor: Bytes::from("descriptor"),
4717            },
4718        );
4719    }
4720
4721    #[test]
4722    fn proto_register_case_insensitive() {
4723        assert!(Command::from_frame(cmd(&["proto.register", "s", "d"])).is_ok());
4724    }
4725
4726    #[test]
4727    fn proto_register_wrong_arity() {
4728        let err = Command::from_frame(cmd(&["PROTO.REGISTER", "only"])).unwrap_err();
4729        assert!(matches!(err, ProtocolError::WrongArity(_)));
4730    }
4731
4732    // --- PROTO.SET ---
4733
4734    #[test]
4735    fn proto_set_basic() {
4736        assert_eq!(
4737            Command::from_frame(cmd(&["PROTO.SET", "key1", "my.Type", "data"])).unwrap(),
4738            Command::ProtoSet {
4739                key: "key1".into(),
4740                type_name: "my.Type".into(),
4741                data: Bytes::from("data"),
4742                expire: None,
4743                nx: false,
4744                xx: false,
4745            },
4746        );
4747    }
4748
4749    #[test]
4750    fn proto_set_with_ex() {
4751        assert_eq!(
4752            Command::from_frame(cmd(&["PROTO.SET", "k", "t", "d", "EX", "60"])).unwrap(),
4753            Command::ProtoSet {
4754                key: "k".into(),
4755                type_name: "t".into(),
4756                data: Bytes::from("d"),
4757                expire: Some(SetExpire::Ex(60)),
4758                nx: false,
4759                xx: false,
4760            },
4761        );
4762    }
4763
4764    #[test]
4765    fn proto_set_with_px_and_nx() {
4766        assert_eq!(
4767            Command::from_frame(cmd(&["PROTO.SET", "k", "t", "d", "PX", "5000", "NX"])).unwrap(),
4768            Command::ProtoSet {
4769                key: "k".into(),
4770                type_name: "t".into(),
4771                data: Bytes::from("d"),
4772                expire: Some(SetExpire::Px(5000)),
4773                nx: true,
4774                xx: false,
4775            },
4776        );
4777    }
4778
4779    #[test]
4780    fn proto_set_nx_xx_conflict() {
4781        let err = Command::from_frame(cmd(&["PROTO.SET", "k", "t", "d", "NX", "XX"])).unwrap_err();
4782        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
4783    }
4784
4785    #[test]
4786    fn proto_set_wrong_arity() {
4787        let err = Command::from_frame(cmd(&["PROTO.SET", "k", "t"])).unwrap_err();
4788        assert!(matches!(err, ProtocolError::WrongArity(_)));
4789    }
4790
4791    #[test]
4792    fn proto_set_zero_expiry() {
4793        let err = Command::from_frame(cmd(&["PROTO.SET", "k", "t", "d", "EX", "0"])).unwrap_err();
4794        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
4795    }
4796
4797    // --- PROTO.GET ---
4798
4799    #[test]
4800    fn proto_get_basic() {
4801        assert_eq!(
4802            Command::from_frame(cmd(&["PROTO.GET", "key1"])).unwrap(),
4803            Command::ProtoGet { key: "key1".into() },
4804        );
4805    }
4806
4807    #[test]
4808    fn proto_get_wrong_arity() {
4809        let err = Command::from_frame(cmd(&["PROTO.GET"])).unwrap_err();
4810        assert!(matches!(err, ProtocolError::WrongArity(_)));
4811    }
4812
4813    // --- PROTO.TYPE ---
4814
4815    #[test]
4816    fn proto_type_basic() {
4817        assert_eq!(
4818            Command::from_frame(cmd(&["PROTO.TYPE", "key1"])).unwrap(),
4819            Command::ProtoType { key: "key1".into() },
4820        );
4821    }
4822
4823    #[test]
4824    fn proto_type_wrong_arity() {
4825        let err = Command::from_frame(cmd(&["PROTO.TYPE"])).unwrap_err();
4826        assert!(matches!(err, ProtocolError::WrongArity(_)));
4827    }
4828
4829    // --- PROTO.SCHEMAS ---
4830
4831    #[test]
4832    fn proto_schemas_basic() {
4833        assert_eq!(
4834            Command::from_frame(cmd(&["PROTO.SCHEMAS"])).unwrap(),
4835            Command::ProtoSchemas,
4836        );
4837    }
4838
4839    #[test]
4840    fn proto_schemas_wrong_arity() {
4841        let err = Command::from_frame(cmd(&["PROTO.SCHEMAS", "extra"])).unwrap_err();
4842        assert!(matches!(err, ProtocolError::WrongArity(_)));
4843    }
4844
4845    // --- PROTO.DESCRIBE ---
4846
4847    #[test]
4848    fn proto_describe_basic() {
4849        assert_eq!(
4850            Command::from_frame(cmd(&["PROTO.DESCRIBE", "myschema"])).unwrap(),
4851            Command::ProtoDescribe {
4852                name: "myschema".into()
4853            },
4854        );
4855    }
4856
4857    #[test]
4858    fn proto_describe_wrong_arity() {
4859        let err = Command::from_frame(cmd(&["PROTO.DESCRIBE"])).unwrap_err();
4860        assert!(matches!(err, ProtocolError::WrongArity(_)));
4861    }
4862
4863    // --- proto.getfield ---
4864
4865    #[test]
4866    fn proto_getfield_basic() {
4867        assert_eq!(
4868            Command::from_frame(cmd(&["PROTO.GETFIELD", "user:1", "name"])).unwrap(),
4869            Command::ProtoGetField {
4870                key: "user:1".into(),
4871                field_path: "name".into(),
4872            },
4873        );
4874    }
4875
4876    #[test]
4877    fn proto_getfield_nested_path() {
4878        assert_eq!(
4879            Command::from_frame(cmd(&["PROTO.GETFIELD", "key", "address.city"])).unwrap(),
4880            Command::ProtoGetField {
4881                key: "key".into(),
4882                field_path: "address.city".into(),
4883            },
4884        );
4885    }
4886
4887    #[test]
4888    fn proto_getfield_wrong_arity() {
4889        let err = Command::from_frame(cmd(&["PROTO.GETFIELD"])).unwrap_err();
4890        assert!(matches!(err, ProtocolError::WrongArity(_)));
4891
4892        let err = Command::from_frame(cmd(&["PROTO.GETFIELD", "key"])).unwrap_err();
4893        assert!(matches!(err, ProtocolError::WrongArity(_)));
4894
4895        let err =
4896            Command::from_frame(cmd(&["PROTO.GETFIELD", "key", "field", "extra"])).unwrap_err();
4897        assert!(matches!(err, ProtocolError::WrongArity(_)));
4898    }
4899
4900    // --- proto.setfield ---
4901
4902    #[test]
4903    fn proto_setfield_basic() {
4904        assert_eq!(
4905            Command::from_frame(cmd(&["PROTO.SETFIELD", "user:1", "name", "bob"])).unwrap(),
4906            Command::ProtoSetField {
4907                key: "user:1".into(),
4908                field_path: "name".into(),
4909                value: "bob".into(),
4910            },
4911        );
4912    }
4913
4914    #[test]
4915    fn proto_setfield_wrong_arity() {
4916        let err = Command::from_frame(cmd(&["PROTO.SETFIELD"])).unwrap_err();
4917        assert!(matches!(err, ProtocolError::WrongArity(_)));
4918
4919        let err = Command::from_frame(cmd(&["PROTO.SETFIELD", "key"])).unwrap_err();
4920        assert!(matches!(err, ProtocolError::WrongArity(_)));
4921
4922        let err = Command::from_frame(cmd(&["PROTO.SETFIELD", "key", "field"])).unwrap_err();
4923        assert!(matches!(err, ProtocolError::WrongArity(_)));
4924
4925        let err = Command::from_frame(cmd(&["PROTO.SETFIELD", "key", "field", "value", "extra"]))
4926            .unwrap_err();
4927        assert!(matches!(err, ProtocolError::WrongArity(_)));
4928    }
4929
4930    // --- proto.delfield ---
4931
4932    #[test]
4933    fn proto_delfield_basic() {
4934        assert_eq!(
4935            Command::from_frame(cmd(&["PROTO.DELFIELD", "user:1", "name"])).unwrap(),
4936            Command::ProtoDelField {
4937                key: "user:1".into(),
4938                field_path: "name".into(),
4939            },
4940        );
4941    }
4942
4943    #[test]
4944    fn proto_delfield_wrong_arity() {
4945        let err = Command::from_frame(cmd(&["PROTO.DELFIELD"])).unwrap_err();
4946        assert!(matches!(err, ProtocolError::WrongArity(_)));
4947
4948        let err = Command::from_frame(cmd(&["PROTO.DELFIELD", "key"])).unwrap_err();
4949        assert!(matches!(err, ProtocolError::WrongArity(_)));
4950
4951        let err =
4952            Command::from_frame(cmd(&["PROTO.DELFIELD", "key", "field", "extra"])).unwrap_err();
4953        assert!(matches!(err, ProtocolError::WrongArity(_)));
4954    }
4955
4956    // --- vector commands ---
4957
4958    #[test]
4959    fn vadd_basic() {
4960        assert_eq!(
4961            Command::from_frame(cmd(&["VADD", "vecs", "elem1", "0.1", "0.2", "0.3"])).unwrap(),
4962            Command::VAdd {
4963                key: "vecs".into(),
4964                element: "elem1".into(),
4965                vector: vec![0.1, 0.2, 0.3],
4966                metric: 0,
4967                quantization: 0,
4968                connectivity: 16,
4969                expansion_add: 64,
4970            },
4971        );
4972    }
4973
4974    #[test]
4975    fn vadd_with_options() {
4976        assert_eq!(
4977            Command::from_frame(cmd(&[
4978                "VADD", "vecs", "elem1", "1.0", "2.0", "METRIC", "L2", "QUANT", "F16", "M", "32",
4979                "EF", "128"
4980            ]))
4981            .unwrap(),
4982            Command::VAdd {
4983                key: "vecs".into(),
4984                element: "elem1".into(),
4985                vector: vec![1.0, 2.0],
4986                metric: 1,
4987                quantization: 1,
4988                connectivity: 32,
4989                expansion_add: 128,
4990            },
4991        );
4992    }
4993
4994    #[test]
4995    fn vadd_wrong_arity() {
4996        // no args
4997        let err = Command::from_frame(cmd(&["VADD"])).unwrap_err();
4998        assert!(matches!(err, ProtocolError::WrongArity(_)));
4999
5000        // key only
5001        let err = Command::from_frame(cmd(&["VADD", "key"])).unwrap_err();
5002        assert!(matches!(err, ProtocolError::WrongArity(_)));
5003
5004        // key + element but no vector — "elem" is not numeric so vector is empty
5005        let err = Command::from_frame(cmd(&["VADD", "key", "elem"])).unwrap_err();
5006        assert!(matches!(
5007            err,
5008            ProtocolError::WrongArity(_) | ProtocolError::InvalidCommandFrame(_)
5009        ));
5010    }
5011
5012    #[test]
5013    fn vadd_m_exceeds_max() {
5014        let err =
5015            Command::from_frame(cmd(&["VADD", "key", "elem", "1.0", "M", "9999"])).unwrap_err();
5016        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
5017    }
5018
5019    #[test]
5020    fn vadd_ef_exceeds_max() {
5021        let err =
5022            Command::from_frame(cmd(&["VADD", "key", "elem", "1.0", "EF", "9999"])).unwrap_err();
5023        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
5024    }
5025
5026    #[test]
5027    fn vadd_unknown_metric() {
5028        let err = Command::from_frame(cmd(&["VADD", "key", "elem", "1.0", "METRIC", "HAMMING"]))
5029            .unwrap_err();
5030        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
5031    }
5032
5033    #[test]
5034    fn vadd_unknown_quantization() {
5035        let err =
5036            Command::from_frame(cmd(&["VADD", "key", "elem", "1.0", "QUANT", "F64"])).unwrap_err();
5037        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
5038    }
5039
5040    // --- vadd_batch ---
5041
5042    #[test]
5043    fn vadd_batch_basic() {
5044        assert_eq!(
5045            Command::from_frame(cmd(&[
5046                "VADD_BATCH",
5047                "vecs",
5048                "DIM",
5049                "3",
5050                "a",
5051                "0.1",
5052                "0.2",
5053                "0.3",
5054                "b",
5055                "0.4",
5056                "0.5",
5057                "0.6"
5058            ]))
5059            .unwrap(),
5060            Command::VAddBatch {
5061                key: "vecs".into(),
5062                entries: vec![
5063                    ("a".into(), vec![0.1, 0.2, 0.3]),
5064                    ("b".into(), vec![0.4, 0.5, 0.6]),
5065                ],
5066                dim: 3,
5067                metric: 0,
5068                quantization: 0,
5069                connectivity: 16,
5070                expansion_add: 64,
5071            },
5072        );
5073    }
5074
5075    #[test]
5076    fn vadd_batch_with_options() {
5077        assert_eq!(
5078            Command::from_frame(cmd(&[
5079                "VADD_BATCH",
5080                "vecs",
5081                "DIM",
5082                "2",
5083                "a",
5084                "1.0",
5085                "2.0",
5086                "METRIC",
5087                "L2",
5088                "QUANT",
5089                "F16",
5090                "M",
5091                "32",
5092                "EF",
5093                "128"
5094            ]))
5095            .unwrap(),
5096            Command::VAddBatch {
5097                key: "vecs".into(),
5098                entries: vec![("a".into(), vec![1.0, 2.0])],
5099                dim: 2,
5100                metric: 1,
5101                quantization: 1,
5102                connectivity: 32,
5103                expansion_add: 128,
5104            },
5105        );
5106    }
5107
5108    #[test]
5109    fn vadd_batch_single_entry() {
5110        assert_eq!(
5111            Command::from_frame(cmd(&["VADD_BATCH", "vecs", "DIM", "1", "x", "3.14"])).unwrap(),
5112            Command::VAddBatch {
5113                key: "vecs".into(),
5114                entries: vec![("x".into(), vec![3.14])],
5115                dim: 1,
5116                metric: 0,
5117                quantization: 0,
5118                connectivity: 16,
5119                expansion_add: 64,
5120            },
5121        );
5122    }
5123
5124    #[test]
5125    fn vadd_batch_empty_entries() {
5126        // key + DIM + n but no entries — valid, returns empty batch
5127        assert_eq!(
5128            Command::from_frame(cmd(&["VADD_BATCH", "vecs", "DIM", "3"])).unwrap(),
5129            Command::VAddBatch {
5130                key: "vecs".into(),
5131                entries: vec![],
5132                dim: 3,
5133                metric: 0,
5134                quantization: 0,
5135                connectivity: 16,
5136                expansion_add: 64,
5137            },
5138        );
5139    }
5140
5141    #[test]
5142    fn vadd_batch_wrong_arity() {
5143        let err = Command::from_frame(cmd(&["VADD_BATCH"])).unwrap_err();
5144        assert!(matches!(err, ProtocolError::WrongArity(_)));
5145
5146        let err = Command::from_frame(cmd(&["VADD_BATCH", "key"])).unwrap_err();
5147        assert!(matches!(err, ProtocolError::WrongArity(_)));
5148
5149        let err = Command::from_frame(cmd(&["VADD_BATCH", "key", "DIM"])).unwrap_err();
5150        assert!(matches!(err, ProtocolError::WrongArity(_)));
5151    }
5152
5153    #[test]
5154    fn vadd_batch_missing_dim_keyword() {
5155        let err = Command::from_frame(cmd(&["VADD_BATCH", "key", "3", "a", "1.0"])).unwrap_err();
5156        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
5157    }
5158
5159    #[test]
5160    fn vadd_batch_dim_zero() {
5161        let err = Command::from_frame(cmd(&["VADD_BATCH", "key", "DIM", "0"])).unwrap_err();
5162        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
5163    }
5164
5165    #[test]
5166    fn vadd_batch_dim_exceeds_max() {
5167        let err = Command::from_frame(cmd(&["VADD_BATCH", "key", "DIM", "99999"])).unwrap_err();
5168        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
5169    }
5170
5171    #[test]
5172    fn vadd_batch_insufficient_floats() {
5173        // DIM=3 but only 2 floats for element "a"
5174        let err = Command::from_frame(cmd(&["VADD_BATCH", "key", "DIM", "3", "a", "1.0", "2.0"]))
5175            .unwrap_err();
5176        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
5177    }
5178
5179    #[test]
5180    fn vadd_batch_m_exceeds_max() {
5181        let err = Command::from_frame(cmd(&[
5182            "VADD_BATCH",
5183            "key",
5184            "DIM",
5185            "2",
5186            "a",
5187            "1.0",
5188            "2.0",
5189            "M",
5190            "9999",
5191        ]))
5192        .unwrap_err();
5193        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
5194    }
5195
5196    #[test]
5197    fn vadd_batch_ef_exceeds_max() {
5198        let err = Command::from_frame(cmd(&[
5199            "VADD_BATCH",
5200            "key",
5201            "DIM",
5202            "2",
5203            "a",
5204            "1.0",
5205            "2.0",
5206            "EF",
5207            "9999",
5208        ]))
5209        .unwrap_err();
5210        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
5211    }
5212
5213    #[test]
5214    fn vsim_basic() {
5215        assert_eq!(
5216            Command::from_frame(cmd(&["VSIM", "vecs", "0.1", "0.2", "0.3", "COUNT", "5"])).unwrap(),
5217            Command::VSim {
5218                key: "vecs".into(),
5219                query: vec![0.1, 0.2, 0.3],
5220                count: 5,
5221                ef_search: 0,
5222                with_scores: false,
5223            },
5224        );
5225    }
5226
5227    #[test]
5228    fn vsim_with_ef_and_scores() {
5229        assert_eq!(
5230            Command::from_frame(cmd(&[
5231                "VSIM",
5232                "vecs",
5233                "1.0",
5234                "2.0",
5235                "COUNT",
5236                "10",
5237                "EF",
5238                "128",
5239                "WITHSCORES"
5240            ]))
5241            .unwrap(),
5242            Command::VSim {
5243                key: "vecs".into(),
5244                query: vec![1.0, 2.0],
5245                count: 10,
5246                ef_search: 128,
5247                with_scores: true,
5248            },
5249        );
5250    }
5251
5252    #[test]
5253    fn vsim_missing_count() {
5254        // all args are numeric so they're consumed as query — COUNT is never found
5255        let err = Command::from_frame(cmd(&["VSIM", "key", "1.0", "2.0"])).unwrap_err();
5256        assert!(matches!(
5257            err,
5258            ProtocolError::InvalidCommandFrame(_) | ProtocolError::WrongArity(_)
5259        ));
5260    }
5261
5262    #[test]
5263    fn vsim_wrong_arity() {
5264        let err = Command::from_frame(cmd(&["VSIM"])).unwrap_err();
5265        assert!(matches!(err, ProtocolError::WrongArity(_)));
5266    }
5267
5268    #[test]
5269    fn vsim_count_exceeds_max() {
5270        let err = Command::from_frame(cmd(&["VSIM", "key", "1.0", "COUNT", "99999"])).unwrap_err();
5271        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
5272    }
5273
5274    #[test]
5275    fn vsim_ef_exceeds_max() {
5276        let err = Command::from_frame(cmd(&["VSIM", "key", "1.0", "COUNT", "5", "EF", "9999"]))
5277            .unwrap_err();
5278        assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
5279    }
5280
5281    #[test]
5282    fn vrem_basic() {
5283        assert_eq!(
5284            Command::from_frame(cmd(&["VREM", "key", "elem"])).unwrap(),
5285            Command::VRem {
5286                key: "key".into(),
5287                element: "elem".into(),
5288            },
5289        );
5290    }
5291
5292    #[test]
5293    fn vrem_wrong_arity() {
5294        let err = Command::from_frame(cmd(&["VREM"])).unwrap_err();
5295        assert!(matches!(err, ProtocolError::WrongArity(_)));
5296
5297        let err = Command::from_frame(cmd(&["VREM", "key"])).unwrap_err();
5298        assert!(matches!(err, ProtocolError::WrongArity(_)));
5299    }
5300
5301    #[test]
5302    fn vget_basic() {
5303        assert_eq!(
5304            Command::from_frame(cmd(&["VGET", "key", "elem"])).unwrap(),
5305            Command::VGet {
5306                key: "key".into(),
5307                element: "elem".into(),
5308            },
5309        );
5310    }
5311
5312    #[test]
5313    fn vget_wrong_arity() {
5314        let err = Command::from_frame(cmd(&["VGET"])).unwrap_err();
5315        assert!(matches!(err, ProtocolError::WrongArity(_)));
5316    }
5317
5318    #[test]
5319    fn vcard_basic() {
5320        assert_eq!(
5321            Command::from_frame(cmd(&["VCARD", "key"])).unwrap(),
5322            Command::VCard { key: "key".into() },
5323        );
5324    }
5325
5326    #[test]
5327    fn vcard_wrong_arity() {
5328        let err = Command::from_frame(cmd(&["VCARD"])).unwrap_err();
5329        assert!(matches!(err, ProtocolError::WrongArity(_)));
5330    }
5331
5332    #[test]
5333    fn vdim_basic() {
5334        assert_eq!(
5335            Command::from_frame(cmd(&["VDIM", "key"])).unwrap(),
5336            Command::VDim { key: "key".into() },
5337        );
5338    }
5339
5340    #[test]
5341    fn vdim_wrong_arity() {
5342        let err = Command::from_frame(cmd(&["VDIM"])).unwrap_err();
5343        assert!(matches!(err, ProtocolError::WrongArity(_)));
5344    }
5345
5346    #[test]
5347    fn vinfo_basic() {
5348        assert_eq!(
5349            Command::from_frame(cmd(&["VINFO", "key"])).unwrap(),
5350            Command::VInfo { key: "key".into() },
5351        );
5352    }
5353
5354    #[test]
5355    fn vinfo_wrong_arity() {
5356        let err = Command::from_frame(cmd(&["VINFO"])).unwrap_err();
5357        assert!(matches!(err, ProtocolError::WrongArity(_)));
5358    }
5359}