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 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 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 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 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#[macro_export]
166macro_rules! redis_module {
167 (
168 name: $module_name:expr,
169 version: $module_version:expr,
170 allocator: ($allocator_type:ty, $allocator_init:expr),
174 data_types: [
175 $($data_type:ident),* $(,)*
176 ],
177 $(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 #[global_allocator]
241 static REDIS_MODULE_ALLOCATOR: $allocator_type = $allocator_init;
242
243 $(
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 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}