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        $(acl_categories: [
134            $($acl_category:expr),* $(,)*
135        ])?
136        commands: [
137            $([
138                $name:expr,
139                $command:expr,
140                $flags:expr,
141                $firstkey:expr,
142                $lastkey:expr,
143                $keystep:expr
144                $(,
145                    $command_acl_categories:expr
146                )?
147            ]),* $(,)?
148        ] $(,)*
149        $(
150            filters: [
151                $([
152                    $filter_func:ident,
153                    $filter_flags:expr
154                ]),* $(,)?
155            ] $(,)*
156        )?
157        $(event_handlers: [
158            $([
159                $(@$event_type:ident) +:
160                $event_handler:expr
161            ]),* $(,)*
162        ] $(,)* )?
163        $(configurations: [
164            $(i64:[$([
165                $i64_configuration_name:expr,
166                $i64_configuration_val:expr,
167                $i64_default:expr,
168                $i64_min:expr,
169                $i64_max:expr,
170                $i64_flags_options:expr,
171                $i64_on_changed:expr $(, $i64_on_set:expr)?
172            ]),* $(,)*],)?
173            $(string:[$([
174                $string_configuration_name:expr,
175                $string_configuration_val:expr,
176                $string_default:expr,
177                $string_flags_options:expr,
178                $string_on_changed:expr $(, $string_on_set:expr)?
179            ]),* $(,)*],)?
180            $(bool:[$([
181                $bool_configuration_name:expr,
182                $bool_configuration_val:expr,
183                $bool_default:expr,
184                $bool_flags_options:expr,
185                $bool_on_changed:expr $(, $bool_on_set:expr)?
186            ]),* $(,)*],)?
187            $(enum:[$([
188                $enum_configuration_name:expr,
189                $enum_configuration_val:expr,
190                $enum_default:expr,
191                $enum_flags_options:expr,
192                $enum_on_changed:expr $(, $enum_on_set:expr)?
193            ]),* $(,)*],)?
194            $(module_args_as_configuration:$use_module_args:expr,)?
195            $(module_config_get:$module_config_get_command:expr,)?
196            $(module_config_set:$module_config_set_command:expr,)?
197        ])?
198    ) => {
199        /// Valkey module allocator.
200        #[global_allocator]
201        static REDIS_MODULE_ALLOCATOR: $allocator_type = $allocator_init;
202
203        use std::sync::OnceLock;
204        static CMD_FILTERS: OnceLock<Vec<valkey_module::CommandFilter>> = OnceLock::new();
205
206        // The old-style info command handler, if specified.
207        $(
208            #[valkey_module_macros::info_command_handler]
209            #[inline]
210            fn module_info(ctx: &InfoContext, for_crash_report: bool) -> ValkeyResult<()> {
211                $info_func(ctx, for_crash_report);
212
213                Ok(())
214            }
215        )?
216
217        extern "C" fn __info_func(
218            ctx: *mut $crate::raw::RedisModuleInfoCtx,
219            for_crash_report: i32,
220        ) {
221            $crate::basic_info_command_handler(&$crate::InfoContext::new(ctx), for_crash_report == 1);
222        }
223
224        #[no_mangle]
225        #[allow(non_snake_case)]
226        pub unsafe extern "C" fn RedisModule_OnLoad(
227            ctx: *mut $crate::raw::RedisModuleCtx,
228            argv: *mut *mut $crate::raw::RedisModuleString,
229            argc: std::os::raw::c_int,
230        ) -> std::os::raw::c_int {
231            use std::os::raw::{c_int, c_char};
232            use std::ffi::{CString, CStr};
233
234            use $crate::raw;
235            use $crate::ValkeyString;
236            use $crate::server_events::register_server_events;
237            use $crate::configuration::register_i64_configuration;
238            use $crate::configuration::register_string_configuration;
239            use $crate::configuration::register_bool_configuration;
240            use $crate::configuration::register_enum_configuration;
241            use $crate::configuration::module_config_get;
242            use $crate::configuration::module_config_set;
243            use $crate::configuration::get_i64_default_config_value;
244            use $crate::configuration::get_string_default_config_value;
245            use $crate::configuration::get_bool_default_config_value;
246            use $crate::configuration::get_enum_default_config_value;
247
248            // We use a statically sized buffer to avoid allocating.
249            // This is needed since we use a custom allocator that relies on the Valkey allocator,
250            // which isn't yet ready at this point.
251            let mut name_buffer = [0; 64];
252            unsafe {
253                std::ptr::copy(
254                    $module_name.as_ptr(),
255                    name_buffer.as_mut_ptr(),
256                    $module_name.len(),
257                );
258            }
259
260            let module_version = $module_version as c_int;
261
262            // This block of code means that when Modules are compiled without the "use-redismodule-api" feature flag,
263            // we expect that ValkeyModule_Init should succeed. We do not YET utilize the ValkeyModule_Init invocation
264            // because the valkeymodule-rs still references RedisModule_* APIs for calls to the server.
265            if !raw::use_redis_module_api() {
266                let status = unsafe {
267                    raw::Export_ValkeyModule_Init(
268                        ctx as *mut raw::ValkeyModuleCtx,
269                        name_buffer.as_ptr().cast::<c_char>(),
270                        module_version,
271                        raw::VALKEYMODULE_APIVER_1 as c_int,
272                    )
273                };
274                if status == raw::Status::Err as c_int {
275                    return raw::Status::Err as c_int;
276                }
277            }
278
279            // For now, we need to initialize through RM_Init because several occurances are still using RedisModule_* APIs.
280            // Once we change every single Module API to be ValkeyModule_* (when the feature flag is not provided), we can
281            // update this block (invocation of RM_Init) to only be executed when the "use-redismodule-api" is provided.
282            let status = unsafe {
283                raw::Export_RedisModule_Init(
284                    ctx,
285                    name_buffer.as_ptr().cast::<c_char>(),
286                    module_version,
287                    raw::REDISMODULE_APIVER_1 as c_int,
288                )
289            };
290            if status == raw::Status::Err as c_int {
291                return raw::Status::Err as c_int;
292            }
293
294            let context = $crate::Context::new(ctx);
295            unsafe {
296                let _ = $crate::MODULE_CONTEXT.set_context(&context);
297            }
298            let args = $crate::decode_args(ctx, argv, argc);
299
300            // perform validation BEFORE loading the module
301            // exit if there are any issues BEFORE registering commands, data types, etc
302            // different from init_func which is done at the end and allows to take advantage of things that were done in the macro
303            $(
304                if $preload_func(&context, &args) == $crate::Status::Err {
305                    return $crate::Status::Err as c_int;
306                }
307            )*
308
309            $(
310                if (&$data_type).create_data_type(ctx).is_err() {
311                    return raw::Status::Err as c_int;
312                }
313            )*
314
315            $(
316                $(
317                    #[cfg(feature = "min-valkey-compatibility-version-8-0")]
318                    context.add_acl_category($acl_category);
319                )*
320            )?
321
322            $(
323                $crate::redis_command!(ctx, $name, $command, $flags, $firstkey, $lastkey, $keystep $(, $command_acl_categories)?);
324            )*
325
326            if $crate::commands::register_commands(&context) == raw::Status::Err {
327                return raw::Status::Err as c_int;
328            }
329
330            // register filters on module load
331            $(
332                let mut cmd_filters_vec = Vec::new();
333                $(
334                    paste::paste! {
335                        // creating a unique extern c filter function name:  __extern_c_ $filter_func
336                        $crate::define_extern_c_filter_func!([<__extern_c_ $filter_func>], $filter_func);
337                        let cmd_filter = context.register_command_filter([<__extern_c_ $filter_func>], $filter_flags);
338                        cmd_filters_vec.push(cmd_filter);
339                    }
340                )*
341                // store filters in a static variable to unregister them on module unload
342                CMD_FILTERS.get_or_init(|| cmd_filters_vec);
343            )?
344
345            $(
346                $(
347                    $crate::redis_event_handler!(ctx, $(raw::NotifyEvent::$event_type |)+ raw::NotifyEvent::empty(), $event_handler);
348                )*
349            )?
350
351            $(
352                $(
353                    $(
354                        let default = if $use_module_args {
355                            match get_i64_default_config_value(&args, $i64_configuration_name, $i64_default) {
356                                Ok(v) => v,
357                                Err(e) => {
358                                    context.log_warning(&format!("{e}"));
359                                    return raw::Status::Err as c_int;
360                                }
361                            }
362                        } else {
363                            $i64_default
364                        };
365                        let mut use_fallback = true;
366                        $(
367                            use_fallback = false;
368                            register_i64_configuration(&context, $i64_configuration_name, $i64_configuration_val, default, $i64_min, $i64_max, $i64_flags_options, $i64_on_changed, $i64_on_set);
369                        )?
370                        if (use_fallback) {
371                            register_i64_configuration(&context, $i64_configuration_name, $i64_configuration_val, default, $i64_min, $i64_max, $i64_flags_options, $i64_on_changed, None);
372                        }
373                    )*
374                )?
375                $(
376                    $(
377                        let default = if $use_module_args {
378                            match get_string_default_config_value(&args, $string_configuration_name, $string_default) {
379                                Ok(v) => v,
380                                Err(e) => {
381                                    context.log_warning(&format!("{e}"));
382                                    return raw::Status::Err as c_int;
383                                }
384                            }
385                        } else {
386                            $string_default
387                        };
388                        let mut use_fallback = true;
389                        $(
390                            use_fallback = false;
391                            register_string_configuration(&context, $string_configuration_name, $string_configuration_val, default, $string_flags_options, $string_on_changed, $string_on_set);
392                        )?
393                        if (use_fallback) {
394                            register_string_configuration(&context, $string_configuration_name, $string_configuration_val, default, $string_flags_options, $string_on_changed, None);
395                        }
396                    )*
397                )?
398                $(
399                    $(
400                        let default = if $use_module_args {
401                            match get_bool_default_config_value(&args, $bool_configuration_name, $bool_default) {
402                                Ok(v) => v,
403                                Err(e) => {
404                                    context.log_warning(&format!("{e}"));
405                                    return raw::Status::Err as c_int;
406                                }
407                            }
408                        } else {
409                            $bool_default
410                        };
411                        let mut use_fallback = true;
412                        $(
413                            use_fallback = false;
414                            register_bool_configuration(&context, $bool_configuration_name, $bool_configuration_val, default, $bool_flags_options, $bool_on_changed, $bool_on_set);
415                        )?
416                        if (use_fallback) {
417                            register_bool_configuration(&context, $bool_configuration_name, $bool_configuration_val, default, $bool_flags_options, $bool_on_changed, None);
418                        }
419                    )*
420                )?
421                $(
422                    $(
423                        let default = if $use_module_args {
424                            match get_enum_default_config_value(&args, $enum_configuration_name, $enum_default) {
425                                Ok(v) => v,
426                                Err(e) => {
427                                    context.log_warning(&format!("{e}"));
428                                    return raw::Status::Err as c_int;
429                                }
430                            }
431                        } else {
432                            $enum_default
433                        };
434                        let mut use_fallback = true;
435                        $(
436                            use_fallback = false;
437                            register_enum_configuration(&context, $enum_configuration_name, $enum_configuration_val, default.clone(), $enum_flags_options, $enum_on_changed, $enum_on_set);
438                        )?
439                        if (use_fallback) {
440                            register_enum_configuration(&context, $enum_configuration_name, $enum_configuration_val, default.clone(), $enum_flags_options, $enum_on_changed, None);
441                        }
442                    )*
443                )?
444                raw::RedisModule_LoadConfigs.unwrap()(ctx);
445
446                $(
447                    $crate::redis_command!(ctx, $module_config_get_command, |ctx, args: Vec<RedisString>| {
448                        module_config_get(ctx, args, $module_name)
449                    }, "", 0, 0, 0);
450                )?
451
452                $(
453                    $crate::redis_command!(ctx, $module_config_set_command, |ctx, args: Vec<RedisString>| {
454                        module_config_set(ctx, args, $module_name)
455                    }, "", 0, 0, 0);
456                )?
457            )?
458
459            raw::register_info_function(ctx, Some(__info_func));
460
461            if let Err(e) = register_server_events(&context) {
462                context.log_warning(&format!("{e}"));
463                return raw::Status::Err as c_int;
464            }
465
466            $(
467                if $init_func(&context, &args) == $crate::Status::Err {
468                    return $crate::Status::Err as c_int;
469                }
470            )*
471
472            raw::Status::Ok as c_int
473        }
474
475        #[no_mangle]
476        #[allow(non_snake_case)]
477        pub extern "C" fn RedisModule_OnUnload(
478            ctx: *mut $crate::raw::RedisModuleCtx
479        ) -> std::os::raw::c_int {
480            use std::os::raw::c_int;
481
482            let context = $crate::Context::new(ctx);
483            $(
484                if $deinit_func(&context) == $crate::Status::Err {
485                    return $crate::Status::Err as c_int;
486                }
487            )*
488
489            // unregister filters on module unload
490            let cmd_filters_vec = match CMD_FILTERS.get(){
491                Some(tmp) => tmp,
492                None => &vec![]
493            };
494            for filter in cmd_filters_vec {
495                context.unregister_command_filter(&filter);
496            }
497
498            $crate::raw::Status::Ok as c_int
499        }
500    }
501}