1use bytes::Bytes;
8
9use crate::error::ProtocolError;
10use crate::types::Frame;
11
12#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum SetExpire {
15 Ex(u64),
17 Px(u64),
19}
20
21#[derive(Debug, Clone, PartialEq)]
23pub enum Command {
24 Ping(Option<Bytes>),
26
27 Echo(Bytes),
29
30 Get { key: String },
32
33 Set {
35 key: String,
36 value: Bytes,
37 expire: Option<SetExpire>,
38 nx: bool,
40 xx: bool,
42 },
43
44 Incr { key: String },
46
47 Decr { key: String },
49
50 Del { keys: Vec<String> },
52
53 Exists { keys: Vec<String> },
55
56 MGet { keys: Vec<String> },
58
59 MSet { pairs: Vec<(String, Bytes)> },
61
62 Expire { key: String, seconds: u64 },
64
65 Ttl { key: String },
67
68 Persist { key: String },
70
71 Pttl { key: String },
73
74 Pexpire { key: String, milliseconds: u64 },
76
77 DbSize,
79
80 Info { section: Option<String> },
82
83 BgSave,
85
86 BgRewriteAof,
88
89 FlushDb,
91
92 Scan {
94 cursor: u64,
95 pattern: Option<String>,
96 count: Option<usize>,
97 },
98
99 LPush { key: String, values: Vec<Bytes> },
101
102 RPush { key: String, values: Vec<Bytes> },
104
105 LPop { key: String },
107
108 RPop { key: String },
110
111 LRange { key: String, start: i64, stop: i64 },
113
114 LLen { key: String },
116
117 Type { key: String },
119
120 ZAdd {
122 key: String,
123 flags: ZAddFlags,
124 members: Vec<(f64, String)>,
125 },
126
127 ZRem { key: String, members: Vec<String> },
129
130 ZScore { key: String, member: String },
132
133 ZRank { key: String, member: String },
135
136 ZCard { key: String },
138
139 ZRange {
141 key: String,
142 start: i64,
143 stop: i64,
144 with_scores: bool,
145 },
146
147 Unknown(String),
149}
150
151#[derive(Debug, Clone, Default, PartialEq)]
153pub struct ZAddFlags {
154 pub nx: bool,
156 pub xx: bool,
158 pub gt: bool,
160 pub lt: bool,
162 pub ch: bool,
164}
165
166impl Eq for ZAddFlags {}
167
168impl Command {
169 pub fn from_frame(frame: Frame) -> Result<Command, ProtocolError> {
174 let frames = match frame {
175 Frame::Array(frames) => frames,
176 _ => {
177 return Err(ProtocolError::InvalidCommandFrame(
178 "expected array frame".into(),
179 ));
180 }
181 };
182
183 if frames.is_empty() {
184 return Err(ProtocolError::InvalidCommandFrame(
185 "empty command array".into(),
186 ));
187 }
188
189 let name = extract_string(&frames[0])?;
190 let name_upper = name.to_ascii_uppercase();
191
192 match name_upper.as_str() {
193 "PING" => parse_ping(&frames[1..]),
194 "ECHO" => parse_echo(&frames[1..]),
195 "GET" => parse_get(&frames[1..]),
196 "SET" => parse_set(&frames[1..]),
197 "INCR" => parse_incr(&frames[1..]),
198 "DECR" => parse_decr(&frames[1..]),
199 "DEL" => parse_del(&frames[1..]),
200 "EXISTS" => parse_exists(&frames[1..]),
201 "MGET" => parse_mget(&frames[1..]),
202 "MSET" => parse_mset(&frames[1..]),
203 "EXPIRE" => parse_expire(&frames[1..]),
204 "TTL" => parse_ttl(&frames[1..]),
205 "PERSIST" => parse_persist(&frames[1..]),
206 "PTTL" => parse_pttl(&frames[1..]),
207 "PEXPIRE" => parse_pexpire(&frames[1..]),
208 "DBSIZE" => parse_dbsize(&frames[1..]),
209 "INFO" => parse_info(&frames[1..]),
210 "BGSAVE" => parse_bgsave(&frames[1..]),
211 "BGREWRITEAOF" => parse_bgrewriteaof(&frames[1..]),
212 "FLUSHDB" => parse_flushdb(&frames[1..]),
213 "SCAN" => parse_scan(&frames[1..]),
214 "LPUSH" => parse_lpush(&frames[1..]),
215 "RPUSH" => parse_rpush(&frames[1..]),
216 "LPOP" => parse_lpop(&frames[1..]),
217 "RPOP" => parse_rpop(&frames[1..]),
218 "LRANGE" => parse_lrange(&frames[1..]),
219 "LLEN" => parse_llen(&frames[1..]),
220 "TYPE" => parse_type(&frames[1..]),
221 "ZADD" => parse_zadd(&frames[1..]),
222 "ZREM" => parse_zrem(&frames[1..]),
223 "ZSCORE" => parse_zscore(&frames[1..]),
224 "ZRANK" => parse_zrank(&frames[1..]),
225 "ZCARD" => parse_zcard(&frames[1..]),
226 "ZRANGE" => parse_zrange(&frames[1..]),
227 _ => Ok(Command::Unknown(name)),
228 }
229 }
230}
231
232fn extract_string(frame: &Frame) -> Result<String, ProtocolError> {
234 match frame {
235 Frame::Bulk(data) => String::from_utf8(data.to_vec()).map_err(|_| {
236 ProtocolError::InvalidCommandFrame("command name is not valid utf-8".into())
237 }),
238 Frame::Simple(s) => Ok(s.clone()),
239 _ => Err(ProtocolError::InvalidCommandFrame(
240 "expected bulk or simple string for command name".into(),
241 )),
242 }
243}
244
245fn extract_bytes(frame: &Frame) -> Result<Bytes, ProtocolError> {
247 match frame {
248 Frame::Bulk(data) => Ok(data.clone()),
249 Frame::Simple(s) => Ok(Bytes::from(s.clone().into_bytes())),
250 _ => Err(ProtocolError::InvalidCommandFrame(
251 "expected bulk or simple string argument".into(),
252 )),
253 }
254}
255
256fn parse_u64(frame: &Frame, cmd: &str) -> Result<u64, ProtocolError> {
258 let s = extract_string(frame)?;
259 s.parse::<u64>().map_err(|_| {
260 ProtocolError::InvalidCommandFrame(format!("value is not a valid integer for '{cmd}'"))
261 })
262}
263
264fn parse_ping(args: &[Frame]) -> Result<Command, ProtocolError> {
265 match args.len() {
266 0 => Ok(Command::Ping(None)),
267 1 => {
268 let msg = extract_bytes(&args[0])?;
269 Ok(Command::Ping(Some(msg)))
270 }
271 _ => Err(ProtocolError::WrongArity("PING".into())),
272 }
273}
274
275fn parse_echo(args: &[Frame]) -> Result<Command, ProtocolError> {
276 if args.len() != 1 {
277 return Err(ProtocolError::WrongArity("ECHO".into()));
278 }
279 let msg = extract_bytes(&args[0])?;
280 Ok(Command::Echo(msg))
281}
282
283fn parse_get(args: &[Frame]) -> Result<Command, ProtocolError> {
284 if args.len() != 1 {
285 return Err(ProtocolError::WrongArity("GET".into()));
286 }
287 let key = extract_string(&args[0])?;
288 Ok(Command::Get { key })
289}
290
291fn parse_set(args: &[Frame]) -> Result<Command, ProtocolError> {
292 if args.len() < 2 {
293 return Err(ProtocolError::WrongArity("SET".into()));
294 }
295
296 let key = extract_string(&args[0])?;
297 let value = extract_bytes(&args[1])?;
298
299 let mut expire = None;
300 let mut nx = false;
301 let mut xx = false;
302 let mut idx = 2;
303
304 while idx < args.len() {
305 let flag = extract_string(&args[idx])?.to_ascii_uppercase();
306 match flag.as_str() {
307 "NX" => {
308 nx = true;
309 idx += 1;
310 }
311 "XX" => {
312 xx = true;
313 idx += 1;
314 }
315 "EX" => {
316 idx += 1;
317 if idx >= args.len() {
318 return Err(ProtocolError::WrongArity("SET".into()));
319 }
320 let amount = parse_u64(&args[idx], "SET")?;
321 if amount == 0 {
322 return Err(ProtocolError::InvalidCommandFrame(
323 "invalid expire time in 'SET' command".into(),
324 ));
325 }
326 expire = Some(SetExpire::Ex(amount));
327 idx += 1;
328 }
329 "PX" => {
330 idx += 1;
331 if idx >= args.len() {
332 return Err(ProtocolError::WrongArity("SET".into()));
333 }
334 let amount = parse_u64(&args[idx], "SET")?;
335 if amount == 0 {
336 return Err(ProtocolError::InvalidCommandFrame(
337 "invalid expire time in 'SET' command".into(),
338 ));
339 }
340 expire = Some(SetExpire::Px(amount));
341 idx += 1;
342 }
343 _ => {
344 return Err(ProtocolError::InvalidCommandFrame(format!(
345 "unsupported SET option '{flag}'"
346 )));
347 }
348 }
349 }
350
351 if nx && xx {
352 return Err(ProtocolError::InvalidCommandFrame(
353 "XX and NX options at the same time are not compatible".into(),
354 ));
355 }
356
357 Ok(Command::Set {
358 key,
359 value,
360 expire,
361 nx,
362 xx,
363 })
364}
365
366fn parse_incr(args: &[Frame]) -> Result<Command, ProtocolError> {
367 if args.len() != 1 {
368 return Err(ProtocolError::WrongArity("INCR".into()));
369 }
370 let key = extract_string(&args[0])?;
371 Ok(Command::Incr { key })
372}
373
374fn parse_decr(args: &[Frame]) -> Result<Command, ProtocolError> {
375 if args.len() != 1 {
376 return Err(ProtocolError::WrongArity("DECR".into()));
377 }
378 let key = extract_string(&args[0])?;
379 Ok(Command::Decr { key })
380}
381
382fn parse_del(args: &[Frame]) -> Result<Command, ProtocolError> {
383 if args.is_empty() {
384 return Err(ProtocolError::WrongArity("DEL".into()));
385 }
386 let keys = args
387 .iter()
388 .map(extract_string)
389 .collect::<Result<Vec<_>, _>>()?;
390 Ok(Command::Del { keys })
391}
392
393fn parse_exists(args: &[Frame]) -> Result<Command, ProtocolError> {
394 if args.is_empty() {
395 return Err(ProtocolError::WrongArity("EXISTS".into()));
396 }
397 let keys = args
398 .iter()
399 .map(extract_string)
400 .collect::<Result<Vec<_>, _>>()?;
401 Ok(Command::Exists { keys })
402}
403
404fn parse_mget(args: &[Frame]) -> Result<Command, ProtocolError> {
405 if args.is_empty() {
406 return Err(ProtocolError::WrongArity("MGET".into()));
407 }
408 let keys = args
409 .iter()
410 .map(extract_string)
411 .collect::<Result<Vec<_>, _>>()?;
412 Ok(Command::MGet { keys })
413}
414
415fn parse_mset(args: &[Frame]) -> Result<Command, ProtocolError> {
416 if args.is_empty() || !args.len().is_multiple_of(2) {
417 return Err(ProtocolError::WrongArity("MSET".into()));
418 }
419 let mut pairs = Vec::with_capacity(args.len() / 2);
420 for chunk in args.chunks(2) {
421 let key = extract_string(&chunk[0])?;
422 let value = extract_bytes(&chunk[1])?;
423 pairs.push((key, value));
424 }
425 Ok(Command::MSet { pairs })
426}
427
428fn parse_expire(args: &[Frame]) -> Result<Command, ProtocolError> {
429 if args.len() != 2 {
430 return Err(ProtocolError::WrongArity("EXPIRE".into()));
431 }
432 let key = extract_string(&args[0])?;
433 let seconds = parse_u64(&args[1], "EXPIRE")?;
434
435 if seconds == 0 {
436 return Err(ProtocolError::InvalidCommandFrame(
437 "invalid expire time in 'EXPIRE' command".into(),
438 ));
439 }
440
441 Ok(Command::Expire { key, seconds })
442}
443
444fn parse_ttl(args: &[Frame]) -> Result<Command, ProtocolError> {
445 if args.len() != 1 {
446 return Err(ProtocolError::WrongArity("TTL".into()));
447 }
448 let key = extract_string(&args[0])?;
449 Ok(Command::Ttl { key })
450}
451
452fn parse_persist(args: &[Frame]) -> Result<Command, ProtocolError> {
453 if args.len() != 1 {
454 return Err(ProtocolError::WrongArity("PERSIST".into()));
455 }
456 let key = extract_string(&args[0])?;
457 Ok(Command::Persist { key })
458}
459
460fn parse_pttl(args: &[Frame]) -> Result<Command, ProtocolError> {
461 if args.len() != 1 {
462 return Err(ProtocolError::WrongArity("PTTL".into()));
463 }
464 let key = extract_string(&args[0])?;
465 Ok(Command::Pttl { key })
466}
467
468fn parse_pexpire(args: &[Frame]) -> Result<Command, ProtocolError> {
469 if args.len() != 2 {
470 return Err(ProtocolError::WrongArity("PEXPIRE".into()));
471 }
472 let key = extract_string(&args[0])?;
473 let milliseconds = parse_u64(&args[1], "PEXPIRE")?;
474
475 if milliseconds == 0 {
476 return Err(ProtocolError::InvalidCommandFrame(
477 "invalid expire time in 'PEXPIRE' command".into(),
478 ));
479 }
480
481 Ok(Command::Pexpire { key, milliseconds })
482}
483
484fn parse_dbsize(args: &[Frame]) -> Result<Command, ProtocolError> {
485 if !args.is_empty() {
486 return Err(ProtocolError::WrongArity("DBSIZE".into()));
487 }
488 Ok(Command::DbSize)
489}
490
491fn parse_info(args: &[Frame]) -> Result<Command, ProtocolError> {
492 match args.len() {
493 0 => Ok(Command::Info { section: None }),
494 1 => {
495 let section = extract_string(&args[0])?;
496 Ok(Command::Info {
497 section: Some(section),
498 })
499 }
500 _ => Err(ProtocolError::WrongArity("INFO".into())),
501 }
502}
503
504fn parse_bgsave(args: &[Frame]) -> Result<Command, ProtocolError> {
505 if !args.is_empty() {
506 return Err(ProtocolError::WrongArity("BGSAVE".into()));
507 }
508 Ok(Command::BgSave)
509}
510
511fn parse_bgrewriteaof(args: &[Frame]) -> Result<Command, ProtocolError> {
512 if !args.is_empty() {
513 return Err(ProtocolError::WrongArity("BGREWRITEAOF".into()));
514 }
515 Ok(Command::BgRewriteAof)
516}
517
518fn parse_flushdb(args: &[Frame]) -> Result<Command, ProtocolError> {
519 if !args.is_empty() {
520 return Err(ProtocolError::WrongArity("FLUSHDB".into()));
521 }
522 Ok(Command::FlushDb)
523}
524
525fn parse_scan(args: &[Frame]) -> Result<Command, ProtocolError> {
526 if args.is_empty() {
527 return Err(ProtocolError::WrongArity("SCAN".into()));
528 }
529
530 let cursor = parse_u64(&args[0], "SCAN")?;
531 let mut pattern = None;
532 let mut count = None;
533 let mut idx = 1;
534
535 while idx < args.len() {
536 let flag = extract_string(&args[idx])?.to_ascii_uppercase();
537 match flag.as_str() {
538 "MATCH" => {
539 idx += 1;
540 if idx >= args.len() {
541 return Err(ProtocolError::WrongArity("SCAN".into()));
542 }
543 pattern = Some(extract_string(&args[idx])?);
544 idx += 1;
545 }
546 "COUNT" => {
547 idx += 1;
548 if idx >= args.len() {
549 return Err(ProtocolError::WrongArity("SCAN".into()));
550 }
551 let n = parse_u64(&args[idx], "SCAN")?;
552 count = Some(n as usize);
553 idx += 1;
554 }
555 _ => {
556 return Err(ProtocolError::InvalidCommandFrame(format!(
557 "unsupported SCAN option '{flag}'"
558 )));
559 }
560 }
561 }
562
563 Ok(Command::Scan {
564 cursor,
565 pattern,
566 count,
567 })
568}
569
570fn parse_i64(frame: &Frame, cmd: &str) -> Result<i64, ProtocolError> {
572 let s = extract_string(frame)?;
573 s.parse::<i64>().map_err(|_| {
574 ProtocolError::InvalidCommandFrame(format!("value is not a valid integer for '{cmd}'"))
575 })
576}
577
578fn parse_lpush(args: &[Frame]) -> Result<Command, ProtocolError> {
579 if args.len() < 2 {
580 return Err(ProtocolError::WrongArity("LPUSH".into()));
581 }
582 let key = extract_string(&args[0])?;
583 let values = args[1..]
584 .iter()
585 .map(extract_bytes)
586 .collect::<Result<Vec<_>, _>>()?;
587 Ok(Command::LPush { key, values })
588}
589
590fn parse_rpush(args: &[Frame]) -> Result<Command, ProtocolError> {
591 if args.len() < 2 {
592 return Err(ProtocolError::WrongArity("RPUSH".into()));
593 }
594 let key = extract_string(&args[0])?;
595 let values = args[1..]
596 .iter()
597 .map(extract_bytes)
598 .collect::<Result<Vec<_>, _>>()?;
599 Ok(Command::RPush { key, values })
600}
601
602fn parse_lpop(args: &[Frame]) -> Result<Command, ProtocolError> {
603 if args.len() != 1 {
604 return Err(ProtocolError::WrongArity("LPOP".into()));
605 }
606 let key = extract_string(&args[0])?;
607 Ok(Command::LPop { key })
608}
609
610fn parse_rpop(args: &[Frame]) -> Result<Command, ProtocolError> {
611 if args.len() != 1 {
612 return Err(ProtocolError::WrongArity("RPOP".into()));
613 }
614 let key = extract_string(&args[0])?;
615 Ok(Command::RPop { key })
616}
617
618fn parse_lrange(args: &[Frame]) -> Result<Command, ProtocolError> {
619 if args.len() != 3 {
620 return Err(ProtocolError::WrongArity("LRANGE".into()));
621 }
622 let key = extract_string(&args[0])?;
623 let start = parse_i64(&args[1], "LRANGE")?;
624 let stop = parse_i64(&args[2], "LRANGE")?;
625 Ok(Command::LRange { key, start, stop })
626}
627
628fn parse_llen(args: &[Frame]) -> Result<Command, ProtocolError> {
629 if args.len() != 1 {
630 return Err(ProtocolError::WrongArity("LLEN".into()));
631 }
632 let key = extract_string(&args[0])?;
633 Ok(Command::LLen { key })
634}
635
636fn parse_type(args: &[Frame]) -> Result<Command, ProtocolError> {
637 if args.len() != 1 {
638 return Err(ProtocolError::WrongArity("TYPE".into()));
639 }
640 let key = extract_string(&args[0])?;
641 Ok(Command::Type { key })
642}
643
644fn parse_f64(frame: &Frame, cmd: &str) -> Result<f64, ProtocolError> {
646 let s = extract_string(frame)?;
647 let v = s.parse::<f64>().map_err(|_| {
648 ProtocolError::InvalidCommandFrame(format!("value is not a valid float for '{cmd}'"))
649 })?;
650 if v.is_nan() {
651 return Err(ProtocolError::InvalidCommandFrame(format!(
652 "NaN is not a valid score for '{cmd}'"
653 )));
654 }
655 Ok(v)
656}
657
658fn parse_zadd(args: &[Frame]) -> Result<Command, ProtocolError> {
659 if args.len() < 3 {
661 return Err(ProtocolError::WrongArity("ZADD".into()));
662 }
663
664 let key = extract_string(&args[0])?;
665 let mut flags = ZAddFlags::default();
666 let mut idx = 1;
667
668 while idx < args.len() {
670 let s = extract_string(&args[idx])?.to_ascii_uppercase();
671 match s.as_str() {
672 "NX" => {
673 flags.nx = true;
674 idx += 1;
675 }
676 "XX" => {
677 flags.xx = true;
678 idx += 1;
679 }
680 "GT" => {
681 flags.gt = true;
682 idx += 1;
683 }
684 "LT" => {
685 flags.lt = true;
686 idx += 1;
687 }
688 "CH" => {
689 flags.ch = true;
690 idx += 1;
691 }
692 _ => break,
693 }
694 }
695
696 if flags.nx && flags.xx {
698 return Err(ProtocolError::InvalidCommandFrame(
699 "XX and NX options at the same time are not compatible".into(),
700 ));
701 }
702 if flags.gt && flags.lt {
704 return Err(ProtocolError::InvalidCommandFrame(
705 "GT and LT options at the same time are not compatible".into(),
706 ));
707 }
708
709 let remaining = &args[idx..];
711 if remaining.is_empty() || !remaining.len().is_multiple_of(2) {
712 return Err(ProtocolError::WrongArity("ZADD".into()));
713 }
714
715 let mut members = Vec::with_capacity(remaining.len() / 2);
716 for pair in remaining.chunks(2) {
717 let score = parse_f64(&pair[0], "ZADD")?;
718 let member = extract_string(&pair[1])?;
719 members.push((score, member));
720 }
721
722 Ok(Command::ZAdd {
723 key,
724 flags,
725 members,
726 })
727}
728
729fn parse_zcard(args: &[Frame]) -> Result<Command, ProtocolError> {
730 if args.len() != 1 {
731 return Err(ProtocolError::WrongArity("ZCARD".into()));
732 }
733 let key = extract_string(&args[0])?;
734 Ok(Command::ZCard { key })
735}
736
737fn parse_zrem(args: &[Frame]) -> Result<Command, ProtocolError> {
738 if args.len() < 2 {
739 return Err(ProtocolError::WrongArity("ZREM".into()));
740 }
741 let key = extract_string(&args[0])?;
742 let members = args[1..]
743 .iter()
744 .map(extract_string)
745 .collect::<Result<Vec<_>, _>>()?;
746 Ok(Command::ZRem { key, members })
747}
748
749fn parse_zscore(args: &[Frame]) -> Result<Command, ProtocolError> {
750 if args.len() != 2 {
751 return Err(ProtocolError::WrongArity("ZSCORE".into()));
752 }
753 let key = extract_string(&args[0])?;
754 let member = extract_string(&args[1])?;
755 Ok(Command::ZScore { key, member })
756}
757
758fn parse_zrank(args: &[Frame]) -> Result<Command, ProtocolError> {
759 if args.len() != 2 {
760 return Err(ProtocolError::WrongArity("ZRANK".into()));
761 }
762 let key = extract_string(&args[0])?;
763 let member = extract_string(&args[1])?;
764 Ok(Command::ZRank { key, member })
765}
766
767fn parse_zrange(args: &[Frame]) -> Result<Command, ProtocolError> {
768 if args.len() < 3 || args.len() > 4 {
769 return Err(ProtocolError::WrongArity("ZRANGE".into()));
770 }
771 let key = extract_string(&args[0])?;
772 let start = parse_i64(&args[1], "ZRANGE")?;
773 let stop = parse_i64(&args[2], "ZRANGE")?;
774
775 let with_scores = if args.len() == 4 {
776 let opt = extract_string(&args[3])?.to_ascii_uppercase();
777 if opt != "WITHSCORES" {
778 return Err(ProtocolError::InvalidCommandFrame(format!(
779 "unsupported ZRANGE option '{opt}'"
780 )));
781 }
782 true
783 } else {
784 false
785 };
786
787 Ok(Command::ZRange {
788 key,
789 start,
790 stop,
791 with_scores,
792 })
793}
794
795#[cfg(test)]
796mod tests {
797 use super::*;
798
799 fn cmd(parts: &[&str]) -> Frame {
801 Frame::Array(
802 parts
803 .iter()
804 .map(|s| Frame::Bulk(Bytes::from(s.to_string())))
805 .collect(),
806 )
807 }
808
809 #[test]
812 fn ping_no_args() {
813 assert_eq!(
814 Command::from_frame(cmd(&["PING"])).unwrap(),
815 Command::Ping(None),
816 );
817 }
818
819 #[test]
820 fn ping_with_message() {
821 assert_eq!(
822 Command::from_frame(cmd(&["PING", "hello"])).unwrap(),
823 Command::Ping(Some(Bytes::from("hello"))),
824 );
825 }
826
827 #[test]
828 fn ping_case_insensitive() {
829 assert_eq!(
830 Command::from_frame(cmd(&["ping"])).unwrap(),
831 Command::Ping(None),
832 );
833 assert_eq!(
834 Command::from_frame(cmd(&["Ping"])).unwrap(),
835 Command::Ping(None),
836 );
837 }
838
839 #[test]
840 fn ping_too_many_args() {
841 let err = Command::from_frame(cmd(&["PING", "a", "b"])).unwrap_err();
842 assert!(matches!(err, ProtocolError::WrongArity(_)));
843 }
844
845 #[test]
848 fn echo() {
849 assert_eq!(
850 Command::from_frame(cmd(&["ECHO", "test"])).unwrap(),
851 Command::Echo(Bytes::from("test")),
852 );
853 }
854
855 #[test]
856 fn echo_missing_arg() {
857 let err = Command::from_frame(cmd(&["ECHO"])).unwrap_err();
858 assert!(matches!(err, ProtocolError::WrongArity(_)));
859 }
860
861 #[test]
864 fn get_basic() {
865 assert_eq!(
866 Command::from_frame(cmd(&["GET", "mykey"])).unwrap(),
867 Command::Get {
868 key: "mykey".into()
869 },
870 );
871 }
872
873 #[test]
874 fn get_no_args() {
875 let err = Command::from_frame(cmd(&["GET"])).unwrap_err();
876 assert!(matches!(err, ProtocolError::WrongArity(_)));
877 }
878
879 #[test]
880 fn get_too_many_args() {
881 let err = Command::from_frame(cmd(&["GET", "a", "b"])).unwrap_err();
882 assert!(matches!(err, ProtocolError::WrongArity(_)));
883 }
884
885 #[test]
886 fn get_case_insensitive() {
887 assert_eq!(
888 Command::from_frame(cmd(&["get", "k"])).unwrap(),
889 Command::Get { key: "k".into() },
890 );
891 }
892
893 #[test]
896 fn set_basic() {
897 assert_eq!(
898 Command::from_frame(cmd(&["SET", "key", "value"])).unwrap(),
899 Command::Set {
900 key: "key".into(),
901 value: Bytes::from("value"),
902 expire: None,
903 nx: false,
904 xx: false,
905 },
906 );
907 }
908
909 #[test]
910 fn set_with_ex() {
911 assert_eq!(
912 Command::from_frame(cmd(&["SET", "key", "val", "EX", "10"])).unwrap(),
913 Command::Set {
914 key: "key".into(),
915 value: Bytes::from("val"),
916 expire: Some(SetExpire::Ex(10)),
917 nx: false,
918 xx: false,
919 },
920 );
921 }
922
923 #[test]
924 fn set_with_px() {
925 assert_eq!(
926 Command::from_frame(cmd(&["SET", "key", "val", "PX", "5000"])).unwrap(),
927 Command::Set {
928 key: "key".into(),
929 value: Bytes::from("val"),
930 expire: Some(SetExpire::Px(5000)),
931 nx: false,
932 xx: false,
933 },
934 );
935 }
936
937 #[test]
938 fn set_ex_case_insensitive() {
939 assert_eq!(
940 Command::from_frame(cmd(&["set", "k", "v", "ex", "5"])).unwrap(),
941 Command::Set {
942 key: "k".into(),
943 value: Bytes::from("v"),
944 expire: Some(SetExpire::Ex(5)),
945 nx: false,
946 xx: false,
947 },
948 );
949 }
950
951 #[test]
952 fn set_nx_flag() {
953 assert_eq!(
954 Command::from_frame(cmd(&["SET", "key", "val", "NX"])).unwrap(),
955 Command::Set {
956 key: "key".into(),
957 value: Bytes::from("val"),
958 expire: None,
959 nx: true,
960 xx: false,
961 },
962 );
963 }
964
965 #[test]
966 fn set_xx_flag() {
967 assert_eq!(
968 Command::from_frame(cmd(&["SET", "key", "val", "XX"])).unwrap(),
969 Command::Set {
970 key: "key".into(),
971 value: Bytes::from("val"),
972 expire: None,
973 nx: false,
974 xx: true,
975 },
976 );
977 }
978
979 #[test]
980 fn set_nx_with_ex() {
981 assert_eq!(
982 Command::from_frame(cmd(&["SET", "key", "val", "EX", "10", "NX"])).unwrap(),
983 Command::Set {
984 key: "key".into(),
985 value: Bytes::from("val"),
986 expire: Some(SetExpire::Ex(10)),
987 nx: true,
988 xx: false,
989 },
990 );
991 }
992
993 #[test]
994 fn set_nx_before_ex() {
995 assert_eq!(
996 Command::from_frame(cmd(&["SET", "key", "val", "NX", "PX", "5000"])).unwrap(),
997 Command::Set {
998 key: "key".into(),
999 value: Bytes::from("val"),
1000 expire: Some(SetExpire::Px(5000)),
1001 nx: true,
1002 xx: false,
1003 },
1004 );
1005 }
1006
1007 #[test]
1008 fn set_nx_xx_conflict() {
1009 let err = Command::from_frame(cmd(&["SET", "k", "v", "NX", "XX"])).unwrap_err();
1010 assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
1011 }
1012
1013 #[test]
1014 fn set_nx_case_insensitive() {
1015 assert_eq!(
1016 Command::from_frame(cmd(&["set", "k", "v", "nx"])).unwrap(),
1017 Command::Set {
1018 key: "k".into(),
1019 value: Bytes::from("v"),
1020 expire: None,
1021 nx: true,
1022 xx: false,
1023 },
1024 );
1025 }
1026
1027 #[test]
1028 fn set_missing_value() {
1029 let err = Command::from_frame(cmd(&["SET", "key"])).unwrap_err();
1030 assert!(matches!(err, ProtocolError::WrongArity(_)));
1031 }
1032
1033 #[test]
1034 fn set_invalid_expire_value() {
1035 let err = Command::from_frame(cmd(&["SET", "k", "v", "EX", "notanum"])).unwrap_err();
1036 assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
1037 }
1038
1039 #[test]
1040 fn set_zero_expire() {
1041 let err = Command::from_frame(cmd(&["SET", "k", "v", "EX", "0"])).unwrap_err();
1042 assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
1043 }
1044
1045 #[test]
1046 fn set_unknown_flag() {
1047 let err = Command::from_frame(cmd(&["SET", "k", "v", "ZZ", "10"])).unwrap_err();
1048 assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
1049 }
1050
1051 #[test]
1052 fn set_incomplete_expire() {
1053 let err = Command::from_frame(cmd(&["SET", "k", "v", "EX"])).unwrap_err();
1055 assert!(matches!(err, ProtocolError::WrongArity(_)));
1056 }
1057
1058 #[test]
1061 fn del_single() {
1062 assert_eq!(
1063 Command::from_frame(cmd(&["DEL", "key"])).unwrap(),
1064 Command::Del {
1065 keys: vec!["key".into()]
1066 },
1067 );
1068 }
1069
1070 #[test]
1071 fn del_multiple() {
1072 assert_eq!(
1073 Command::from_frame(cmd(&["DEL", "a", "b", "c"])).unwrap(),
1074 Command::Del {
1075 keys: vec!["a".into(), "b".into(), "c".into()]
1076 },
1077 );
1078 }
1079
1080 #[test]
1081 fn del_no_args() {
1082 let err = Command::from_frame(cmd(&["DEL"])).unwrap_err();
1083 assert!(matches!(err, ProtocolError::WrongArity(_)));
1084 }
1085
1086 #[test]
1089 fn exists_single() {
1090 assert_eq!(
1091 Command::from_frame(cmd(&["EXISTS", "key"])).unwrap(),
1092 Command::Exists {
1093 keys: vec!["key".into()]
1094 },
1095 );
1096 }
1097
1098 #[test]
1099 fn exists_multiple() {
1100 assert_eq!(
1101 Command::from_frame(cmd(&["EXISTS", "a", "b"])).unwrap(),
1102 Command::Exists {
1103 keys: vec!["a".into(), "b".into()]
1104 },
1105 );
1106 }
1107
1108 #[test]
1109 fn exists_no_args() {
1110 let err = Command::from_frame(cmd(&["EXISTS"])).unwrap_err();
1111 assert!(matches!(err, ProtocolError::WrongArity(_)));
1112 }
1113
1114 #[test]
1117 fn mget_single() {
1118 assert_eq!(
1119 Command::from_frame(cmd(&["MGET", "key"])).unwrap(),
1120 Command::MGet {
1121 keys: vec!["key".into()]
1122 },
1123 );
1124 }
1125
1126 #[test]
1127 fn mget_multiple() {
1128 assert_eq!(
1129 Command::from_frame(cmd(&["MGET", "a", "b", "c"])).unwrap(),
1130 Command::MGet {
1131 keys: vec!["a".into(), "b".into(), "c".into()]
1132 },
1133 );
1134 }
1135
1136 #[test]
1137 fn mget_no_args() {
1138 let err = Command::from_frame(cmd(&["MGET"])).unwrap_err();
1139 assert!(matches!(err, ProtocolError::WrongArity(_)));
1140 }
1141
1142 #[test]
1145 fn mset_single_pair() {
1146 assert_eq!(
1147 Command::from_frame(cmd(&["MSET", "key", "val"])).unwrap(),
1148 Command::MSet {
1149 pairs: vec![("key".into(), Bytes::from("val"))]
1150 },
1151 );
1152 }
1153
1154 #[test]
1155 fn mset_multiple_pairs() {
1156 assert_eq!(
1157 Command::from_frame(cmd(&["MSET", "a", "1", "b", "2"])).unwrap(),
1158 Command::MSet {
1159 pairs: vec![
1160 ("a".into(), Bytes::from("1")),
1161 ("b".into(), Bytes::from("2")),
1162 ]
1163 },
1164 );
1165 }
1166
1167 #[test]
1168 fn mset_no_args() {
1169 let err = Command::from_frame(cmd(&["MSET"])).unwrap_err();
1170 assert!(matches!(err, ProtocolError::WrongArity(_)));
1171 }
1172
1173 #[test]
1174 fn mset_odd_args() {
1175 let err = Command::from_frame(cmd(&["MSET", "a", "1", "b"])).unwrap_err();
1177 assert!(matches!(err, ProtocolError::WrongArity(_)));
1178 }
1179
1180 #[test]
1183 fn expire_basic() {
1184 assert_eq!(
1185 Command::from_frame(cmd(&["EXPIRE", "key", "60"])).unwrap(),
1186 Command::Expire {
1187 key: "key".into(),
1188 seconds: 60,
1189 },
1190 );
1191 }
1192
1193 #[test]
1194 fn expire_wrong_arity() {
1195 let err = Command::from_frame(cmd(&["EXPIRE", "key"])).unwrap_err();
1196 assert!(matches!(err, ProtocolError::WrongArity(_)));
1197 }
1198
1199 #[test]
1200 fn expire_invalid_seconds() {
1201 let err = Command::from_frame(cmd(&["EXPIRE", "key", "abc"])).unwrap_err();
1202 assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
1203 }
1204
1205 #[test]
1206 fn expire_zero_seconds() {
1207 let err = Command::from_frame(cmd(&["EXPIRE", "key", "0"])).unwrap_err();
1208 assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
1209 }
1210
1211 #[test]
1214 fn ttl_basic() {
1215 assert_eq!(
1216 Command::from_frame(cmd(&["TTL", "key"])).unwrap(),
1217 Command::Ttl { key: "key".into() },
1218 );
1219 }
1220
1221 #[test]
1222 fn ttl_wrong_arity() {
1223 let err = Command::from_frame(cmd(&["TTL"])).unwrap_err();
1224 assert!(matches!(err, ProtocolError::WrongArity(_)));
1225 }
1226
1227 #[test]
1230 fn dbsize_basic() {
1231 assert_eq!(
1232 Command::from_frame(cmd(&["DBSIZE"])).unwrap(),
1233 Command::DbSize,
1234 );
1235 }
1236
1237 #[test]
1238 fn dbsize_case_insensitive() {
1239 assert_eq!(
1240 Command::from_frame(cmd(&["dbsize"])).unwrap(),
1241 Command::DbSize,
1242 );
1243 }
1244
1245 #[test]
1246 fn dbsize_extra_args() {
1247 let err = Command::from_frame(cmd(&["DBSIZE", "extra"])).unwrap_err();
1248 assert!(matches!(err, ProtocolError::WrongArity(_)));
1249 }
1250
1251 #[test]
1254 fn info_no_section() {
1255 assert_eq!(
1256 Command::from_frame(cmd(&["INFO"])).unwrap(),
1257 Command::Info { section: None },
1258 );
1259 }
1260
1261 #[test]
1262 fn info_with_section() {
1263 assert_eq!(
1264 Command::from_frame(cmd(&["INFO", "keyspace"])).unwrap(),
1265 Command::Info {
1266 section: Some("keyspace".into())
1267 },
1268 );
1269 }
1270
1271 #[test]
1272 fn info_too_many_args() {
1273 let err = Command::from_frame(cmd(&["INFO", "a", "b"])).unwrap_err();
1274 assert!(matches!(err, ProtocolError::WrongArity(_)));
1275 }
1276
1277 #[test]
1280 fn bgsave_basic() {
1281 assert_eq!(
1282 Command::from_frame(cmd(&["BGSAVE"])).unwrap(),
1283 Command::BgSave,
1284 );
1285 }
1286
1287 #[test]
1288 fn bgsave_case_insensitive() {
1289 assert_eq!(
1290 Command::from_frame(cmd(&["bgsave"])).unwrap(),
1291 Command::BgSave,
1292 );
1293 }
1294
1295 #[test]
1296 fn bgsave_extra_args() {
1297 let err = Command::from_frame(cmd(&["BGSAVE", "extra"])).unwrap_err();
1298 assert!(matches!(err, ProtocolError::WrongArity(_)));
1299 }
1300
1301 #[test]
1304 fn bgrewriteaof_basic() {
1305 assert_eq!(
1306 Command::from_frame(cmd(&["BGREWRITEAOF"])).unwrap(),
1307 Command::BgRewriteAof,
1308 );
1309 }
1310
1311 #[test]
1312 fn bgrewriteaof_case_insensitive() {
1313 assert_eq!(
1314 Command::from_frame(cmd(&["bgrewriteaof"])).unwrap(),
1315 Command::BgRewriteAof,
1316 );
1317 }
1318
1319 #[test]
1320 fn bgrewriteaof_extra_args() {
1321 let err = Command::from_frame(cmd(&["BGREWRITEAOF", "extra"])).unwrap_err();
1322 assert!(matches!(err, ProtocolError::WrongArity(_)));
1323 }
1324
1325 #[test]
1328 fn flushdb_basic() {
1329 assert_eq!(
1330 Command::from_frame(cmd(&["FLUSHDB"])).unwrap(),
1331 Command::FlushDb,
1332 );
1333 }
1334
1335 #[test]
1336 fn flushdb_case_insensitive() {
1337 assert_eq!(
1338 Command::from_frame(cmd(&["flushdb"])).unwrap(),
1339 Command::FlushDb,
1340 );
1341 }
1342
1343 #[test]
1344 fn flushdb_extra_args() {
1345 let err = Command::from_frame(cmd(&["FLUSHDB", "extra"])).unwrap_err();
1346 assert!(matches!(err, ProtocolError::WrongArity(_)));
1347 }
1348
1349 #[test]
1352 fn lpush_single() {
1353 assert_eq!(
1354 Command::from_frame(cmd(&["LPUSH", "list", "val"])).unwrap(),
1355 Command::LPush {
1356 key: "list".into(),
1357 values: vec![Bytes::from("val")],
1358 },
1359 );
1360 }
1361
1362 #[test]
1363 fn lpush_multiple() {
1364 assert_eq!(
1365 Command::from_frame(cmd(&["LPUSH", "list", "a", "b", "c"])).unwrap(),
1366 Command::LPush {
1367 key: "list".into(),
1368 values: vec![Bytes::from("a"), Bytes::from("b"), Bytes::from("c")],
1369 },
1370 );
1371 }
1372
1373 #[test]
1374 fn lpush_no_value() {
1375 let err = Command::from_frame(cmd(&["LPUSH", "key"])).unwrap_err();
1376 assert!(matches!(err, ProtocolError::WrongArity(_)));
1377 }
1378
1379 #[test]
1380 fn lpush_case_insensitive() {
1381 assert!(matches!(
1382 Command::from_frame(cmd(&["lpush", "k", "v"])).unwrap(),
1383 Command::LPush { .. }
1384 ));
1385 }
1386
1387 #[test]
1390 fn rpush_single() {
1391 assert_eq!(
1392 Command::from_frame(cmd(&["RPUSH", "list", "val"])).unwrap(),
1393 Command::RPush {
1394 key: "list".into(),
1395 values: vec![Bytes::from("val")],
1396 },
1397 );
1398 }
1399
1400 #[test]
1401 fn rpush_no_value() {
1402 let err = Command::from_frame(cmd(&["RPUSH", "key"])).unwrap_err();
1403 assert!(matches!(err, ProtocolError::WrongArity(_)));
1404 }
1405
1406 #[test]
1409 fn lpop_basic() {
1410 assert_eq!(
1411 Command::from_frame(cmd(&["LPOP", "list"])).unwrap(),
1412 Command::LPop { key: "list".into() },
1413 );
1414 }
1415
1416 #[test]
1417 fn lpop_wrong_arity() {
1418 let err = Command::from_frame(cmd(&["LPOP"])).unwrap_err();
1419 assert!(matches!(err, ProtocolError::WrongArity(_)));
1420 }
1421
1422 #[test]
1425 fn rpop_basic() {
1426 assert_eq!(
1427 Command::from_frame(cmd(&["RPOP", "list"])).unwrap(),
1428 Command::RPop { key: "list".into() },
1429 );
1430 }
1431
1432 #[test]
1435 fn lrange_basic() {
1436 assert_eq!(
1437 Command::from_frame(cmd(&["LRANGE", "list", "0", "-1"])).unwrap(),
1438 Command::LRange {
1439 key: "list".into(),
1440 start: 0,
1441 stop: -1,
1442 },
1443 );
1444 }
1445
1446 #[test]
1447 fn lrange_wrong_arity() {
1448 let err = Command::from_frame(cmd(&["LRANGE", "list", "0"])).unwrap_err();
1449 assert!(matches!(err, ProtocolError::WrongArity(_)));
1450 }
1451
1452 #[test]
1453 fn lrange_invalid_index() {
1454 let err = Command::from_frame(cmd(&["LRANGE", "list", "abc", "0"])).unwrap_err();
1455 assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
1456 }
1457
1458 #[test]
1461 fn llen_basic() {
1462 assert_eq!(
1463 Command::from_frame(cmd(&["LLEN", "list"])).unwrap(),
1464 Command::LLen { key: "list".into() },
1465 );
1466 }
1467
1468 #[test]
1469 fn llen_wrong_arity() {
1470 let err = Command::from_frame(cmd(&["LLEN"])).unwrap_err();
1471 assert!(matches!(err, ProtocolError::WrongArity(_)));
1472 }
1473
1474 #[test]
1477 fn type_basic() {
1478 assert_eq!(
1479 Command::from_frame(cmd(&["TYPE", "key"])).unwrap(),
1480 Command::Type { key: "key".into() },
1481 );
1482 }
1483
1484 #[test]
1485 fn type_wrong_arity() {
1486 let err = Command::from_frame(cmd(&["TYPE"])).unwrap_err();
1487 assert!(matches!(err, ProtocolError::WrongArity(_)));
1488 }
1489
1490 #[test]
1491 fn type_case_insensitive() {
1492 assert!(matches!(
1493 Command::from_frame(cmd(&["type", "k"])).unwrap(),
1494 Command::Type { .. }
1495 ));
1496 }
1497
1498 #[test]
1501 fn unknown_command() {
1502 assert_eq!(
1503 Command::from_frame(cmd(&["FOOBAR", "arg"])).unwrap(),
1504 Command::Unknown("FOOBAR".into()),
1505 );
1506 }
1507
1508 #[test]
1509 fn non_array_frame() {
1510 let err = Command::from_frame(Frame::Simple("PING".into())).unwrap_err();
1511 assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
1512 }
1513
1514 #[test]
1515 fn empty_array() {
1516 let err = Command::from_frame(Frame::Array(vec![])).unwrap_err();
1517 assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
1518 }
1519
1520 #[test]
1523 fn zadd_basic() {
1524 let parsed = Command::from_frame(cmd(&["ZADD", "board", "100", "alice"])).unwrap();
1525 match parsed {
1526 Command::ZAdd {
1527 key,
1528 flags,
1529 members,
1530 } => {
1531 assert_eq!(key, "board");
1532 assert_eq!(flags, ZAddFlags::default());
1533 assert_eq!(members, vec![(100.0, "alice".into())]);
1534 }
1535 other => panic!("expected ZAdd, got {other:?}"),
1536 }
1537 }
1538
1539 #[test]
1540 fn zadd_multiple_members() {
1541 let parsed =
1542 Command::from_frame(cmd(&["ZADD", "board", "100", "alice", "200", "bob"])).unwrap();
1543 match parsed {
1544 Command::ZAdd { members, .. } => {
1545 assert_eq!(members.len(), 2);
1546 assert_eq!(members[0], (100.0, "alice".into()));
1547 assert_eq!(members[1], (200.0, "bob".into()));
1548 }
1549 other => panic!("expected ZAdd, got {other:?}"),
1550 }
1551 }
1552
1553 #[test]
1554 fn zadd_with_flags() {
1555 let parsed = Command::from_frame(cmd(&["ZADD", "z", "NX", "CH", "100", "alice"])).unwrap();
1556 match parsed {
1557 Command::ZAdd { flags, .. } => {
1558 assert!(flags.nx);
1559 assert!(flags.ch);
1560 assert!(!flags.xx);
1561 assert!(!flags.gt);
1562 assert!(!flags.lt);
1563 }
1564 other => panic!("expected ZAdd, got {other:?}"),
1565 }
1566 }
1567
1568 #[test]
1569 fn zadd_gt_flag() {
1570 let parsed = Command::from_frame(cmd(&["zadd", "z", "gt", "100", "alice"])).unwrap();
1571 match parsed {
1572 Command::ZAdd { flags, .. } => assert!(flags.gt),
1573 other => panic!("expected ZAdd, got {other:?}"),
1574 }
1575 }
1576
1577 #[test]
1578 fn zadd_nx_xx_conflict() {
1579 let err = Command::from_frame(cmd(&["ZADD", "z", "NX", "XX", "100", "alice"])).unwrap_err();
1580 assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
1581 }
1582
1583 #[test]
1584 fn zadd_gt_lt_conflict() {
1585 let err = Command::from_frame(cmd(&["ZADD", "z", "GT", "LT", "100", "alice"])).unwrap_err();
1586 assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
1587 }
1588
1589 #[test]
1590 fn zadd_wrong_arity() {
1591 let err = Command::from_frame(cmd(&["ZADD", "z"])).unwrap_err();
1592 assert!(matches!(err, ProtocolError::WrongArity(_)));
1593 }
1594
1595 #[test]
1596 fn zadd_odd_score_member_count() {
1597 let err = Command::from_frame(cmd(&["ZADD", "z", "100"])).unwrap_err();
1599 assert!(matches!(err, ProtocolError::WrongArity(_)));
1600 }
1601
1602 #[test]
1603 fn zadd_invalid_score() {
1604 let err = Command::from_frame(cmd(&["ZADD", "z", "notanum", "alice"])).unwrap_err();
1605 assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
1606 }
1607
1608 #[test]
1609 fn zadd_nan_score() {
1610 let err = Command::from_frame(cmd(&["ZADD", "z", "nan", "alice"])).unwrap_err();
1611 assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
1612 }
1613
1614 #[test]
1615 fn zadd_negative_score() {
1616 let parsed = Command::from_frame(cmd(&["ZADD", "z", "-50.5", "alice"])).unwrap();
1617 match parsed {
1618 Command::ZAdd { members, .. } => {
1619 assert_eq!(members[0].0, -50.5);
1620 }
1621 other => panic!("expected ZAdd, got {other:?}"),
1622 }
1623 }
1624
1625 #[test]
1628 fn zrem_basic() {
1629 assert_eq!(
1630 Command::from_frame(cmd(&["ZREM", "z", "alice"])).unwrap(),
1631 Command::ZRem {
1632 key: "z".into(),
1633 members: vec!["alice".into()],
1634 },
1635 );
1636 }
1637
1638 #[test]
1639 fn zrem_multiple() {
1640 let parsed = Command::from_frame(cmd(&["ZREM", "z", "a", "b", "c"])).unwrap();
1641 match parsed {
1642 Command::ZRem { members, .. } => assert_eq!(members.len(), 3),
1643 other => panic!("expected ZRem, got {other:?}"),
1644 }
1645 }
1646
1647 #[test]
1648 fn zrem_wrong_arity() {
1649 let err = Command::from_frame(cmd(&["ZREM", "z"])).unwrap_err();
1650 assert!(matches!(err, ProtocolError::WrongArity(_)));
1651 }
1652
1653 #[test]
1656 fn zscore_basic() {
1657 assert_eq!(
1658 Command::from_frame(cmd(&["ZSCORE", "z", "alice"])).unwrap(),
1659 Command::ZScore {
1660 key: "z".into(),
1661 member: "alice".into(),
1662 },
1663 );
1664 }
1665
1666 #[test]
1667 fn zscore_wrong_arity() {
1668 let err = Command::from_frame(cmd(&["ZSCORE", "z"])).unwrap_err();
1669 assert!(matches!(err, ProtocolError::WrongArity(_)));
1670 }
1671
1672 #[test]
1675 fn zrank_basic() {
1676 assert_eq!(
1677 Command::from_frame(cmd(&["ZRANK", "z", "alice"])).unwrap(),
1678 Command::ZRank {
1679 key: "z".into(),
1680 member: "alice".into(),
1681 },
1682 );
1683 }
1684
1685 #[test]
1686 fn zrank_wrong_arity() {
1687 let err = Command::from_frame(cmd(&["ZRANK", "z"])).unwrap_err();
1688 assert!(matches!(err, ProtocolError::WrongArity(_)));
1689 }
1690
1691 #[test]
1694 fn zcard_basic() {
1695 assert_eq!(
1696 Command::from_frame(cmd(&["ZCARD", "z"])).unwrap(),
1697 Command::ZCard { key: "z".into() },
1698 );
1699 }
1700
1701 #[test]
1702 fn zcard_wrong_arity() {
1703 let err = Command::from_frame(cmd(&["ZCARD"])).unwrap_err();
1704 assert!(matches!(err, ProtocolError::WrongArity(_)));
1705 }
1706
1707 #[test]
1710 fn zrange_basic() {
1711 assert_eq!(
1712 Command::from_frame(cmd(&["ZRANGE", "z", "0", "-1"])).unwrap(),
1713 Command::ZRange {
1714 key: "z".into(),
1715 start: 0,
1716 stop: -1,
1717 with_scores: false,
1718 },
1719 );
1720 }
1721
1722 #[test]
1723 fn zrange_with_scores() {
1724 assert_eq!(
1725 Command::from_frame(cmd(&["ZRANGE", "z", "0", "-1", "WITHSCORES"])).unwrap(),
1726 Command::ZRange {
1727 key: "z".into(),
1728 start: 0,
1729 stop: -1,
1730 with_scores: true,
1731 },
1732 );
1733 }
1734
1735 #[test]
1736 fn zrange_withscores_case_insensitive() {
1737 assert_eq!(
1738 Command::from_frame(cmd(&["zrange", "z", "0", "-1", "withscores"])).unwrap(),
1739 Command::ZRange {
1740 key: "z".into(),
1741 start: 0,
1742 stop: -1,
1743 with_scores: true,
1744 },
1745 );
1746 }
1747
1748 #[test]
1749 fn zrange_invalid_option() {
1750 let err = Command::from_frame(cmd(&["ZRANGE", "z", "0", "-1", "BADOPT"])).unwrap_err();
1751 assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
1752 }
1753
1754 #[test]
1755 fn zrange_wrong_arity() {
1756 let err = Command::from_frame(cmd(&["ZRANGE", "z", "0"])).unwrap_err();
1757 assert!(matches!(err, ProtocolError::WrongArity(_)));
1758 }
1759
1760 #[test]
1763 fn incr_basic() {
1764 assert_eq!(
1765 Command::from_frame(cmd(&["INCR", "counter"])).unwrap(),
1766 Command::Incr {
1767 key: "counter".into()
1768 },
1769 );
1770 }
1771
1772 #[test]
1773 fn incr_wrong_arity() {
1774 let err = Command::from_frame(cmd(&["INCR"])).unwrap_err();
1775 assert!(matches!(err, ProtocolError::WrongArity(_)));
1776 }
1777
1778 #[test]
1781 fn decr_basic() {
1782 assert_eq!(
1783 Command::from_frame(cmd(&["DECR", "counter"])).unwrap(),
1784 Command::Decr {
1785 key: "counter".into()
1786 },
1787 );
1788 }
1789
1790 #[test]
1791 fn decr_wrong_arity() {
1792 let err = Command::from_frame(cmd(&["DECR"])).unwrap_err();
1793 assert!(matches!(err, ProtocolError::WrongArity(_)));
1794 }
1795
1796 #[test]
1799 fn persist_basic() {
1800 assert_eq!(
1801 Command::from_frame(cmd(&["PERSIST", "key"])).unwrap(),
1802 Command::Persist { key: "key".into() },
1803 );
1804 }
1805
1806 #[test]
1807 fn persist_case_insensitive() {
1808 assert_eq!(
1809 Command::from_frame(cmd(&["persist", "key"])).unwrap(),
1810 Command::Persist { key: "key".into() },
1811 );
1812 }
1813
1814 #[test]
1815 fn persist_wrong_arity() {
1816 let err = Command::from_frame(cmd(&["PERSIST"])).unwrap_err();
1817 assert!(matches!(err, ProtocolError::WrongArity(_)));
1818 }
1819
1820 #[test]
1823 fn pttl_basic() {
1824 assert_eq!(
1825 Command::from_frame(cmd(&["PTTL", "key"])).unwrap(),
1826 Command::Pttl { key: "key".into() },
1827 );
1828 }
1829
1830 #[test]
1831 fn pttl_wrong_arity() {
1832 let err = Command::from_frame(cmd(&["PTTL"])).unwrap_err();
1833 assert!(matches!(err, ProtocolError::WrongArity(_)));
1834 }
1835
1836 #[test]
1839 fn pexpire_basic() {
1840 assert_eq!(
1841 Command::from_frame(cmd(&["PEXPIRE", "key", "5000"])).unwrap(),
1842 Command::Pexpire {
1843 key: "key".into(),
1844 milliseconds: 5000,
1845 },
1846 );
1847 }
1848
1849 #[test]
1850 fn pexpire_wrong_arity() {
1851 let err = Command::from_frame(cmd(&["PEXPIRE", "key"])).unwrap_err();
1852 assert!(matches!(err, ProtocolError::WrongArity(_)));
1853 }
1854
1855 #[test]
1856 fn pexpire_zero_millis() {
1857 let err = Command::from_frame(cmd(&["PEXPIRE", "key", "0"])).unwrap_err();
1858 assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
1859 }
1860
1861 #[test]
1862 fn pexpire_invalid_millis() {
1863 let err = Command::from_frame(cmd(&["PEXPIRE", "key", "notanum"])).unwrap_err();
1864 assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
1865 }
1866
1867 #[test]
1870 fn scan_basic() {
1871 assert_eq!(
1872 Command::from_frame(cmd(&["SCAN", "0"])).unwrap(),
1873 Command::Scan {
1874 cursor: 0,
1875 pattern: None,
1876 count: None,
1877 },
1878 );
1879 }
1880
1881 #[test]
1882 fn scan_with_match() {
1883 assert_eq!(
1884 Command::from_frame(cmd(&["SCAN", "0", "MATCH", "user:*"])).unwrap(),
1885 Command::Scan {
1886 cursor: 0,
1887 pattern: Some("user:*".into()),
1888 count: None,
1889 },
1890 );
1891 }
1892
1893 #[test]
1894 fn scan_with_count() {
1895 assert_eq!(
1896 Command::from_frame(cmd(&["SCAN", "42", "COUNT", "100"])).unwrap(),
1897 Command::Scan {
1898 cursor: 42,
1899 pattern: None,
1900 count: Some(100),
1901 },
1902 );
1903 }
1904
1905 #[test]
1906 fn scan_with_match_and_count() {
1907 assert_eq!(
1908 Command::from_frame(cmd(&["SCAN", "0", "MATCH", "*:data", "COUNT", "50"])).unwrap(),
1909 Command::Scan {
1910 cursor: 0,
1911 pattern: Some("*:data".into()),
1912 count: Some(50),
1913 },
1914 );
1915 }
1916
1917 #[test]
1918 fn scan_count_before_match() {
1919 assert_eq!(
1920 Command::from_frame(cmd(&["SCAN", "0", "COUNT", "10", "MATCH", "foo*"])).unwrap(),
1921 Command::Scan {
1922 cursor: 0,
1923 pattern: Some("foo*".into()),
1924 count: Some(10),
1925 },
1926 );
1927 }
1928
1929 #[test]
1930 fn scan_case_insensitive() {
1931 assert_eq!(
1932 Command::from_frame(cmd(&["scan", "0", "match", "x*", "count", "5"])).unwrap(),
1933 Command::Scan {
1934 cursor: 0,
1935 pattern: Some("x*".into()),
1936 count: Some(5),
1937 },
1938 );
1939 }
1940
1941 #[test]
1942 fn scan_wrong_arity() {
1943 let err = Command::from_frame(cmd(&["SCAN"])).unwrap_err();
1944 assert!(matches!(err, ProtocolError::WrongArity(_)));
1945 }
1946
1947 #[test]
1948 fn scan_invalid_cursor() {
1949 let err = Command::from_frame(cmd(&["SCAN", "notanum"])).unwrap_err();
1950 assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
1951 }
1952
1953 #[test]
1954 fn scan_invalid_count() {
1955 let err = Command::from_frame(cmd(&["SCAN", "0", "COUNT", "bad"])).unwrap_err();
1956 assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
1957 }
1958
1959 #[test]
1960 fn scan_unknown_flag() {
1961 let err = Command::from_frame(cmd(&["SCAN", "0", "BADOPT", "val"])).unwrap_err();
1962 assert!(matches!(err, ProtocolError::InvalidCommandFrame(_)));
1963 }
1964
1965 #[test]
1966 fn scan_match_missing_pattern() {
1967 let err = Command::from_frame(cmd(&["SCAN", "0", "MATCH"])).unwrap_err();
1968 assert!(matches!(err, ProtocolError::WrongArity(_)));
1969 }
1970
1971 #[test]
1972 fn scan_count_missing_value() {
1973 let err = Command::from_frame(cmd(&["SCAN", "0", "COUNT"])).unwrap_err();
1974 assert!(matches!(err, ProtocolError::WrongArity(_)));
1975 }
1976}