1#[macro_export]
2macro_rules! redis_command {
3 (
4 $ctx:expr,
5 $command_name:expr,
6 $command_handler:expr,
7 $command_flags:expr,
8 $firstkey:expr,
9 $lastkey:expr,
10 $keystep:expr
11 $(,
12 $command_acl_categories:expr
13 )?
14 ) => {{
15 let name = CString::new($command_name).unwrap();
16 let flags = CString::new($command_flags).unwrap();
17 extern "C" fn __do_command(
19 ctx: *mut $crate::raw::RedisModuleCtx,
20 argv: *mut *mut $crate::raw::RedisModuleString,
21 argc: c_int,
22 ) -> c_int {
23 let context = $crate::Context::new(ctx);
24 let args = $crate::decode_args(ctx, argv, argc);
25 let response = $command_handler(&context, args);
26 context.reply(response.map(|v| v.into())) as c_int
27 }
28
29 if unsafe {
30 $crate::raw::RedisModule_CreateCommand.unwrap()(
31 $ctx,
32 name.as_ptr(),
33 Some(__do_command),
34 flags.as_ptr(),
35 $firstkey,
36 $lastkey,
37 $keystep,
38 )
39 } == $crate::raw::Status::Err as c_int
40 {
41 return $crate::raw::Status::Err as c_int;
42 }
43
44 $(
45 let context = $crate::Context::new($ctx);
46 let acl_categories_to_add = CString::new($command_acl_categories).unwrap();
47 #[cfg(feature = "min-valkey-compatibility-version-8-0")]
48 context.set_acl_category(name.as_ptr(), acl_categories_to_add.as_ptr());
49 )?
50 }};
51}
52
53#[macro_export]
54macro_rules! redis_event_handler {
55 (
56 $ctx: expr,
57 $event_type: expr,
58 $event_handler: expr
59 ) => {{
60 extern "C" fn __handle_event(
61 ctx: *mut $crate::raw::RedisModuleCtx,
62 event_type: c_int,
63 event: *const c_char,
64 key: *mut $crate::raw::RedisModuleString,
65 ) -> c_int {
66 let context = $crate::Context::new(ctx);
67
68 let redis_key = $crate::ValkeyString::string_as_slice(key);
69 let event_str = unsafe { CStr::from_ptr(event) };
70 $event_handler(
71 &context,
72 $crate::NotifyEvent::from_bits_truncate(event_type),
73 event_str.to_str().unwrap(),
74 redis_key,
75 );
76
77 $crate::raw::Status::Ok as c_int
78 }
79
80 let all_available_notification_flags = $crate::raw::get_keyspace_notification_flags_all();
81 let available_wanted_notification_flags = $event_type.intersection(all_available_notification_flags);
82 if !all_available_notification_flags.contains($event_type) {
83 let not_supported = $event_type.difference(all_available_notification_flags);
84 $crate::Context::new($ctx).log_notice(&format!(
85 "These event notification flags set aren't supported: {not_supported:?}. These flags will be used: {available_wanted_notification_flags:?}"
86 ));
87 }
88
89 if !available_wanted_notification_flags.is_empty() && unsafe {
90 $crate::raw::RedisModule_SubscribeToKeyspaceEvents.unwrap()(
91 $ctx,
92 available_wanted_notification_flags.bits(),
93 Some(__handle_event),
94 )
95 } == $crate::raw::Status::Err as c_int
96 {
97 return $crate::raw::Status::Err as c_int;
98 }
99 }};
100}
101
102#[macro_export]
105macro_rules! define_extern_c_filter_func {
106 ($filter_func_name:ident, $filter_func:expr) => {
107 extern "C" fn $filter_func_name(ctx: *mut valkey_module::RedisModuleCommandFilterCtx) {
108 $filter_func(ctx);
109 }
110 };
111}
112
113#[macro_export]
118macro_rules! valkey_module {
119 (
120 name: $module_name:expr,
121 version: $module_version:expr,
122 allocator: ($allocator_type:ty, $allocator_init:expr),
126 data_types: [
127 $($data_type:ident),* $(,)*
128 ],
129 $(preload: $preload_func:ident,)* $(,)*
130 $(init: $init_func:ident,)* $(,)*
131 $(deinit: $deinit_func:ident,)* $(,)*
132 $(info: $info_func:ident,)?
133 $(auth: [
134 $($auth_callback:expr),* $(,)*
135 ],)?
136 $(acl_categories: [
137 $($acl_category:expr),* $(,)*
138 ])?
139 commands: [
140 $([
141 $name:expr,
142 $command:expr,
143 $flags:expr,
144 $firstkey:expr,
145 $lastkey:expr,
146 $keystep:expr
147 $(,
148 $command_acl_categories:expr
149 )?
150 ]),* $(,)?
151 ] $(,)*
152 $(
153 filters: [
154 $([
155 $filter_func:ident,
156 $filter_flags:expr
157 ]),* $(,)?
158 ] $(,)*
159 )?
160 $(event_handlers: [
161 $([
162 $(@$event_type:ident) +:
163 $event_handler:expr
164 ]),* $(,)*
165 ] $(,)* )?
166 $(configurations: [
167 $(i64:[$([
168 $i64_configuration_name:expr,
169 $i64_configuration_val:expr,
170 $i64_default:expr,
171 $i64_min:expr,
172 $i64_max:expr,
173 $i64_flags_options:expr,
174 $i64_on_changed:expr $(, $i64_on_set:expr)?
175 ]),* $(,)*],)?
176 $(string:[$([
177 $string_configuration_name:expr,
178 $string_configuration_val:expr,
179 $string_default:expr,
180 $string_flags_options:expr,
181 $string_on_changed:expr $(, $string_on_set:expr)?
182 ]),* $(,)*],)?
183 $(bool:[$([
184 $bool_configuration_name:expr,
185 $bool_configuration_val:expr,
186 $bool_default:expr,
187 $bool_flags_options:expr,
188 $bool_on_changed:expr $(, $bool_on_set:expr)?
189 ]),* $(,)*],)?
190 $(enum:[$([
191 $enum_configuration_name:expr,
192 $enum_configuration_val:expr,
193 $enum_default:expr,
194 $enum_flags_options:expr,
195 $enum_on_changed:expr $(, $enum_on_set:expr)?
196 ]),* $(,)*],)?
197 $(module_args_as_configuration:$use_module_args:expr,)?
198 $(module_config_get:$module_config_get_command:expr,)?
199 $(module_config_set:$module_config_set_command:expr,)?
200 ])?
201 ) => {
202 #[global_allocator]
204 static REDIS_MODULE_ALLOCATOR: $allocator_type = $allocator_init;
205
206 use std::sync::OnceLock;
207 static CMD_FILTERS: OnceLock<Vec<valkey_module::CommandFilter>> = OnceLock::new();
208
209 $(
211 #[valkey_module_macros::info_command_handler]
212 #[inline]
213 fn module_info(ctx: &InfoContext, for_crash_report: bool) -> ValkeyResult<()> {
214 $info_func(ctx, for_crash_report);
215
216 Ok(())
217 }
218 )?
219
220 extern "C" fn __info_func(
221 ctx: *mut $crate::raw::RedisModuleInfoCtx,
222 for_crash_report: i32,
223 ) {
224 $crate::basic_info_command_handler(&$crate::InfoContext::new(ctx), for_crash_report == 1);
225 }
226
227 #[no_mangle]
228 #[allow(non_snake_case)]
229 pub unsafe extern "C" fn RedisModule_OnLoad(
230 ctx: *mut $crate::raw::RedisModuleCtx,
231 argv: *mut *mut $crate::raw::RedisModuleString,
232 argc: std::os::raw::c_int,
233 ) -> std::os::raw::c_int {
234 use std::os::raw::{c_int, c_char};
235 use std::ffi::{CString, CStr};
236
237 use $crate::raw;
238 use $crate::ValkeyString;
239 use $crate::server_events::register_server_events;
240 use $crate::configuration::register_i64_configuration;
241 use $crate::configuration::register_string_configuration;
242 use $crate::configuration::register_bool_configuration;
243 use $crate::configuration::register_enum_configuration;
244 use $crate::configuration::module_config_get;
245 use $crate::configuration::module_config_set;
246 use $crate::configuration::get_i64_default_config_value;
247 use $crate::configuration::get_string_default_config_value;
248 use $crate::configuration::get_bool_default_config_value;
249 use $crate::configuration::get_enum_default_config_value;
250
251 let mut name_buffer = [0; 64];
255 unsafe {
256 std::ptr::copy(
257 $module_name.as_ptr(),
258 name_buffer.as_mut_ptr(),
259 $module_name.len(),
260 );
261 }
262
263 let module_version = $module_version as c_int;
264
265 if !raw::use_redis_module_api() {
269 let status = unsafe {
270 raw::Export_ValkeyModule_Init(
271 ctx as *mut raw::ValkeyModuleCtx,
272 name_buffer.as_ptr().cast::<c_char>(),
273 module_version,
274 raw::VALKEYMODULE_APIVER_1 as c_int,
275 )
276 };
277 if status == raw::Status::Err as c_int {
278 return raw::Status::Err as c_int;
279 }
280 }
281
282 let status = unsafe {
286 raw::Export_RedisModule_Init(
287 ctx,
288 name_buffer.as_ptr().cast::<c_char>(),
289 module_version,
290 raw::REDISMODULE_APIVER_1 as c_int,
291 )
292 };
293 if status == raw::Status::Err as c_int {
294 return raw::Status::Err as c_int;
295 }
296
297 let context = $crate::Context::new(ctx);
298 unsafe {
299 let _ = $crate::MODULE_CONTEXT.set_context(&context);
300 }
301 let args = $crate::decode_args(ctx, argv, argc);
302
303 $(
307 if $preload_func(&context, &args) == $crate::Status::Err {
308 return $crate::Status::Err as c_int;
309 }
310 )*
311
312 $(
313 if (&$data_type).create_data_type(ctx).is_err() {
314 return raw::Status::Err as c_int;
315 }
316 )*
317
318 $(
319 $(
320 #[cfg(feature = "min-valkey-compatibility-version-8-0")]
321 context.add_acl_category($acl_category);
322 )*
323 )?
324
325 $(
326 $crate::redis_command!(ctx, $name, $command, $flags, $firstkey, $lastkey, $keystep $(, $command_acl_categories)?);
327 )*
328
329 if $crate::commands::register_commands(&context) == raw::Status::Err {
330 return raw::Status::Err as c_int;
331 }
332
333 $(
335 let mut cmd_filters_vec = Vec::new();
336 $(
337 paste::paste! {
338 $crate::define_extern_c_filter_func!([<__extern_c_ $filter_func>], $filter_func);
340 let cmd_filter = context.register_command_filter([<__extern_c_ $filter_func>], $filter_flags);
341 cmd_filters_vec.push(cmd_filter);
342 }
343 )*
344 CMD_FILTERS.get_or_init(|| cmd_filters_vec);
346 )?
347
348 $(
349 $(
350 $crate::redis_event_handler!(ctx, $(raw::NotifyEvent::$event_type |)+ raw::NotifyEvent::empty(), $event_handler);
351 )*
352 )?
353
354 $(
355 $(
356 $(
357 let default = if $use_module_args {
358 match get_i64_default_config_value(&args, $i64_configuration_name, $i64_default) {
359 Ok(v) => v,
360 Err(e) => {
361 context.log_warning(&format!("{e}"));
362 return raw::Status::Err as c_int;
363 }
364 }
365 } else {
366 $i64_default
367 };
368 let mut use_fallback = true;
369 $(
370 use_fallback = false;
371 register_i64_configuration(&context, $i64_configuration_name, $i64_configuration_val, default, $i64_min, $i64_max, $i64_flags_options, $i64_on_changed, $i64_on_set);
372 )?
373 if (use_fallback) {
374 register_i64_configuration(&context, $i64_configuration_name, $i64_configuration_val, default, $i64_min, $i64_max, $i64_flags_options, $i64_on_changed, None);
375 }
376 )*
377 )?
378 $(
379 $(
380 let default = if $use_module_args {
381 match get_string_default_config_value(&args, $string_configuration_name, $string_default) {
382 Ok(v) => v,
383 Err(e) => {
384 context.log_warning(&format!("{e}"));
385 return raw::Status::Err as c_int;
386 }
387 }
388 } else {
389 $string_default
390 };
391 let mut use_fallback = true;
392 $(
393 use_fallback = false;
394 register_string_configuration(&context, $string_configuration_name, $string_configuration_val, default, $string_flags_options, $string_on_changed, $string_on_set);
395 )?
396 if (use_fallback) {
397 register_string_configuration(&context, $string_configuration_name, $string_configuration_val, default, $string_flags_options, $string_on_changed, None);
398 }
399 )*
400 )?
401 $(
402 $(
403 let default = if $use_module_args {
404 match get_bool_default_config_value(&args, $bool_configuration_name, $bool_default) {
405 Ok(v) => v,
406 Err(e) => {
407 context.log_warning(&format!("{e}"));
408 return raw::Status::Err as c_int;
409 }
410 }
411 } else {
412 $bool_default
413 };
414 let mut use_fallback = true;
415 $(
416 use_fallback = false;
417 register_bool_configuration(&context, $bool_configuration_name, $bool_configuration_val, default, $bool_flags_options, $bool_on_changed, $bool_on_set);
418 )?
419 if (use_fallback) {
420 register_bool_configuration(&context, $bool_configuration_name, $bool_configuration_val, default, $bool_flags_options, $bool_on_changed, None);
421 }
422 )*
423 )?
424 $(
425 $(
426 let default = if $use_module_args {
427 match get_enum_default_config_value(&args, $enum_configuration_name, $enum_default) {
428 Ok(v) => v,
429 Err(e) => {
430 context.log_warning(&format!("{e}"));
431 return raw::Status::Err as c_int;
432 }
433 }
434 } else {
435 $enum_default
436 };
437 let mut use_fallback = true;
438 $(
439 use_fallback = false;
440 register_enum_configuration(&context, $enum_configuration_name, $enum_configuration_val, default.clone(), $enum_flags_options, $enum_on_changed, $enum_on_set);
441 )?
442 if (use_fallback) {
443 register_enum_configuration(&context, $enum_configuration_name, $enum_configuration_val, default.clone(), $enum_flags_options, $enum_on_changed, None);
444 }
445 )*
446 )?
447 raw::RedisModule_LoadConfigs.unwrap()(ctx);
448
449 $(
450 $crate::redis_command!(ctx, $module_config_get_command, |ctx, args: Vec<RedisString>| {
451 module_config_get(ctx, args, $module_name)
452 }, "", 0, 0, 0);
453 )?
454
455 $(
456 $crate::redis_command!(ctx, $module_config_set_command, |ctx, args: Vec<RedisString>| {
457 module_config_set(ctx, args, $module_name)
458 }, "", 0, 0, 0);
459 )?
460 )?
461
462 raw::register_info_function(ctx, Some(__info_func));
463
464 $(
465 $crate::valkey_module_auth!($module_name, ctx, $($auth_callback),*);
466 )?
467
468 if let Err(e) = register_server_events(&context) {
469 context.log_warning(&format!("{e}"));
470 return raw::Status::Err as c_int;
471 }
472
473 $(
474 if $init_func(&context, &args) == $crate::Status::Err {
475 return $crate::Status::Err as c_int;
476 }
477 )*
478
479 raw::Status::Ok as c_int
480 }
481
482 #[no_mangle]
483 #[allow(non_snake_case)]
484 pub extern "C" fn RedisModule_OnUnload(
485 ctx: *mut $crate::raw::RedisModuleCtx
486 ) -> std::os::raw::c_int {
487 use std::os::raw::c_int;
488
489 let context = $crate::Context::new(ctx);
490 $(
491 if $deinit_func(&context) == $crate::Status::Err {
492 return $crate::Status::Err as c_int;
493 }
494 )*
495
496 let cmd_filters_vec = match CMD_FILTERS.get(){
498 Some(tmp) => tmp,
499 None => &vec![]
500 };
501 for filter in cmd_filters_vec {
502 context.unregister_command_filter(&filter);
503 }
504
505 $crate::raw::Status::Ok as c_int
506 }
507 }
508}
509
510#[macro_export]
535macro_rules! valkey_module_auth {
536 ($module_name:expr, $ctx:expr, $($auth_callback:expr),* $(,)*) => {
537 $(
538 {
539 paste::paste! {
540 extern "C" fn [<__do_auth_ $module_name _ $auth_callback>](
541 ctx: *mut $crate::raw::RedisModuleCtx,
542 username: *mut $crate::raw::RedisModuleString,
543 password: *mut $crate::raw::RedisModuleString,
544 err: *mut *mut $crate::raw::RedisModuleString,
545 ) -> std::os::raw::c_int {
546 let context = $crate::Context::new(ctx);
547 let ctx_ptr = unsafe { std::ptr::NonNull::new_unchecked(ctx) };
548 let username = $crate::ValkeyString::new(Some(ctx_ptr), username);
549 let password = $crate::ValkeyString::new(Some(ctx_ptr), password);
550
551 match $auth_callback(&context, username, password) {
552 Ok(result) => result,
553 Err(e) => {
554 let error_msg = $crate::ValkeyString::create_and_retain(&e.to_string());
555 unsafe { *err = error_msg.inner };
556 $crate::AUTH_HANDLED
557 }
558 }
559 }
560
561 #[cfg(not(any(
562 feature = "min-redis-compatibility-version-7-2",
563 feature = "min-valkey-compatibility-version-8-0"
564 )))]
565 compile_error!("Auth callbacks require Redis 7.2 or Valkey 7.2 and above");
566
567 #[cfg(any(
568 feature = "min-redis-compatibility-version-7-2",
569 feature = "min-valkey-compatibility-version-8-0"
570 ))]
571 unsafe {
572 $crate::raw::RedisModule_RegisterAuthCallback.expect("RedisModule_RegisterAuthCallback should exist on Redis/Valkey 7.2 and above")($ctx, Some([<__do_auth_ $module_name _ $auth_callback>]));
573 }
574 }
575 }
576 )*
577 };
578}