1use crate::raw;
2use crate::Context;
3use crate::RedisError;
4use crate::Status;
5use bitflags::bitflags;
6use libc::c_char;
7use linkme::distributed_slice;
8use redis_module_macros_internals::api;
9use std::ffi::CString;
10use std::mem::MaybeUninit;
11use std::os::raw::c_int;
12use std::ptr;
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 = RedisError;
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(RedisError::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, RedisError>] = [..];
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<(), RedisError> {
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 = CString::new(
375 command_info
376 .flags
377 .as_deref()
378 .unwrap_or(""),
379 )
380 .unwrap();
381
382 if unsafe {
383 RedisModule_CreateCommand(
384 ctx.ctx,
385 name.as_ptr(),
386 Some(command_info.callback),
387 flags.as_ptr(),
388 0,
389 0,
390 0,
391 )
392 } == raw::Status::Err as i32
393 {
394 return Err(RedisError::String(format!(
395 "Failed register command {}.",
396 command_info.name
397 )));
398 }
399
400 let command = unsafe { RedisModule_GetCommand(ctx.ctx, name.as_ptr()) };
402
403 if command.is_null() {
404 return Err(RedisError::String(format!(
405 "Failed finding command {} after registration.",
406 command_info.name
407 )));
408 }
409
410 let summary = command_info
411 .summary
412 .as_ref()
413 .map(|v| Some(CString::new(v.as_str()).unwrap()))
414 .unwrap_or(None);
415 let complexity = command_info
416 .complexity
417 .as_ref()
418 .map(|v| Some(CString::new(v.as_str()).unwrap()))
419 .unwrap_or(None);
420 let since = command_info
421 .since
422 .as_ref()
423 .map(|v| Some(CString::new(v.as_str()).unwrap()))
424 .unwrap_or(None);
425 let tips = command_info
426 .tips
427 .as_ref()
428 .map(|v| Some(CString::new(v.as_str()).unwrap()))
429 .unwrap_or(None);
430
431 let key_specs = get_redis_key_spec(command_info.key_spec);
432
433 let mut redis_command_info = raw::RedisModuleCommandInfo {
434 version: &COMMNAD_INFO_VERSION,
435 summary: summary.as_ref().map(|v| v.as_ptr()).unwrap_or(ptr::null_mut()),
436 complexity: complexity.as_ref().map(|v| v.as_ptr()).unwrap_or(ptr::null_mut()),
437 since: since.as_ref().map(|v| v.as_ptr()).unwrap_or(ptr::null_mut()),
438 history: ptr::null_mut(), tips: tips.as_ref().map(|v| v.as_ptr()).unwrap_or(ptr::null_mut()),
440 arity: command_info.arity as c_int,
441 key_specs: key_specs.as_ptr() as *mut raw::RedisModuleCommandKeySpec,
442 args: ptr::null_mut(),
443 };
444
445 if unsafe { RedisModule_SetCommandInfo(command, &mut redis_command_info as *mut raw::RedisModuleCommandInfo) } == raw::Status::Err as i32 {
446 return Err(RedisError::String(format!(
447 "Failed setting info for command {}.",
448 command_info.name
449 )));
450 }
451
452 key_specs.into_iter().for_each(|v|{
454 if !v.notes.is_null() {
455 unsafe{CString::from_raw(v.notes as *mut c_char)};
456 }
457 if v.begin_search_type == raw::RedisModuleKeySpecBeginSearchType_REDISMODULE_KSPEC_BS_KEYWORD {
458 let keyword = unsafe{v.bs.keyword.keyword};
459 if !keyword.is_null() {
460 unsafe{CString::from_raw(v.bs.keyword.keyword as *mut c_char)};
461 }
462 }
463 });
464
465 Ok(())
466 })
467 }
468}
469
470#[cfg(all(
471 any(
472 feature = "min-redis-compatibility-version-7-2",
473 feature = "min-redis-compatibility-version-7-0"
474 ),
475 not(any(
476 feature = "min-redis-compatibility-version-6-2",
477 feature = "min-redis-compatibility-version-6-0"
478 ))
479))]
480pub fn register_commands(ctx: &Context) -> Status {
481 register_commands_internal(ctx).map_or_else(
482 |e| {
483 ctx.log_warning(&e.to_string());
484 Status::Err
485 },
486 |_| Status::Ok,
487 )
488}
489
490#[cfg(all(
491 any(
492 feature = "min-redis-compatibility-version-6-2",
493 feature = "min-redis-compatibility-version-6-0"
494 ),
495 not(any(
496 feature = "min-redis-compatibility-version-7-2",
497 feature = "min-redis-compatibility-version-7-0"
498 ))
499))]
500pub fn register_commands(ctx: &Context) -> Status {
501 register_commands_internal(ctx).map_or_else(
502 |e| {
503 ctx.log_warning(&e.to_string());
504 Status::Err
505 },
506 |v| {
507 v.map_or_else(
508 |e| {
509 ctx.log_warning(&e.to_string());
510 Status::Err
511 },
512 |_| Status::Ok,
513 )
514 },
515 )
516}