use kevy_resp::{
ArgvView, RespVersion, encode_bulk, encode_error, encode_integer, encode_simple_string,
encode_verbatim,
};
use super::wrong_args;
pub(crate) fn cmd_client<A: ArgvView + ?Sized>(args: &A, out: &mut Vec<u8>, proto: RespVersion) {
let sub = match args.get(1) {
Some(s) => s.to_ascii_uppercase(),
None => return wrong_args(out, "client"),
};
match sub.as_slice() {
b"ID" => encode_integer(out, 1),
b"GETNAME" => encode_bulk(out, &[]),
b"SETNAME" => {
if args.len() == 3 {
encode_simple_string(out, "OK");
} else {
wrong_args(out, "client|setname");
}
}
b"LIST" => {
if args.len() == 2 {
emit_client_text(out, &[], proto);
} else {
encode_error(out, "ERR syntax error");
}
}
b"KILL" => match kevy_rt::ClientKillFilter::parse(args) {
Some(_) => encode_integer(out, 0),
None => encode_error(out, "ERR syntax error"),
},
b"INFO" => {
let body = "id=1 addr=127.0.0.1:0 laddr=127.0.0.1:0 fd=0 name= \
age=0 idle=0 flags=N db=0 sub=0 psub=0 ssub=0 \
multi=-1 cmd=client|info user=default resp=2";
emit_client_text(out, body.as_bytes(), proto);
}
b"NO-EVICT" => encode_simple_string(out, "OK"),
_ => encode_error(
out,
&format!(
"ERR unknown CLIENT subcommand '{}'",
String::from_utf8_lossy(args.get(1).unwrap_or(&[][..]))
),
),
}
}
fn emit_client_text(out: &mut Vec<u8>, body: &[u8], proto: RespVersion) {
match proto {
RespVersion::V2 => encode_bulk(out, body),
RespVersion::V3 => encode_verbatim(out, *b"txt", body),
}
}
#[cfg(test)]
mod tests {
use super::*;
use kevy_resp::Argv;
fn run(rest: &[&[u8]]) -> Vec<u8> {
let mut a = Argv::default();
a.push(b"CLIENT");
for r in rest {
a.push(r);
}
let mut out = Vec::new();
cmd_client(&a, &mut out, RespVersion::V2);
out
}
#[test]
fn id_returns_integer() {
let out = run(&[b"ID"]);
assert_eq!(out, b":1\r\n");
}
#[test]
fn getname_returns_empty_bulk() {
let out = run(&[b"GETNAME"]);
assert_eq!(out, b"$0\r\n\r\n");
}
#[test]
fn setname_accepted() {
let out = run(&[b"SETNAME", b"my-client"]);
assert_eq!(out, b"+OK\r\n");
}
#[test]
fn setname_wrong_args() {
let out = run(&[b"SETNAME"]);
assert!(out.starts_with(b"-ERR"));
}
#[test]
fn bare_list_without_a_runtime_is_empty() {
let out = run(&[b"LIST"]);
assert_eq!(out, b"$0\r\n\r\n");
}
#[test]
fn filtered_list_is_a_syntax_error() {
let out = run(&[b"LIST", b"TYPE", b"normal"]);
assert!(out.starts_with(b"-ERR"));
}
#[test]
fn kill_returns_zero() {
let out = run(&[b"KILL", b"ADDR", b"1.2.3.4:5"]);
assert_eq!(out, b":0\r\n");
}
#[test]
fn malformed_kill_is_a_syntax_error() {
let out = run(&[b"KILL", b"LADDR", b"1.2.3.4:5"]);
assert!(out.starts_with(b"-ERR"));
}
#[test]
fn info_returns_bulk() {
let out = run(&[b"INFO"]);
let s = String::from_utf8(out).unwrap();
assert!(s.starts_with('$'));
assert!(s.contains("id=1"));
}
#[test]
fn no_evict_returns_ok() {
let out = run(&[b"NO-EVICT", b"ON"]);
assert_eq!(out, b"+OK\r\n");
}
#[test]
fn unknown_subcommand_errors() {
let out = run(&[b"UNKNOWN-SUB"]);
assert!(out.starts_with(b"-ERR"));
}
#[test]
fn bare_client_errors() {
let out = run(&[]);
assert!(out.starts_with(b"-ERR"));
}
}