use crate::{Resp3, RespWriter, sanitize_error_str};
pub const RESP_OK: &[u8] = b"+OK\r\n";
pub const RESP_PONG: &[u8] = b"+PONG\r\n";
pub const RESP_EMPTYLIST: &[u8] = b"*0\r\n";
pub const RESP_RETURN_VAL_0: &[u8] = b":0\r\n";
pub const RESP_RETURN_VAL_1: &[u8] = b":1\r\n";
pub const RESP_RETURN_VAL_N1: &[u8] = b":-1\r\n";
pub const RESP_RETURN_VAL_N2: &[u8] = b":-2\r\n";
pub const RESP_ERR_GENERIC: &str = "generic error";
pub const RESP_ERR_NOAUTH: &str = "NOAUTH Authentication required.";
pub const RESP_ERR_NOPERM: &str = "NOPERM this user has no permissions to run the command";
pub const RESP_ERR_WRONG_TYPE: &str =
"WRONGTYPE Operation against a key holding the wrong kind of value.";
pub const RESP_ERR_GENERIC_UNK_CMD: &str = "ERR unknown command";
pub const RESP_ERR_GENERIC_CLUSTER_DISABLED: &str =
"ERR This instance has cluster support disabled";
pub const RESP_ERR_GENERIC_NOSUCHKEY: &str = "ERR no such key";
pub const RESP_ERR_GENERIC_INVALIDEXP_IN_SET: &str = "ERR invalid expire time in 'set' command";
pub const RESP_ERR_GENERIC_INVALIDEXP_IN_GETEX: &str = "ERR invalid expire time in 'getex' command";
pub const RESP_ERR_OVERFLOWEXP_IN_GETEX: &str = "ERR expire time overflows date in 'getex' command";
pub const RESP_ERR_GENERIC_SYNTAX_ERROR: &str = "ERR syntax error";
pub const RESP_ERR_INVALID_ETAG: &str = "ETAG must be a numerical value greater than or equal to 0";
pub const NOGET: &str = "NOGET";
pub const RESP_ERR_GENERIC_NAN_INFINITY_INCR: &str = "ERR increment would produce NaN or Infinity";
pub const RESP_ERR_GENERIC_OFFSETOUTOFRANGE: &str = "ERR offset is out of range";
pub const RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER: &str =
"ERR value is not an integer or out of range.";
pub const RESP_ERR_GENERIC_VALUE_IS_OUT_OF_RANGE: &str =
"ERR value is out of range, must be positive.";
pub const RESP_ERR_GENERIC_BIT_IS_NOT_INTEGER: &str = "ERR bit is not an integer or out of range";
pub const RESP_ERR_GENERIC_BITOFFSET_IS_NOT_INTEGER: &str =
"ERR bit offset is not an integer or out of range";
pub const RESP_ERR_PROTOCOL_VALUE_IS_NOT_INTEGER: &str =
"ERR Protocol version is not an integer or out of range.";
pub const RESP_ERR_UNSUPPORTED_PROTOCOL_VERSION: &str = "ERR Unsupported protocol version";
pub const RESP_ERR_NOT_VALID_FLOAT: &str = "ERR value is not a valid float";
pub const RESP_ERR_TIMEOUT_NOT_VALID_FLOAT: &str = "ERR timeout is not a float or out of range";
pub const RESP_ERR_NOT_VALID_RADIUS: &str = "ERR need numeric radius";
pub const RESP_ERR_RADIUS_IS_NEGATIVE: &str = "ERR radius cannot be negative";
pub const RESP_ERR_NOT_VALID_WIDTH: &str = "ERR need numeric width";
pub const RESP_ERR_NOT_VALID_HEIGHT: &str = "ERR need numeric height";
pub const RESP_ERR_HEIGHT_OR_WIDTH_NEGATIVE: &str = "ERR height or width cannot be negative";
pub const RESP_ERR_NOT_VALID_GEO_DISTANCE_UNIT: &str =
"ERR unsupported unit provided. please use M, KM, FT, MI";
pub const RESP_ERR_COUNT_IS_NOT_POSITIVE: &str = "ERR COUNT must be > 0";
pub const RESP_ERR_SLOW_PATH_STORAGE: &str = "ERR slow path storage error";
pub const RESP_WRONGPASS_INVALID_PASSWORD: &str = "WRONGPASS Invalid password";
pub const RESP_WRONGPASS_INVALID_USERNAME_PASSWORD: &str =
"WRONGPASS Invalid username/password combination";
pub const RESP_ERR_BUSSYKEY: &str = "BUSYKEY Target key name already exists.";
pub const RESP_ERR_INVALID_EXPIRE_TIME: &str = "ERR invalid expire time, must be >= 0";
pub const RESP_ERR_NOSCRIPT: &str = "ERR This Redis command is not allowed from script";
pub const RESP_ERR_HCOLLECT_ALREADY_IN_PROGRESS: &str = "ERR HCOLLECT scan already in progress";
pub const RESP_ERR_ZCOLLECT_ALREADY_IN_PROGRESS: &str = "ERR ZCOLLECT scan already in progress";
pub const RESP_ERR_OBJECT_FREQ_UNSUPPORTED: &str = "ERR OBJECT FREQ is not supported: Garnet does not track access frequency (no LFU maxmemory policy).";
pub const RESP_INVALID_COMMAND_SPECIFIED: &str = "Invalid command specified";
pub const RESP_COMMAND_HAS_NO_KEY_ARGS: &str = "The command has no key arguments";
pub const RESP_ERR_NOT_SUPPORTED_RESP2: &str = "ERR command not supported in RESP2";
pub const RESP_ERR_ASYNC_PROTOCOL_CHANGE: &str =
"ERR protocol change is not allowed with pending async operations";
pub const RESP_ERR_CANNOT_LIST_CLIENTS: &str = "ERR Clients cannot be listed.";
pub const RESP_ERR_UBLOCKING_CLINET: &str = "ERR Unable to unblock client because of error.";
pub const RESP_ERR_NO_SUCH_CLIENT: &str = "ERR No such client";
pub const RESP_ERR_INVALID_CLIENT_ID: &str = "ERR Invalid client ID";
pub const RESP_ERR_INVALID_CLIENT_NAME: &str =
"ERR Client names cannot contain spaces, newlines or special characters.";
pub const RESP_ERR_INVALID_CLIENT_UNBLOCK_REASON: &str =
"ERR CLIENT UNBLOCK reason should be TIMEOUT or ERROR";
pub const RESP_UNBLOCKED_CLIENT_VIA_CLIENT_UNBLOCK: &str =
"UNBLOCKED client unblocked via CLIENT UNBLOCK";
pub const RESP_ERR_EXPDELSCAN_INVALID: &str =
"ERR Cannot execute EXPDELSCAN with background expired key deletion scan enabled";
pub const RESP_ERR_CHECKPOINT_ALREADY_IN_PROGRESS: &str = "ERR checkpoint already in progress";
pub const RESP_ERR_DB_INDEX_OUT_OF_RANGE: &str = "ERR DB index is out of range.";
pub const RESP_ERR_INVALID_FIRST_DB_INDEX: &str = "ERR invalid first DB index.";
pub const RESP_ERR_INVALID_SECOND_DB_INDEX: &str = "ERR invalid second DB index.";
pub const RESP_ERR_SELECT_UNSUCCESSFUL: &str = "ERR unable to select database.";
pub const RESP_ERR_GENERIC_SELECT_CLUSTER_MODE: &str = "ERR SELECT is not allowed in cluster mode";
pub const RESP_ERR_GENERIC_SWAPDB_CLUSTER_MODE: &str = "ERR SWAPDB is not allowed in cluster mode";
pub const RESP_ERR_SWAPDB_UNSUPPORTED: &str =
"ERR SWAPDB is currently unsupported when multiple clients are connected.";
pub const RESP_ERR_DB_ID_CLUSTER_MODE: &str =
"ERR specifying non-zero DBID is not allowed in cluster mode";
pub const RESP_ERR_FLUSHALL_READONLY_REPLICA: &str =
"ERR You can't write against a read only replica.";
pub const GENERIC_ERR_WRONG_NUM_ARGS: &str = "ERR wrong number of arguments for '{0}' command";
#[macro_export]
macro_rules! wrong_num_args {
($cmd:literal) => {
concat!("ERR wrong number of arguments for '", $cmd, "' command")
};
}
pub const RESP_ERR_WRONG_NUMBER_OF_ARGUMENTS: &str = "ERR wrong number of arguments";
pub const RESP_ERR_GENERIC_NUMKEYS: &str = "ERR numkeys should be greater than 0";
pub const RESP_ERR_NO_TRANSACTION_PROCEDURE: &str = "ERR Could not get transaction procedure";
pub const RESP_ERR_GENERIC_INVALIDCURSOR: &str = "ERR invalid cursor";
pub const RESP_ERR_GENERIC_UNSUPPORTED_OPERATION: &str = "ERR unsupported operation";
pub const GENERIC_ERR_UNKNOWN_SUB_COMMAND: &str = "ERR unknown subcommand '{0}'. Try {1} HELP";
pub const GENERIC_ERR_UNKNOWN_SUB_COMMAND_NO_HELP: &str = "ERR unknown subcommand '{0}'.";
pub const GENERIC_UNKNOWN_CLIENT_TYPE: &str = "ERR Unknown client type '{0}'";
pub const GENERIC_ERR_DUPLICATE_FILTER: &str = "ERR Filter '{0}' defined multiple times";
pub const GENERIC_PUBSUB_COMMAND_NOT_ALLOWED: &str = "ERR Can't execute '{0}': only (P|S)SUBSCRIBE / (P|S)UNSUBSCRIBE / PING / QUIT are allowed in this context";
pub const GENERIC_ERR_SHOULD_BE_GREATER_THAN_ZERO: &str = "ERR {0} should be greater than 0";
pub const GENERIC_ERR_NOT_A_FLOAT_WEIGHT: &str = "ERR weight value is not a valid float";
pub const GENERIC_PARAM_SHOULD_BE_GREATER_THAN_ZERO: &str =
"ERR Parameter `{0}` should be greater than 0";
pub const GENERIC_ERR_UNKNOWN_OPTION_CONFIG_SET: &str =
"ERR Unknown option or number of arguments for CONFIG SET - '{0}'";
pub const RESP_ERR_DEBUG_DISALLOWED: &str = "ERR DEBUG command not allowed. If the enable-debug-command option is set to \"local\", you can run it from a local connection, otherwise you need to set this option in the configuration file, and then restart the server.";
pub const MAX_PARAM_NAME_LEN: usize = 128;
#[inline]
pub fn write_error_raw(output: &mut Vec<u8>, msg: &str) {
RespWriter::new_ref(output).write_error(msg);
}
#[inline]
pub fn abort_with_wrong_number_of_arguments(output: &mut Vec<u8>, cmd_name: &str) {
let clean_name = sanitize_error_str(cmd_name, MAX_PARAM_NAME_LEN);
output.extend_from_slice(b"-ERR wrong number of arguments for '");
output.extend_from_slice(clean_name.as_bytes());
output.extend_from_slice(b"' command\r\n");
}
#[inline]
pub fn abort_with_unsupported_option(output: &mut Vec<u8>, option: &str) {
let clean_opt = sanitize_error_str(option, MAX_PARAM_NAME_LEN);
output.extend_from_slice(b"-ERR Unsupported option ");
output.extend_from_slice(clean_opt.as_bytes());
output.extend_from_slice(b"\r\n");
}
#[inline]
pub fn abort_with_unknown_subcommand(output: &mut Vec<u8>, sub_command: &str, cmd_name: &str) {
let clean_sub = sanitize_error_str(sub_command, MAX_PARAM_NAME_LEN);
let clean_cmd = sanitize_error_str(cmd_name, MAX_PARAM_NAME_LEN);
output.extend_from_slice(b"-ERR unknown subcommand '");
output.extend_from_slice(clean_sub.as_bytes());
output.extend_from_slice(b"'. Try ");
output.extend_from_slice(clean_cmd.as_bytes());
output.extend_from_slice(b" HELP\r\n");
}
#[inline]
pub fn abort_with_unknown_subcommand_or_wrong_num_args(
output: &mut Vec<u8>,
sub_command: &str,
cmd_name: &str,
) {
let clean_sub = sanitize_error_str(sub_command, MAX_PARAM_NAME_LEN);
let clean_cmd = sanitize_error_str(cmd_name, MAX_PARAM_NAME_LEN);
output.extend_from_slice(b"-ERR unknown subcommand or wrong number of arguments for '");
output.extend_from_slice(clean_sub.as_bytes());
output.extend_from_slice(b"'. Try ");
output.extend_from_slice(clean_cmd.as_bytes());
output.extend_from_slice(b" HELP\r\n");
}
pub fn abort_with_error_message(output: &mut Vec<u8>, error_message: &str) {
write_error_raw(output, error_message);
}
#[inline]
pub fn write_raw(output: &mut Vec<u8>, frame: &[u8]) {
output.extend_from_slice(frame);
}
#[inline]
pub fn write_map_len_resp2(output: &mut Vec<u8>, len: usize) {
RespWriter::new_ref(output).write_map_length(len);
}
#[inline]
pub fn write_map_len(output: &mut Vec<u8>, len: usize, resp_protocol_version: u8) {
if resp_protocol_version >= 3 {
RespWriter::<&mut Vec<u8>, Resp3>::new_ref_p(output).write_map_length(len);
} else {
write_map_len_resp2(output, len);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn write_error_raw_frames_message() {
let mut out = Vec::new();
write_error_raw(&mut out, RESP_ERR_GENERIC_NOSUCHKEY);
assert_eq!(out, b"-ERR no such key\r\n");
}
#[test]
fn write_error_raw_noperm() {
let mut out = Vec::new();
write_error_raw(&mut out, RESP_ERR_NOPERM);
assert_eq!(
out,
b"-NOPERM this user has no permissions to run the command\r\n"
);
}
#[test]
fn write_error_raw_sanitizes_crlf() {
let mut out = Vec::new();
write_error_raw(&mut out, "ERR error with \r\ninjection");
assert_eq!(out, b"-ERR error with \r\n");
}
#[test]
fn wrong_num_args_formats_command_name() {
let mut out = Vec::new();
abort_with_wrong_number_of_arguments(&mut out, "GETEX");
assert_eq!(
out,
b"-ERR wrong number of arguments for 'GETEX' command\r\n"
);
}
#[test]
fn map_len_resp2_doubles_array_length() {
let mut out = Vec::new();
write_map_len_resp2(&mut out, 2);
assert_eq!(out, b"*4\r\n");
}
#[test]
fn map_len_dispatches_by_protocol_version() {
let mut out = Vec::new();
write_map_len(&mut out, 3, 3);
assert_eq!(out, b"%3\r\n");
let mut out = Vec::new();
write_map_len(&mut out, 3, 2);
assert_eq!(out, b"*6\r\n");
}
#[test]
fn raw_frames_passthrough() {
let mut out = Vec::new();
write_raw(&mut out, RESP_OK);
write_raw(&mut out, RESP_RETURN_VAL_N2);
assert_eq!(out, b"+OK\r\n:-2\r\n");
}
}