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/// Defines a Valkey module.
103///
104/// It registers the defined module, sets it up and initialises properly,
105/// registers all the commands and types.
106#[macro_export]
107macro_rules! valkey_module {
108    (
109        name: $module_name:expr,
110        version: $module_version:expr,
111        /// Global allocator for the valkey module defined.
112        /// In most of the cases, the Valkey allocator ([crate::alloc::ValkeyAlloc])
113        /// should be used.
114        allocator: ($allocator_type:ty, $allocator_init:expr),
115        data_types: [
116            $($data_type:ident),* $(,)*
117        ],
118        $(init: $init_func:ident,)* $(,)*
119        $(deinit: $deinit_func:ident,)* $(,)*
120        $(info: $info_func:ident,)?
121        $(acl_categories: [
122            $($acl_category:expr),* $(,)*
123        ])?
124        commands: [
125            $([
126                $name:expr,
127                $command:expr,
128                $flags:expr,
129                $firstkey:expr,
130                $lastkey:expr,
131                $keystep:expr
132                $(,
133                    $command_acl_categories:expr
134                )?
135            ]),* $(,)?
136        ] $(,)*
137        $(event_handlers: [
138            $([
139                $(@$event_type:ident) +:
140                $event_handler:expr
141            ]),* $(,)*
142        ] $(,)* )?
143        $(configurations: [
144            $(i64:[$([
145                $i64_configuration_name:expr,
146                $i64_configuration_val:expr,
147                $i64_default:expr,
148                $i64_min:expr,
149                $i64_max:expr,
150                $i64_flags_options:expr,
151                $i64_on_changed:expr $(, $i64_on_set:expr)?
152            ]),* $(,)*],)?
153            $(string:[$([
154                $string_configuration_name:expr,
155                $string_configuration_val:expr,
156                $string_default:expr,
157                $string_flags_options:expr,
158                $string_on_changed:expr $(, $string_on_set:expr)?
159            ]),* $(,)*],)?
160            $(bool:[$([
161                $bool_configuration_name:expr,
162                $bool_configuration_val:expr,
163                $bool_default:expr,
164                $bool_flags_options:expr,
165                $bool_on_changed:expr $(, $bool_on_set:expr)?
166            ]),* $(,)*],)?
167            $(enum:[$([
168                $enum_configuration_name:expr,
169                $enum_configuration_val:expr,
170                $enum_default:expr,
171                $enum_flags_options:expr,
172                $enum_on_changed:expr $(, $enum_on_set:expr)?
173            ]),* $(,)*],)?
174            $(module_args_as_configuration:$use_module_args:expr,)?
175            $(module_config_get:$module_config_get_command:expr,)?
176            $(module_config_set:$module_config_set_command:expr,)?
177        ])?
178    ) => {
179        /// Valkey module allocator.
180        #[global_allocator]
181        static REDIS_MODULE_ALLOCATOR: $allocator_type = $allocator_init;
182
183        // The old-style info command handler, if specified.
184        $(
185            #[valkey_module_macros::info_command_handler]
186            #[inline]
187            fn module_info(ctx: &InfoContext, for_crash_report: bool) -> ValkeyResult<()> {
188                $info_func(ctx, for_crash_report);
189
190                Ok(())
191            }
192        )?
193
194        extern "C" fn __info_func(
195            ctx: *mut $crate::raw::RedisModuleInfoCtx,
196            for_crash_report: i32,
197        ) {
198            $crate::basic_info_command_handler(&$crate::InfoContext::new(ctx), for_crash_report == 1);
199        }
200
201        #[no_mangle]
202        #[allow(non_snake_case)]
203        pub unsafe extern "C" fn RedisModule_OnLoad(
204            ctx: *mut $crate::raw::RedisModuleCtx,
205            argv: *mut *mut $crate::raw::RedisModuleString,
206            argc: std::os::raw::c_int,
207        ) -> std::os::raw::c_int {
208            use std::os::raw::{c_int, c_char};
209            use std::ffi::{CString, CStr};
210
211            use $crate::raw;
212            use $crate::ValkeyString;
213            use $crate::server_events::register_server_events;
214            use $crate::configuration::register_i64_configuration;
215            use $crate::configuration::register_string_configuration;
216            use $crate::configuration::register_bool_configuration;
217            use $crate::configuration::register_enum_configuration;
218            use $crate::configuration::module_config_get;
219            use $crate::configuration::module_config_set;
220            use $crate::configuration::get_i64_default_config_value;
221            use $crate::configuration::get_string_default_config_value;
222            use $crate::configuration::get_bool_default_config_value;
223            use $crate::configuration::get_enum_default_config_value;
224
225            // We use a statically sized buffer to avoid allocating.
226            // This is needed since we use a custom allocator that relies on the Valkey allocator,
227            // which isn't yet ready at this point.
228            let mut name_buffer = [0; 64];
229            unsafe {
230                std::ptr::copy(
231                    $module_name.as_ptr(),
232                    name_buffer.as_mut_ptr(),
233                    $module_name.len(),
234                );
235            }
236
237            let module_version = $module_version as c_int;
238
239            if unsafe { raw::Export_RedisModule_Init(
240                ctx,
241                name_buffer.as_ptr().cast::<c_char>(),
242                module_version,
243                raw::REDISMODULE_APIVER_1 as c_int,
244            ) } == raw::Status::Err as c_int { return raw::Status::Err as c_int; }
245
246            let context = $crate::Context::new(ctx);
247            unsafe {
248                let _ = $crate::MODULE_CONTEXT.set_context(&context);
249            }
250            let args = $crate::decode_args(ctx, argv, argc);
251
252            $(
253                if (&$data_type).create_data_type(ctx).is_err() {
254                    return raw::Status::Err as c_int;
255                }
256            )*
257
258            $(
259                $(
260                    #[cfg(feature = "min-valkey-compatibility-version-8-0")]
261                    context.add_acl_category($acl_category);
262                )*
263            )?
264
265            $(
266                $crate::redis_command!(ctx, $name, $command, $flags, $firstkey, $lastkey, $keystep $(, $command_acl_categories)?);
267            )*
268
269            if $crate::commands::register_commands(&context) == raw::Status::Err {
270                return raw::Status::Err as c_int;
271            }
272
273            $(
274                $(
275                    $crate::redis_event_handler!(ctx, $(raw::NotifyEvent::$event_type |)+ raw::NotifyEvent::empty(), $event_handler);
276                )*
277            )?
278
279            $(
280                $(
281                    $(
282                        let default = if $use_module_args {
283                            match get_i64_default_config_value(&args, $i64_configuration_name, $i64_default) {
284                                Ok(v) => v,
285                                Err(e) => {
286                                    context.log_warning(&format!("{e}"));
287                                    return raw::Status::Err as c_int;
288                                }
289                            }
290                        } else {
291                            $i64_default
292                        };
293                        let mut use_fallback = true;
294                        $(
295                            use_fallback = false;
296                            register_i64_configuration(&context, $i64_configuration_name, $i64_configuration_val, default, $i64_min, $i64_max, $i64_flags_options, $i64_on_changed, $i64_on_set);
297                        )?
298                        if (use_fallback) {
299                            register_i64_configuration(&context, $i64_configuration_name, $i64_configuration_val, default, $i64_min, $i64_max, $i64_flags_options, $i64_on_changed, None);
300                        }
301                    )*
302                )?
303                $(
304                    $(
305                        let default = if $use_module_args {
306                            match get_string_default_config_value(&args, $string_configuration_name, $string_default) {
307                                Ok(v) => v,
308                                Err(e) => {
309                                    context.log_warning(&format!("{e}"));
310                                    return raw::Status::Err as c_int;
311                                }
312                            }
313                        } else {
314                            $string_default
315                        };
316                        let mut use_fallback = true;
317                        $(
318                            use_fallback = false;
319                            register_string_configuration(&context, $string_configuration_name, $string_configuration_val, default, $string_flags_options, $string_on_changed, $string_on_set);
320                        )?
321                        if (use_fallback) {
322                            register_string_configuration(&context, $string_configuration_name, $string_configuration_val, default, $string_flags_options, $string_on_changed, None);
323                        }
324                    )*
325                )?
326                $(
327                    $(
328                        let default = if $use_module_args {
329                            match get_bool_default_config_value(&args, $bool_configuration_name, $bool_default) {
330                                Ok(v) => v,
331                                Err(e) => {
332                                    context.log_warning(&format!("{e}"));
333                                    return raw::Status::Err as c_int;
334                                }
335                            }
336                        } else {
337                            $bool_default
338                        };
339                        let mut use_fallback = true;
340                        $(
341                            use_fallback = false;
342                            register_bool_configuration(&context, $bool_configuration_name, $bool_configuration_val, default, $bool_flags_options, $bool_on_changed, $bool_on_set);
343                        )?
344                        if (use_fallback) {
345                            register_bool_configuration(&context, $bool_configuration_name, $bool_configuration_val, default, $bool_flags_options, $bool_on_changed, None);
346                        }
347                    )*
348                )?
349                $(
350                    $(
351                        let default = if $use_module_args {
352                            match get_enum_default_config_value(&args, $enum_configuration_name, $enum_default) {
353                                Ok(v) => v,
354                                Err(e) => {
355                                    context.log_warning(&format!("{e}"));
356                                    return raw::Status::Err as c_int;
357                                }
358                            }
359                        } else {
360                            $enum_default
361                        };
362                        let mut use_fallback = true;
363                        $(
364                            use_fallback = false;
365                            register_enum_configuration(&context, $enum_configuration_name, $enum_configuration_val, default.clone(), $enum_flags_options, $enum_on_changed, $enum_on_set);
366                        )?
367                        if (use_fallback) {
368                            register_enum_configuration(&context, $enum_configuration_name, $enum_configuration_val, default.clone(), $enum_flags_options, $enum_on_changed, None);
369                        }
370                    )*
371                )?
372                raw::RedisModule_LoadConfigs.unwrap()(ctx);
373
374                $(
375                    $crate::redis_command!(ctx, $module_config_get_command, |ctx, args: Vec<RedisString>| {
376                        module_config_get(ctx, args, $module_name)
377                    }, "", 0, 0, 0);
378                )?
379
380                $(
381                    $crate::redis_command!(ctx, $module_config_set_command, |ctx, args: Vec<RedisString>| {
382                        module_config_set(ctx, args, $module_name)
383                    }, "", 0, 0, 0);
384                )?
385            )?
386
387            raw::register_info_function(ctx, Some(__info_func));
388
389            if let Err(e) = register_server_events(&context) {
390                context.log_warning(&format!("{e}"));
391                return raw::Status::Err as c_int;
392            }
393
394            $(
395                if $init_func(&context, &args) == $crate::Status::Err {
396                    return $crate::Status::Err as c_int;
397                }
398            )*
399
400            raw::Status::Ok as c_int
401        }
402
403        #[no_mangle]
404        #[allow(non_snake_case)]
405        pub extern "C" fn RedisModule_OnUnload(
406            ctx: *mut $crate::raw::RedisModuleCtx
407        ) -> std::os::raw::c_int {
408            use std::os::raw::c_int;
409
410            let context = $crate::Context::new(ctx);
411            $(
412                if $deinit_func(&context) == $crate::Status::Err {
413                    return $crate::Status::Err as c_int;
414                }
415            )*
416
417            $crate::raw::Status::Ok as c_int
418        }
419    }
420}