1use crate::raw;
2use crate::Context;
3use crate::Status;
4use crate::ValkeyError;
5use bitflags::bitflags;
6use libc::c_char;
7use linkme::distributed_slice;
8use std::ffi::CString;
9use std::mem::MaybeUninit;
10use std::os::raw::c_int;
11use std::ptr;
12use valkey_module_macros_internals::api;
13
14const COMMNAD_INFO_VERSION: raw::RedisModuleCommandInfoVersion =
15 raw::RedisModuleCommandInfoVersion {
16 version: 1,
17 sizeof_historyentry: std::mem::size_of::<raw::RedisModuleCommandHistoryEntry>(),
18 sizeof_keyspec: std::mem::size_of::<raw::RedisModuleCommandKeySpec>(),
19 sizeof_arg: std::mem::size_of::<raw::RedisModuleCommandArg>(),
20 };
21
22bitflags! {
23 pub struct KeySpecFlags : u32 {
39 const READ_ONLY = raw::REDISMODULE_CMD_KEY_RO;
41
42 const READ_WRITE = raw::REDISMODULE_CMD_KEY_RW;
44
45 const OVERWRITE = raw::REDISMODULE_CMD_KEY_OW;
47
48 const REMOVE = raw::REDISMODULE_CMD_KEY_RM;
50
51 const ACCESS = raw::REDISMODULE_CMD_KEY_ACCESS;
53
54 const UPDATE = raw::REDISMODULE_CMD_KEY_UPDATE;
56
57 const INSERT = raw::REDISMODULE_CMD_KEY_INSERT;
59
60 const DELETE = raw::REDISMODULE_CMD_KEY_DELETE;
62
63 const NOT_KEY = raw::REDISMODULE_CMD_KEY_NOT_KEY;
65
66 const INCOMPLETE = raw::REDISMODULE_CMD_KEY_INCOMPLETE;
68
69 const VARIABLE_FLAGS = raw::REDISMODULE_CMD_KEY_VARIABLE_FLAGS;
71 }
72}
73
74impl TryFrom<&str> for KeySpecFlags {
75 type Error = ValkeyError;
76 fn try_from(value: &str) -> Result<Self, Self::Error> {
77 match value.to_lowercase().as_str() {
78 "read_only" => Ok(KeySpecFlags::READ_ONLY),
79 "read_write" => Ok(KeySpecFlags::READ_WRITE),
80 "overwrite" => Ok(KeySpecFlags::OVERWRITE),
81 "remove" => Ok(KeySpecFlags::REMOVE),
82 "access" => Ok(KeySpecFlags::ACCESS),
83 "update" => Ok(KeySpecFlags::UPDATE),
84 "insert" => Ok(KeySpecFlags::INSERT),
85 "delete" => Ok(KeySpecFlags::DELETE),
86 "not_key" => Ok(KeySpecFlags::NOT_KEY),
87 "incomplete" => Ok(KeySpecFlags::INCOMPLETE),
88 "variable_flags" => Ok(KeySpecFlags::VARIABLE_FLAGS),
89 _ => Err(ValkeyError::String(format!(
90 "Value {value} is not a valid key spec flag."
91 ))),
92 }
93 }
94}
95
96impl From<Vec<KeySpecFlags>> for KeySpecFlags {
97 fn from(value: Vec<KeySpecFlags>) -> Self {
98 value
99 .into_iter()
100 .fold(KeySpecFlags::empty(), |a, item| a | item)
101 }
102}
103
104pub struct BeginSearchIndex {
108 index: i32,
109}
110
111pub struct BeginSearchKeyword {
115 keyword: String,
116 startfrom: i32,
117}
118
119pub enum BeginSearch {
124 Index(BeginSearchIndex),
125 Keyword(BeginSearchKeyword),
126}
127
128impl BeginSearch {
129 pub fn new_index(index: i32) -> BeginSearch {
130 BeginSearch::Index(BeginSearchIndex { index })
131 }
132
133 pub fn new_keyword(keyword: String, startfrom: i32) -> BeginSearch {
134 BeginSearch::Keyword(BeginSearchKeyword { keyword, startfrom })
135 }
136}
137
138impl From<&BeginSearch>
139 for (
140 raw::RedisModuleKeySpecBeginSearchType,
141 raw::RedisModuleCommandKeySpec__bindgen_ty_1,
142 )
143{
144 fn from(value: &BeginSearch) -> Self {
145 match value {
146 BeginSearch::Index(index_spec) => (
147 raw::RedisModuleKeySpecBeginSearchType_REDISMODULE_KSPEC_BS_INDEX,
148 raw::RedisModuleCommandKeySpec__bindgen_ty_1 {
149 index: raw::RedisModuleCommandKeySpec__bindgen_ty_1__bindgen_ty_1 {
150 pos: index_spec.index,
151 },
152 },
153 ),
154 BeginSearch::Keyword(keyword_spec) => {
155 let keyword = CString::new(keyword_spec.keyword.as_str())
156 .unwrap()
157 .into_raw();
158 (
159 raw::RedisModuleKeySpecBeginSearchType_REDISMODULE_KSPEC_BS_KEYWORD,
160 raw::RedisModuleCommandKeySpec__bindgen_ty_1 {
161 keyword: raw::RedisModuleCommandKeySpec__bindgen_ty_1__bindgen_ty_2 {
162 keyword,
163 startfrom: keyword_spec.startfrom,
164 },
165 },
166 )
167 }
168 }
169 }
170}
171
172pub struct FindKeysRange {
183 last_key: i32,
184 steps: i32,
185 limit: i32,
186}
187
188pub struct FindKeysNum {
197 key_num_idx: i32,
198 first_key: i32,
199 key_step: i32,
200}
201
202pub enum FindKeys {
211 Range(FindKeysRange),
212 Keynum(FindKeysNum),
213}
214
215impl FindKeys {
216 pub fn new_range(last_key: i32, steps: i32, limit: i32) -> FindKeys {
217 FindKeys::Range(FindKeysRange {
218 last_key,
219 steps,
220 limit,
221 })
222 }
223
224 pub fn new_keys_num(key_num_idx: i32, first_key: i32, key_step: i32) -> FindKeys {
225 FindKeys::Keynum(FindKeysNum {
226 key_num_idx,
227 first_key,
228 key_step,
229 })
230 }
231}
232
233impl From<&FindKeys>
234 for (
235 raw::RedisModuleKeySpecFindKeysType,
236 raw::RedisModuleCommandKeySpec__bindgen_ty_2,
237 )
238{
239 fn from(value: &FindKeys) -> Self {
240 match value {
241 FindKeys::Range(range_spec) => (
242 raw::RedisModuleKeySpecFindKeysType_REDISMODULE_KSPEC_FK_RANGE,
243 raw::RedisModuleCommandKeySpec__bindgen_ty_2 {
244 range: raw::RedisModuleCommandKeySpec__bindgen_ty_2__bindgen_ty_1 {
245 lastkey: range_spec.last_key,
246 keystep: range_spec.steps,
247 limit: range_spec.limit,
248 },
249 },
250 ),
251 FindKeys::Keynum(keynum_spec) => (
252 raw::RedisModuleKeySpecFindKeysType_REDISMODULE_KSPEC_FK_KEYNUM,
253 raw::RedisModuleCommandKeySpec__bindgen_ty_2 {
254 keynum: raw::RedisModuleCommandKeySpec__bindgen_ty_2__bindgen_ty_2 {
255 keynumidx: keynum_spec.key_num_idx,
256 firstkey: keynum_spec.first_key,
257 keystep: keynum_spec.key_step,
258 },
259 },
260 ),
261 }
262 }
263}
264
265pub struct KeySpec {
270 notes: Option<String>,
271 flags: KeySpecFlags,
272 begin_search: BeginSearch,
273 find_keys: FindKeys,
274}
275
276impl KeySpec {
277 pub fn new(
278 notes: Option<String>,
279 flags: KeySpecFlags,
280 begin_search: BeginSearch,
281 find_keys: FindKeys,
282 ) -> KeySpec {
283 KeySpec {
284 notes,
285 flags,
286 begin_search,
287 find_keys,
288 }
289 }
290}
291
292impl From<&KeySpec> for raw::RedisModuleCommandKeySpec {
293 fn from(value: &KeySpec) -> Self {
294 let (begin_search_type, bs) = (&value.begin_search).into();
295 let (find_keys_type, fk) = (&value.find_keys).into();
296 raw::RedisModuleCommandKeySpec {
297 notes: value
298 .notes
299 .as_ref()
300 .map(|v| CString::new(v.as_str()).unwrap().into_raw())
301 .unwrap_or(ptr::null_mut()),
302 flags: value.flags.bits() as u64,
303 begin_search_type,
304 bs,
305 find_keys_type,
306 fk,
307 }
308 }
309}
310
311type CommandCallback =
312 extern "C" fn(*mut raw::RedisModuleCtx, *mut *mut raw::RedisModuleString, i32) -> i32;
313
314pub struct CommandInfo {
316 name: String,
317 flags: Option<String>,
318 summary: Option<String>,
319 complexity: Option<String>,
320 since: Option<String>,
321 tips: Option<String>,
322 arity: i64,
323 key_spec: Vec<KeySpec>,
324 callback: CommandCallback,
325}
326
327impl CommandInfo {
328 pub fn new(
329 name: String,
330 flags: Option<String>,
331 summary: Option<String>,
332 complexity: Option<String>,
333 since: Option<String>,
334 tips: Option<String>,
335 arity: i64,
336 key_spec: Vec<KeySpec>,
337 callback: CommandCallback,
338 ) -> CommandInfo {
339 CommandInfo {
340 name,
341 flags,
342 summary,
343 complexity,
344 since,
345 tips,
346 arity,
347 key_spec,
348 callback,
349 }
350 }
351}
352
353#[distributed_slice()]
354pub static COMMANDS_LIST: [fn() -> Result<CommandInfo, ValkeyError>] = [..];
355
356pub fn get_redis_key_spec(key_spec: Vec<KeySpec>) -> Vec<raw::RedisModuleCommandKeySpec> {
357 let mut redis_key_spec: Vec<raw::RedisModuleCommandKeySpec> =
358 key_spec.into_iter().map(|v| (&v).into()).collect();
359 let zerod: raw::RedisModuleCommandKeySpec = unsafe { MaybeUninit::zeroed().assume_init() };
360 redis_key_spec.push(zerod);
361 redis_key_spec
362}
363
364api! {[
365 RedisModule_CreateCommand,
366 RedisModule_GetCommand,
367 RedisModule_SetCommandInfo,
368 ],
369 fn register_commands_internal(ctx: &Context) -> Result<(), ValkeyError> {
371 COMMANDS_LIST.iter().try_for_each(|command| {
372 let command_info = command()?;
373 let name: CString = CString::new(command_info.name.as_str()).unwrap();
374 let flags = command_info.flags.as_deref().unwrap_or("").to_owned();
375 let flags = CString::new(flags).map_err(|e| ValkeyError::String(e.to_string()))?;
376
377 if unsafe {
378 RedisModule_CreateCommand(
379 ctx.ctx,
380 name.as_ptr(),
381 Some(command_info.callback),
382 flags.as_ptr(),
383 0,
384 0,
385 0,
386 )
387 } == raw::Status::Err as i32
388 {
389 return Err(ValkeyError::String(format!(
390 "Failed register command {}.",
391 command_info.name
392 )));
393 }
394
395 let command = unsafe { RedisModule_GetCommand(ctx.ctx, name.as_ptr()) };
397
398 if command.is_null() {
399 return Err(ValkeyError::String(format!(
400 "Failed finding command {} after registration.",
401 command_info.name
402 )));
403 }
404
405 let summary = command_info
406 .summary
407 .as_ref()
408 .map(|v| Some(CString::new(v.as_str()).unwrap()))
409 .unwrap_or(None);
410 let complexity = command_info
411 .complexity
412 .as_ref()
413 .map(|v| Some(CString::new(v.as_str()).unwrap()))
414 .unwrap_or(None);
415 let since = command_info
416 .since
417 .as_ref()
418 .map(|v| Some(CString::new(v.as_str()).unwrap()))
419 .unwrap_or(None);
420 let tips = command_info
421 .tips
422 .as_ref()
423 .map(|v| Some(CString::new(v.as_str()).unwrap()))
424 .unwrap_or(None);
425
426 let key_specs = get_redis_key_spec(command_info.key_spec);
427
428 let mut redis_command_info = raw::RedisModuleCommandInfo {
429 version: &COMMNAD_INFO_VERSION,
430 summary: summary.as_ref().map(|v| v.as_ptr()).unwrap_or(ptr::null_mut()),
431 complexity: complexity.as_ref().map(|v| v.as_ptr()).unwrap_or(ptr::null_mut()),
432 since: since.as_ref().map(|v| v.as_ptr()).unwrap_or(ptr::null_mut()),
433 history: ptr::null_mut(), tips: tips.as_ref().map(|v| v.as_ptr()).unwrap_or(ptr::null_mut()),
435 arity: command_info.arity as c_int,
436 key_specs: key_specs.as_ptr() as *mut raw::RedisModuleCommandKeySpec,
437 args: ptr::null_mut(),
438 };
439
440 if unsafe { RedisModule_SetCommandInfo(command, &mut redis_command_info as *mut raw::RedisModuleCommandInfo) } == raw::Status::Err as i32 {
441 return Err(ValkeyError::String(format!(
442 "Failed setting info for command {}.",
443 command_info.name
444 )));
445 }
446
447 key_specs.into_iter().for_each(|v|{
449 if !v.notes.is_null() {
450 unsafe{drop(CString::from_raw(v.notes as *mut c_char))};
451 }
452 if v.begin_search_type == raw::RedisModuleKeySpecBeginSearchType_REDISMODULE_KSPEC_BS_KEYWORD {
453 let keyword = unsafe{v.bs.keyword.keyword};
454 if !keyword.is_null() {
455 unsafe{drop(CString::from_raw(v.bs.keyword.keyword as *mut c_char))};
456 }
457 }
458 });
459
460 Ok(())
461 })
462 }
463}
464
465#[cfg(all(
466 any(
467 feature = "min-valkey-compatibility-version-8-0",
468 feature = "min-redis-compatibility-version-7-2",
469 feature = "min-redis-compatibility-version-7-0"
470 ),
471 not(any(
472 feature = "min-redis-compatibility-version-6-2",
473 feature = "min-redis-compatibility-version-6-0"
474 ))
475))]
476pub fn register_commands(ctx: &Context) -> Status {
477 register_commands_internal(ctx).map_or_else(
478 |e| {
479 ctx.log_warning(&e.to_string());
480 Status::Err
481 },
482 |_| Status::Ok,
483 )
484}
485
486#[cfg(all(
487 any(
488 feature = "min-redis-compatibility-version-6-2",
489 feature = "min-redis-compatibility-version-6-0"
490 ),
491 not(any(
492 feature = "min-valkey-compatibility-version-8-0",
493 feature = "min-redis-compatibility-version-7-2",
494 feature = "min-redis-compatibility-version-7-0"
495 ))
496))]
497pub fn register_commands(ctx: &Context) -> Status {
498 register_commands_internal(ctx).map_or_else(
499 |e| {
500 ctx.log_warning(&e.to_string());
501 Status::Err
502 },
503 |v| {
504 v.map_or_else(
505 |e| {
506 ctx.log_warning(&e.to_string());
507 Status::Err
508 },
509 |_| Status::Ok,
510 )
511 },
512 )
513}