use crate::request::Argv;
use yo_common::num::{parse_f64, parse_i64};
use yo_common::{Code, Error, Result};
pub const NOT_AN_INT: &str = "value is not an integer or out of range";
pub const NOT_A_FLOAT: &str = "value is not a valid float";
pub const SYNTAX: &str = "syntax error";
#[derive(Clone, Copy)]
pub struct Args<'a> {
argv: &'a Argv,
buf: &'a [u8],
}
impl<'a> Args<'a> {
#[must_use]
pub fn new(argv: &'a Argv, buf: &'a [u8]) -> Args<'a> {
Args { argv, buf }
}
#[must_use]
pub fn len(&self) -> usize {
self.argv.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.argv.is_empty()
}
#[must_use]
pub fn get(&self, i: usize) -> &'a [u8] {
self.argv.arg(self.buf, i).unwrap_or(b"")
}
#[must_use]
pub fn opt(&self, i: usize) -> Option<&'a [u8]> {
self.argv.arg(self.buf, i)
}
#[must_use]
pub fn name(&self) -> &'a [u8] {
self.get(0)
}
pub fn int(&self, i: usize) -> Result<i64> {
parse_i64(self.get(i)).ok_or_else(|| Error::new(Code::Invalid, NOT_AN_INT).at(i as u32))
}
pub fn float(&self, i: usize) -> Result<f64> {
parse_f64(self.get(i)).ok_or_else(|| Error::new(Code::Invalid, NOT_A_FLOAT).at(i as u32))
}
}
#[must_use]
pub fn is(arg: &[u8], keyword: &[u8]) -> bool {
arg.len() == keyword.len() && arg.eq_ignore_ascii_case(keyword)
}
#[must_use]
pub fn syntax() -> Error {
Error::new(Code::Invalid, SYNTAX)
}
#[must_use]
pub fn wrong_arity(name: &str) -> Error {
Error::fmt(
Code::Invalid,
format_args!("wrong number of arguments for '{name}' command"),
)
}
#[must_use]
pub fn wrong_arity_sub(name: &str, sub: &str) -> Error {
Error::fmt(
Code::Invalid,
format_args!("wrong number of arguments for '{name}|{sub}' command"),
)
}
#[must_use]
pub fn unknown_subcommand(sub: &[u8], container: &str) -> Error {
yo_alloc::allow(|| {
Error::fmt(
Code::Unsupported,
format_args!(
"unknown subcommand '{}'. Try {} HELP.",
String::from_utf8_lossy(sub),
container
),
)
})
}
#[must_use]
pub fn subcommand_syntax(sub: &[u8], container: &str) -> Error {
yo_alloc::allow(|| {
Error::fmt(
Code::Unsupported,
format_args!(
"unknown subcommand or wrong number of arguments for '{}'. Try {} HELP.",
String::from_utf8_lossy(sub),
container
),
)
})
}
#[must_use]
pub fn invalid_expire(name: &str) -> Error {
Error::fmt(
Code::Invalid,
format_args!("invalid expire time in '{name}' command"),
)
}
#[must_use]
pub fn unknown_command(args: Args<'_>) -> Error {
yo_alloc::allow(|| {
let mut msg = String::from("unknown command '");
msg.push_str(&String::from_utf8_lossy(args.name()));
msg.push('\'');
if args.len() == 1 {
return Error::new(Code::Unsupported, msg);
}
msg.push_str(", with args beginning with: ");
let start = msg.len();
for i in 1..args.len() {
let used = msg.len() - start;
if used >= 128 {
break;
}
let arg = args.get(i);
let arg = &arg[..arg.len().min(128 - used)];
msg.push('\'');
msg.push_str(&String::from_utf8_lossy(arg));
msg.push_str("' ");
}
Error::new(Code::Unsupported, msg)
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dispatch::tests::encode;
use crate::proto::Limits;
use crate::request::Step;
#[test]
fn arguments_are_slices_of_the_read_buffer() {
let wire = encode(&[b"SET", b"k", b"v"]);
let mut argv = Argv::new();
assert!(matches!(
argv.decode(&wire, &Limits::default()).unwrap(),
Step::Command { .. }
));
let args = Args::new(&argv, &wire);
assert_eq!(args.len(), 3);
assert_eq!(args.name(), b"SET");
assert_eq!(args.get(2), b"v");
assert_eq!(args.get(9), b"");
assert_eq!(args.opt(9), None);
}
#[test]
fn keywords_match_whatever_case_the_client_used() {
assert!(is(b"nx", b"NX"));
assert!(is(b"Nx", b"NX"));
assert!(!is(b"nxx", b"NX"));
assert!(!is(b"n", b"NX"));
}
#[test]
fn the_unknown_command_message_is_redis_own() {
let wire = encode(&[b"NOPE", b"a", b"b"]);
let mut argv = Argv::new();
argv.decode(&wire, &Limits::default()).unwrap();
let e = unknown_command(Args::new(&argv, &wire));
assert_eq!(
e.message(),
"unknown command 'NOPE', with args beginning with: 'a' 'b' "
);
}
#[test]
fn a_command_with_no_arguments_gets_the_short_sentence() {
let wire = encode(&[b"NOPE"]);
let mut argv = Argv::new();
argv.decode(&wire, &Limits::default()).unwrap();
let e = unknown_command(Args::new(&argv, &wire));
assert_eq!(e.message(), "unknown command 'NOPE'");
}
#[test]
fn a_client_that_sends_a_megabyte_does_not_get_it_back() {
let big = vec![b'x'; 1024];
let wire = encode(&[b"NOPE", &big, &big]);
let mut argv = Argv::new();
argv.decode(&wire, &Limits::default()).unwrap();
let e = unknown_command(Args::new(&argv, &wire));
assert_eq!(
e.message().len(),
"unknown command 'NOPE', with args beginning with: ".len() + 131
);
}
}