use crate::raw;
use crate::Context;
use crate::Status;
use crate::ValkeyError;
use bitflags::bitflags;
use libc::c_char;
use linkme::distributed_slice;
use std::ffi::CString;
use std::mem::MaybeUninit;
use std::os::raw::c_int;
use std::ptr;
use valkey_module_macros_internals::api;
const COMMNAD_INFO_VERSION: raw::RedisModuleCommandInfoVersion =
raw::RedisModuleCommandInfoVersion {
version: 1,
sizeof_historyentry: std::mem::size_of::<raw::RedisModuleCommandHistoryEntry>(),
sizeof_keyspec: std::mem::size_of::<raw::RedisModuleCommandKeySpec>(),
sizeof_arg: std::mem::size_of::<raw::RedisModuleCommandArg>(),
};
bitflags! {
pub struct KeySpecFlags : u32 {
const READ_ONLY = raw::REDISMODULE_CMD_KEY_RO;
const READ_WRITE = raw::REDISMODULE_CMD_KEY_RW;
const OVERWRITE = raw::REDISMODULE_CMD_KEY_OW;
const REMOVE = raw::REDISMODULE_CMD_KEY_RM;
const ACCESS = raw::REDISMODULE_CMD_KEY_ACCESS;
const UPDATE = raw::REDISMODULE_CMD_KEY_UPDATE;
const INSERT = raw::REDISMODULE_CMD_KEY_INSERT;
const DELETE = raw::REDISMODULE_CMD_KEY_DELETE;
const NOT_KEY = raw::REDISMODULE_CMD_KEY_NOT_KEY;
const INCOMPLETE = raw::REDISMODULE_CMD_KEY_INCOMPLETE;
const VARIABLE_FLAGS = raw::REDISMODULE_CMD_KEY_VARIABLE_FLAGS;
}
}
impl TryFrom<&str> for KeySpecFlags {
type Error = ValkeyError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value.to_lowercase().as_str() {
"read_only" => Ok(KeySpecFlags::READ_ONLY),
"read_write" => Ok(KeySpecFlags::READ_WRITE),
"overwrite" => Ok(KeySpecFlags::OVERWRITE),
"remove" => Ok(KeySpecFlags::REMOVE),
"access" => Ok(KeySpecFlags::ACCESS),
"update" => Ok(KeySpecFlags::UPDATE),
"insert" => Ok(KeySpecFlags::INSERT),
"delete" => Ok(KeySpecFlags::DELETE),
"not_key" => Ok(KeySpecFlags::NOT_KEY),
"incomplete" => Ok(KeySpecFlags::INCOMPLETE),
"variable_flags" => Ok(KeySpecFlags::VARIABLE_FLAGS),
_ => Err(ValkeyError::String(format!(
"Value {value} is not a valid key spec flag."
))),
}
}
}
impl From<Vec<KeySpecFlags>> for KeySpecFlags {
fn from(value: Vec<KeySpecFlags>) -> Self {
value
.into_iter()
.fold(KeySpecFlags::empty(), |a, item| a | item)
}
}
pub struct BeginSearchIndex {
index: i32,
}
pub struct BeginSearchKeyword {
keyword: String,
startfrom: i32,
}
pub enum BeginSearch {
Index(BeginSearchIndex),
Keyword(BeginSearchKeyword),
}
impl BeginSearch {
pub fn new_index(index: i32) -> BeginSearch {
BeginSearch::Index(BeginSearchIndex { index })
}
pub fn new_keyword(keyword: String, startfrom: i32) -> BeginSearch {
BeginSearch::Keyword(BeginSearchKeyword { keyword, startfrom })
}
}
impl From<&BeginSearch>
for (
raw::RedisModuleKeySpecBeginSearchType,
raw::RedisModuleCommandKeySpec__bindgen_ty_1,
)
{
fn from(value: &BeginSearch) -> Self {
match value {
BeginSearch::Index(index_spec) => (
raw::RedisModuleKeySpecBeginSearchType_REDISMODULE_KSPEC_BS_INDEX,
raw::RedisModuleCommandKeySpec__bindgen_ty_1 {
index: raw::RedisModuleCommandKeySpec__bindgen_ty_1__bindgen_ty_1 {
pos: index_spec.index,
},
},
),
BeginSearch::Keyword(keyword_spec) => {
let keyword = CString::new(keyword_spec.keyword.as_str())
.unwrap()
.into_raw();
(
raw::RedisModuleKeySpecBeginSearchType_REDISMODULE_KSPEC_BS_KEYWORD,
raw::RedisModuleCommandKeySpec__bindgen_ty_1 {
keyword: raw::RedisModuleCommandKeySpec__bindgen_ty_1__bindgen_ty_2 {
keyword,
startfrom: keyword_spec.startfrom,
},
},
)
}
}
}
}
pub struct FindKeysRange {
last_key: i32,
steps: i32,
limit: i32,
}
pub struct FindKeysNum {
key_num_idx: i32,
first_key: i32,
key_step: i32,
}
pub enum FindKeys {
Range(FindKeysRange),
Keynum(FindKeysNum),
}
impl FindKeys {
pub fn new_range(last_key: i32, steps: i32, limit: i32) -> FindKeys {
FindKeys::Range(FindKeysRange {
last_key,
steps,
limit,
})
}
pub fn new_keys_num(key_num_idx: i32, first_key: i32, key_step: i32) -> FindKeys {
FindKeys::Keynum(FindKeysNum {
key_num_idx,
first_key,
key_step,
})
}
}
impl From<&FindKeys>
for (
raw::RedisModuleKeySpecFindKeysType,
raw::RedisModuleCommandKeySpec__bindgen_ty_2,
)
{
fn from(value: &FindKeys) -> Self {
match value {
FindKeys::Range(range_spec) => (
raw::RedisModuleKeySpecFindKeysType_REDISMODULE_KSPEC_FK_RANGE,
raw::RedisModuleCommandKeySpec__bindgen_ty_2 {
range: raw::RedisModuleCommandKeySpec__bindgen_ty_2__bindgen_ty_1 {
lastkey: range_spec.last_key,
keystep: range_spec.steps,
limit: range_spec.limit,
},
},
),
FindKeys::Keynum(keynum_spec) => (
raw::RedisModuleKeySpecFindKeysType_REDISMODULE_KSPEC_FK_KEYNUM,
raw::RedisModuleCommandKeySpec__bindgen_ty_2 {
keynum: raw::RedisModuleCommandKeySpec__bindgen_ty_2__bindgen_ty_2 {
keynumidx: keynum_spec.key_num_idx,
firstkey: keynum_spec.first_key,
keystep: keynum_spec.key_step,
},
},
),
}
}
}
pub struct KeySpec {
notes: Option<String>,
flags: KeySpecFlags,
begin_search: BeginSearch,
find_keys: FindKeys,
}
impl KeySpec {
pub fn new(
notes: Option<String>,
flags: KeySpecFlags,
begin_search: BeginSearch,
find_keys: FindKeys,
) -> KeySpec {
KeySpec {
notes,
flags,
begin_search,
find_keys,
}
}
}
impl From<&KeySpec> for raw::RedisModuleCommandKeySpec {
fn from(value: &KeySpec) -> Self {
let (begin_search_type, bs) = (&value.begin_search).into();
let (find_keys_type, fk) = (&value.find_keys).into();
raw::RedisModuleCommandKeySpec {
notes: value
.notes
.as_ref()
.map(|v| CString::new(v.as_str()).unwrap().into_raw())
.unwrap_or(ptr::null_mut()),
flags: value.flags.bits() as u64,
begin_search_type,
bs,
find_keys_type,
fk,
}
}
}
type CommandCallback =
extern "C" fn(*mut raw::RedisModuleCtx, *mut *mut raw::RedisModuleString, i32) -> i32;
pub struct CommandInfo {
name: String,
flags: Option<String>,
summary: Option<String>,
complexity: Option<String>,
since: Option<String>,
tips: Option<String>,
arity: i64,
key_spec: Vec<KeySpec>,
callback: CommandCallback,
}
impl CommandInfo {
pub fn new(
name: String,
flags: Option<String>,
summary: Option<String>,
complexity: Option<String>,
since: Option<String>,
tips: Option<String>,
arity: i64,
key_spec: Vec<KeySpec>,
callback: CommandCallback,
) -> CommandInfo {
CommandInfo {
name,
flags,
summary,
complexity,
since,
tips,
arity,
key_spec,
callback,
}
}
}
#[distributed_slice()]
pub static COMMANDS_LIST: [fn() -> Result<CommandInfo, ValkeyError>] = [..];
pub fn get_redis_key_spec(key_spec: Vec<KeySpec>) -> Vec<raw::RedisModuleCommandKeySpec> {
let mut redis_key_spec: Vec<raw::RedisModuleCommandKeySpec> =
key_spec.into_iter().map(|v| (&v).into()).collect();
let zerod: raw::RedisModuleCommandKeySpec = unsafe { MaybeUninit::zeroed().assume_init() };
redis_key_spec.push(zerod);
redis_key_spec
}
api! {[
RedisModule_CreateCommand,
RedisModule_GetCommand,
RedisModule_SetCommandInfo,
],
fn register_commands_internal(ctx: &Context) -> Result<(), ValkeyError> {
COMMANDS_LIST.iter().try_for_each(|command| {
let command_info = command()?;
let name: CString = CString::new(command_info.name.as_str()).unwrap();
let flags = command_info.flags.as_deref().unwrap_or("").to_owned();
let flags = CString::new(flags).map_err(|e| ValkeyError::String(e.to_string()))?;
if unsafe {
RedisModule_CreateCommand(
ctx.ctx,
name.as_ptr(),
Some(command_info.callback),
flags.as_ptr(),
0,
0,
0,
)
} == raw::Status::Err as i32
{
return Err(ValkeyError::String(format!(
"Failed register command {}.",
command_info.name
)));
}
let command = unsafe { RedisModule_GetCommand(ctx.ctx, name.as_ptr()) };
if command.is_null() {
return Err(ValkeyError::String(format!(
"Failed finding command {} after registration.",
command_info.name
)));
}
let summary = command_info
.summary
.as_ref()
.map(|v| Some(CString::new(v.as_str()).unwrap()))
.unwrap_or(None);
let complexity = command_info
.complexity
.as_ref()
.map(|v| Some(CString::new(v.as_str()).unwrap()))
.unwrap_or(None);
let since = command_info
.since
.as_ref()
.map(|v| Some(CString::new(v.as_str()).unwrap()))
.unwrap_or(None);
let tips = command_info
.tips
.as_ref()
.map(|v| Some(CString::new(v.as_str()).unwrap()))
.unwrap_or(None);
let key_specs = get_redis_key_spec(command_info.key_spec);
let mut redis_command_info = raw::RedisModuleCommandInfo {
version: &COMMNAD_INFO_VERSION,
summary: summary.as_ref().map(|v| v.as_ptr()).unwrap_or(ptr::null_mut()),
complexity: complexity.as_ref().map(|v| v.as_ptr()).unwrap_or(ptr::null_mut()),
since: since.as_ref().map(|v| v.as_ptr()).unwrap_or(ptr::null_mut()),
history: ptr::null_mut(), tips: tips.as_ref().map(|v| v.as_ptr()).unwrap_or(ptr::null_mut()),
arity: command_info.arity as c_int,
key_specs: key_specs.as_ptr() as *mut raw::RedisModuleCommandKeySpec,
args: ptr::null_mut(),
};
if unsafe { RedisModule_SetCommandInfo(command, &mut redis_command_info as *mut raw::RedisModuleCommandInfo) } == raw::Status::Err as i32 {
return Err(ValkeyError::String(format!(
"Failed setting info for command {}.",
command_info.name
)));
}
key_specs.into_iter().for_each(|v|{
if !v.notes.is_null() {
unsafe{drop(CString::from_raw(v.notes as *mut c_char))};
}
if v.begin_search_type == raw::RedisModuleKeySpecBeginSearchType_REDISMODULE_KSPEC_BS_KEYWORD {
let keyword = unsafe{v.bs.keyword.keyword};
if !keyword.is_null() {
unsafe{drop(CString::from_raw(v.bs.keyword.keyword as *mut c_char))};
}
}
});
Ok(())
})
}
}
#[cfg(all(
any(
feature = "min-valkey-compatibility-version-8-0",
feature = "min-redis-compatibility-version-7-2",
feature = "min-redis-compatibility-version-7-0"
),
not(any(
feature = "min-redis-compatibility-version-6-2",
feature = "min-redis-compatibility-version-6-0"
))
))]
pub fn register_commands(ctx: &Context) -> Status {
register_commands_internal(ctx).map_or_else(
|e| {
ctx.log_warning(&e.to_string());
Status::Err
},
|_| Status::Ok,
)
}
#[cfg(all(
any(
feature = "min-redis-compatibility-version-6-2",
feature = "min-redis-compatibility-version-6-0"
),
not(any(
feature = "min-valkey-compatibility-version-8-0",
feature = "min-redis-compatibility-version-7-2",
feature = "min-redis-compatibility-version-7-0"
))
))]
pub fn register_commands(ctx: &Context) -> Status {
register_commands_internal(ctx).map_or_else(
|e| {
ctx.log_warning(&e.to_string());
Status::Err
},
|v| {
v.map_or_else(
|e| {
ctx.log_warning(&e.to_string());
Status::Err
},
|_| Status::Ok,
)
},
)
}