valkey_module/
macros.rs

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        /////////////////////
18        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// create an extern "C" wrapper for Rust filter function passed in to the macro
103// function names must be known at compile time so using a macro to generate them
104#[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/// Defines a Valkey module.
114///
115/// It registers the defined module, sets it up and initialises properly,
116/// registers all the commands and types.
117#[macro_export]
118macro_rules! valkey_module {
119    (
120        name: $module_name:expr,
121        version: $module_version:expr,
122        /// Global allocator for the valkey module defined.
123        /// In most of the cases, the Valkey allocator ([crate::alloc::ValkeyAlloc])
124        /// should be used.
125        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        /// Valkey module allocator.
203        #[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        // The old-style info command handler, if specified.
210        $(
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            // We use a statically sized buffer to avoid allocating.
252            // This is needed since we use a custom allocator that relies on the Valkey allocator,
253            // which isn't yet ready at this point.
254            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            // This block of code means that when Modules are compiled without the "use-redismodule-api" feature flag,
266            // we expect that ValkeyModule_Init should succeed. We do not YET utilize the ValkeyModule_Init invocation
267            // because the valkeymodule-rs still references RedisModule_* APIs for calls to the server.
268            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            // For now, we need to initialize through RM_Init because several occurances are still using RedisModule_* APIs.
283            // Once we change every single Module API to be ValkeyModule_* (when the feature flag is not provided), we can
284            // update this block (invocation of RM_Init) to only be executed when the "use-redismodule-api" is provided.
285            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            // perform validation BEFORE loading the module
304            // exit if there are any issues BEFORE registering commands, data types, etc
305            // different from init_func which is done at the end and allows to take advantage of things that were done in the macro
306            $(
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            // register filters on module load
334            $(
335                let mut cmd_filters_vec = Vec::new();
336                $(
337                    paste::paste! {
338                        // creating a unique extern c filter function name:  __extern_c_ $filter_func
339                        $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                // store filters in a static variable to unregister them on module unload
345                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            // unregister filters on module unload
497            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/// Registers authentication callbacks with the Valkey module
511///
512/// Provides an unique safe wrapper for Valkey's authentication callback registration system.
513/// Used within the `valkey_module!` macro's auth field.
514///
515/// # Example
516/// ```ignore
517/// use valkey_module::valkey_module;
518///
519/// valkey_module! {
520///     name: "mymodule",
521///     version: 1,
522///     allocator: (ValkeyAlloc, ValkeyAlloc),
523///     auth: [
524///         blocking_callback,    // Called 2nd (LIFO)
525///         simple_callback,     // Called 1st
526///     ],
527///     // ... other fields
528/// }
529/// ```
530///
531/// # Note
532/// Callbacks are registered in LIFO order (last callback is called first).
533/// Requires Redis 7.2+ or Valkey 7.2+.
534#[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}