Skip to main content

redis_module/
macros.rs

1#[macro_export]
2macro_rules! redis_command {
3    ($ctx:expr,
4     $command_name:expr,
5     $command_handler:expr,
6     $command_flags:expr,
7     $firstkey:expr,
8     $lastkey:expr,
9     $keystep:expr,
10     $mandatory_acl_categories:expr
11     $(, $optional_acl_categories:expr)?
12    ) => {{
13        use $crate::AclCategory;
14
15        let name = CString::new($command_name).unwrap();
16        let flags = CString::new($command_flags).unwrap();
17
18        /////////////////////
19        extern "C" fn __do_command(
20            ctx: *mut $crate::raw::RedisModuleCtx,
21            argv: *mut *mut $crate::raw::RedisModuleString,
22            argc: c_int,
23        ) -> c_int {
24            let context = $crate::Context::new(ctx);
25
26            let args = $crate::decode_args(ctx, argv, argc);
27            let response = $command_handler(&context, args);
28            context.reply(response.map(|v| v.into())) as c_int
29        }
30        /////////////////////
31
32        if unsafe {
33            $crate::raw::RedisModule_CreateCommand.unwrap()(
34                $ctx,
35                name.as_ptr(),
36                Some(__do_command),
37                flags.as_ptr(),
38                $firstkey,
39                $lastkey,
40                $keystep,
41            )
42        } == $crate::raw::Status::Err as c_int
43        {
44            $crate::raw::redis_log(
45                $ctx,
46                &format!("Error: failed to create command {}", $command_name),
47            );
48            return $crate::raw::Status::Err as c_int;
49        }
50
51        let mandatory = AclCategory::from($mandatory_acl_categories);
52        if let Some(RM_SetCommandACLCategories) = $crate::raw::RedisModule_SetCommandACLCategories {
53            let command =
54            unsafe { $crate::raw::RedisModule_GetCommand.unwrap()($ctx, name.as_ptr()) };
55            if command.is_null() {
56                $crate::raw::redis_log(
57                    $ctx,
58                    &format!("Error: failed to get command {}", $command_name),
59                );
60                return $crate::raw::Status::Err as c_int;
61            }
62            let mut optional_failed = true;
63            let mut acl_categories = CString::default();
64            $(
65                let optional = AclCategory::from($optional_acl_categories);
66                assert!(optional != AclCategory::None);
67                optional_failed = false;
68                if mandatory != AclCategory::None {
69                    acl_categories = CString::new(format!("{mandatory} {optional}")).unwrap();
70                } else {
71                    acl_categories = CString::new(format!("{optional}")).unwrap();
72                }
73                // Warn if optional ACL categories are not set, but don't fail.
74                if RM_SetCommandACLCategories(command, acl_categories.as_ptr()) == $crate::raw::Status::Err as c_int {
75                    optional_failed = true;
76                    $crate::raw::redis_log(
77                        $ctx,
78                        &format!(
79                            "Warning: failed to set command `{}` ACL categories `{}`",
80                            $command_name, acl_categories.to_str().unwrap()
81                        ),
82                    );
83                }
84            )?
85            if optional_failed && mandatory != AclCategory::None {
86                acl_categories = CString::new(format!("{mandatory}")).unwrap();
87
88                // Fail if mandatory ACL categories are not set.
89                if RM_SetCommandACLCategories(command, acl_categories.as_ptr())
90                    == $crate::raw::Status::Err as c_int
91                {
92                    $crate::raw::redis_log(
93                        $ctx,
94                        &format!(
95                            "Error: failed to set command `{}` mandatory ACL categories `{mandatory}`",
96                            $command_name
97                        ),
98                    );
99                    return $crate::raw::Status::Err as c_int;
100                }
101            }
102        } else if mandatory != AclCategory::None {
103            $crate::raw::redis_log(
104                $ctx,
105                "Error: Redis version does not support ACL categories",
106            );
107            return $crate::raw::Status::Err as c_int;
108        }
109    }};
110}
111
112#[macro_export]
113macro_rules! redis_event_handler {
114    (
115        $ctx: expr,
116        $event_type: expr,
117        $event_handler: expr
118    ) => {{
119        extern "C" fn __handle_event(
120            ctx: *mut $crate::raw::RedisModuleCtx,
121            event_type: c_int,
122            event: *const c_char,
123            key: *mut $crate::raw::RedisModuleString,
124        ) -> c_int {
125            let context = $crate::Context::new(ctx);
126
127            let redis_key = $crate::RedisString::string_as_slice(key);
128            let event_str = unsafe { CStr::from_ptr(event) };
129            $event_handler(
130                &context,
131                $crate::NotifyEvent::from_bits_truncate(event_type),
132                event_str.to_str().unwrap(),
133                redis_key,
134            );
135
136            $crate::raw::Status::Ok as c_int
137        }
138
139        let all_available_notification_flags = $crate::raw::get_keyspace_notification_flags_all();
140        let available_wanted_notification_flags = $event_type.intersection(all_available_notification_flags);
141        if !all_available_notification_flags.contains($event_type) {
142            let not_supported = $event_type.difference(all_available_notification_flags);
143            $crate::Context::new($ctx).log_notice(&format!(
144                "These event notification flags set aren't supported: {not_supported:?}. These flags will be used: {available_wanted_notification_flags:?}"
145            ));
146        }
147
148        if !available_wanted_notification_flags.is_empty() && unsafe {
149            $crate::raw::RedisModule_SubscribeToKeyspaceEvents.unwrap()(
150                $ctx,
151                available_wanted_notification_flags.bits(),
152                Some(__handle_event),
153            )
154        } == $crate::raw::Status::Err as c_int
155        {
156            return $crate::raw::Status::Err as c_int;
157        }
158    }};
159}
160
161/// Defines a Redis module.
162///
163/// It registers the defined module, sets it up and initialises properly,
164/// registers all the commands and types.
165#[macro_export]
166macro_rules! redis_module {
167    (
168        name: $module_name:expr,
169        version: $module_version:expr,
170        /// Global allocator for the redis module defined.
171        /// In most of the cases, the Redis allocator ([crate::alloc::RedisAlloc])
172        /// should be used.
173        allocator: ($allocator_type:ty, $allocator_init:expr),
174        data_types: [
175            $($data_type:ident),* $(,)*
176        ],
177        // eg: `acl_category: [ "name_of_module_acl_category", ],`
178        // This will add the specified (optional) ACL categories.
179        $(acl_categories: [
180            $($module_acl_category:expr,)*
181        ],)?
182        $(init: $init_func:ident,)* $(,)*
183        $(deinit: $deinit_func:ident,)* $(,)*
184        $(info: $info_func:ident,)?
185        $(commands: [
186            $([
187                $name:expr,
188                $command:expr,
189                $flags:expr,
190                $firstkey:expr,
191                $lastkey:expr,
192                $keystep:expr,
193                $mandatory_command_acl_categories:expr
194                $(, $optional_command_acl_categories:expr)?
195              ]),* $(,)*
196        ] $(,)*)?
197        $(event_handlers: [
198            $([
199                $(@$event_type:ident) +:
200                $event_handler:expr
201            ]),* $(,)*
202        ] $(,)* )?
203        $(configurations: [
204            $(i64:[$([
205                $i64_configuration_name:expr,
206                $i64_configuration_val:expr,
207                $i64_default:expr,
208                $i64_min:expr,
209                $i64_max:expr,
210                $i64_flags_options:expr,
211                $i64_on_changed:expr
212            ]),* $(,)*],)?
213            $(string:[$([
214                $string_configuration_name:expr,
215                $string_configuration_val:expr,
216                $string_default:expr,
217                $string_flags_options:expr,
218                $string_on_changed:expr
219            ]),* $(,)*],)?
220            $(bool:[$([
221                $bool_configuration_name:expr,
222                $bool_configuration_val:expr,
223                $bool_default:expr,
224                $bool_flags_options:expr,
225                $bool_on_changed:expr
226            ]),* $(,)*],)?
227            $(enum:[$([
228                $enum_configuration_name:expr,
229                $enum_configuration_val:expr,
230                $enum_default:expr,
231                $enum_flags_options:expr,
232                $enum_on_changed:expr
233            ]),* $(,)*],)?
234            $(module_args_as_configuration:$use_module_args:expr,)?
235            $(module_config_get:$module_config_get_command:expr,)?
236            $(module_config_set:$module_config_set_command:expr,)?
237        ])?
238    ) => {
239        /// Redis module allocator.
240        #[global_allocator]
241        static REDIS_MODULE_ALLOCATOR: $allocator_type = $allocator_init;
242
243        // The old-style info command handler, if specified.
244        $(
245            #[redis_module_macros::info_command_handler]
246            #[inline]
247            fn module_info(ctx: &InfoContext, for_crash_report: bool) -> RedisResult<()> {
248                $info_func(ctx, for_crash_report);
249
250                Ok(())
251            }
252        )?
253
254        extern "C" fn __info_func(
255            ctx: *mut $crate::raw::RedisModuleInfoCtx,
256            for_crash_report: i32,
257        ) {
258            $crate::basic_info_command_handler(&$crate::InfoContext::new(ctx), for_crash_report == 1);
259        }
260
261        #[no_mangle]
262        #[allow(non_snake_case)]
263        pub unsafe extern "C" fn RedisModule_OnLoad(
264            ctx: *mut $crate::raw::RedisModuleCtx,
265            argv: *mut *mut $crate::raw::RedisModuleString,
266            argc: std::os::raw::c_int,
267        ) -> std::os::raw::c_int {
268            use std::os::raw::{c_int, c_char};
269            use std::ffi::{CString, CStr};
270
271            use $crate::raw;
272            use $crate::RedisString;
273            use $crate::server_events::register_server_events;
274            use $crate::configuration::register_i64_configuration;
275            use $crate::configuration::register_string_configuration;
276            use $crate::configuration::register_bool_configuration;
277            use $crate::configuration::register_enum_configuration;
278            use $crate::configuration::module_config_get;
279            use $crate::configuration::module_config_set;
280            use $crate::configuration::get_i64_default_config_value;
281            use $crate::configuration::get_string_default_config_value;
282            use $crate::configuration::get_bool_default_config_value;
283            use $crate::configuration::get_enum_default_config_value;
284
285            // We use a statically sized buffer to avoid allocating.
286            // This is needed since we use a custom allocator that relies on the Redis allocator,
287            // which isn't yet ready at this point.
288            let mut name_buffer = [0; 64];
289            unsafe {
290                std::ptr::copy(
291                    $module_name.as_ptr(),
292                    name_buffer.as_mut_ptr(),
293                    $module_name.len(),
294                );
295            }
296
297            let module_version = $module_version as c_int;
298
299            if unsafe { raw::Export_RedisModule_Init(
300                ctx,
301                name_buffer.as_ptr().cast::<c_char>(),
302                module_version,
303                raw::REDISMODULE_APIVER_1 as c_int,
304            ) } == raw::Status::Err as c_int { return raw::Status::Err as c_int; }
305
306            let context = $crate::Context::new(ctx);
307            unsafe {
308                let _ = $crate::MODULE_CONTEXT.set_context(&context);
309            }
310            let args = $crate::decode_args(ctx, argv, argc);
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                    if let Some(RM_AddACLCategory) = raw::RedisModule_AddACLCategory {
321                        let module_acl_category = AclCategory::from($module_acl_category);
322                        if module_acl_category != AclCategory::None {
323                            let category = CString::new(format!("{module_acl_category}")).unwrap();
324                            if RM_AddACLCategory(ctx, category.as_ptr()) == raw::Status::Err as c_int {
325                                raw::redis_log(ctx, &format!("Error: failed to add ACL category `{module_acl_category}`"));
326                                return raw::Status::Err as c_int;
327                            }
328                        }
329                    } else {
330                        raw::redis_log(ctx, "Warning: Redis version does not support adding new ACL categories");
331                    }
332                )*
333            )?
334
335            $(
336                $(
337                    $crate::redis_command!(ctx, $name, $command, $flags, $firstkey, $lastkey, $keystep, $mandatory_command_acl_categories $(, $optional_command_acl_categories)?);
338                )*
339            )?
340
341            if $crate::commands::register_commands(&context) == raw::Status::Err {
342                return raw::Status::Err as c_int;
343            }
344
345            if let Err(e) = $crate::defrag::register_defrag_functions(&context) {
346                context.log_warning(&format!("{e}"));
347                return raw::Status::Err as c_int;
348            }
349
350            $(
351                $(
352                    $crate::redis_event_handler!(ctx, $(raw::NotifyEvent::$event_type |)+ raw::NotifyEvent::empty(), $event_handler);
353                )*
354            )?
355
356            $(
357                $(
358                    $(
359                        let default = if $use_module_args {
360                            match get_i64_default_config_value(&args, $i64_configuration_name, $i64_default) {
361                                Ok(v) => v,
362                                Err(e) => {
363                                    context.log_warning(&format!("{e}"));
364                                    return raw::Status::Err as c_int;
365                                }
366                            }
367                        } else {
368                            $i64_default
369                        };
370                        register_i64_configuration(&context, $i64_configuration_name, $i64_configuration_val, default, $i64_min, $i64_max, $i64_flags_options, $i64_on_changed);
371                    )*
372                )?
373                $(
374                    $(
375                        let default = if $use_module_args {
376                            match get_string_default_config_value(&args, $string_configuration_name, $string_default) {
377                                Ok(v) => v,
378                                Err(e) => {
379                                    context.log_warning(&format!("{e}"));
380                                    return raw::Status::Err as c_int;
381                                }
382                            }
383                        } else {
384                            $string_default
385                        };
386                        register_string_configuration(&context, $string_configuration_name, $string_configuration_val, default, $string_flags_options, $string_on_changed);
387                    )*
388                )?
389                $(
390                    $(
391                        let default = if $use_module_args {
392                            match get_bool_default_config_value(&args, $bool_configuration_name, $bool_default) {
393                                Ok(v) => v,
394                                Err(e) => {
395                                    context.log_warning(&format!("{e}"));
396                                    return raw::Status::Err as c_int;
397                                }
398                            }
399                        } else {
400                            $bool_default
401                        };
402                        register_bool_configuration(&context, $bool_configuration_name, $bool_configuration_val, default, $bool_flags_options, $bool_on_changed);
403                    )*
404                )?
405                $(
406                    $(
407                        let default = if $use_module_args {
408                            match get_enum_default_config_value(&args, $enum_configuration_name, $enum_default) {
409                                Ok(v) => v,
410                                Err(e) => {
411                                    context.log_warning(&format!("{e}"));
412                                    return raw::Status::Err as c_int;
413                                }
414                            }
415                        } else {
416                            $enum_default
417                        };
418                        register_enum_configuration(&context, $enum_configuration_name, $enum_configuration_val, default, $enum_flags_options, $enum_on_changed);
419                    )*
420                )?
421                if let Some(load_config) = raw::RedisModule_LoadConfigs {
422                    load_config(ctx);
423                }
424
425                $(
426                    $crate::redis_command!(ctx, $module_config_get_command, |ctx, args: Vec<RedisString>| {
427                        module_config_get(ctx, args, $module_name)
428                    }, "", 0, 0, 0, "");
429                )?
430
431                $(
432                    $crate::redis_command!(ctx, $module_config_set_command, |ctx, args: Vec<RedisString>| {
433                        module_config_set(ctx, args, $module_name)
434                    }, "", 0, 0, 0, "");
435                )?
436            )?
437
438            raw::register_info_function(ctx, Some(__info_func));
439
440            if let Err(e) = register_server_events(&context) {
441                context.log_warning(&format!("{e}"));
442                return raw::Status::Err as c_int;
443            }
444
445            $(
446                if $init_func(&context, &args) == $crate::Status::Err {
447                    return $crate::Status::Err as c_int;
448                }
449            )*
450
451            raw::Status::Ok as c_int
452        }
453
454        #[no_mangle]
455        #[allow(non_snake_case)]
456        pub extern "C" fn RedisModule_OnUnload(
457            ctx: *mut $crate::raw::RedisModuleCtx
458        ) -> std::os::raw::c_int {
459            use std::os::raw::c_int;
460
461            let context = $crate::Context::new(ctx);
462            $(
463                if $deinit_func(&context) == $crate::Status::Err {
464                    return $crate::Status::Err as c_int;
465                }
466            )*
467
468            $crate::raw::Status::Ok as c_int
469        }
470    }
471}