1use bitflags::bitflags;
2use redis_module_macros_internals::api;
3use std::collections::{BTreeMap, HashMap};
4use std::ffi::CString;
5use std::os::raw::c_void;
6use std::os::raw::{c_char, c_int, c_long, c_longlong};
7use std::ptr::{self, NonNull};
8use std::sync::atomic::{AtomicPtr, Ordering};
9
10use crate::key::{KeyFlags, RedisKey, RedisKeyWritable};
11use crate::logging::RedisLogLevel;
12use crate::raw::{ModuleOptions, Version};
13use crate::redisvalue::RedisValueKey;
14use crate::{
15 add_info_begin_dict_field, add_info_end_dict_field, add_info_field_double,
16 add_info_field_long_long, add_info_field_str, add_info_field_unsigned_long_long, raw, utils,
17 Status,
18};
19use crate::{add_info_section, RedisResult};
20use crate::{RedisError, RedisString, RedisValue};
21use std::ops::Deref;
22
23use std::ffi::CStr;
24
25use self::call_reply::{create_promise_call_reply, CallResult, PromiseCallReply};
26use self::thread_safe::RedisLockIndicator;
27
28mod timer;
29
30pub mod blocked;
31pub mod call_reply;
32pub mod commands;
33pub mod defrag;
34pub mod info;
35pub mod key_cursor;
36pub mod keys_cursor;
37pub mod server_events;
38pub mod thread_safe;
39
40pub struct CallOptionsBuilder {
41 options: String,
42}
43
44impl Default for CallOptionsBuilder {
45 fn default() -> Self {
46 CallOptionsBuilder {
47 options: "v".to_string(),
48 }
49 }
50}
51
52#[derive(Clone)]
53pub struct CallOptions {
54 options: CString,
55}
56
57#[derive(Clone)]
58#[cfg(any(
59 feature = "min-redis-compatibility-version-8-0",
60 feature = "min-redis-compatibility-version-7-4",
61 feature = "min-redis-compatibility-version-7-2"
62))]
63pub struct BlockingCallOptions {
64 options: CString,
65}
66
67#[derive(Copy, Clone)]
68pub enum CallOptionResp {
69 Resp2,
70 Resp3,
71 Auto,
72}
73
74impl CallOptionsBuilder {
75 pub fn new() -> CallOptionsBuilder {
76 Self::default()
77 }
78
79 fn add_flag(&mut self, flag: &str) {
80 self.options.push_str(flag);
81 }
82
83 pub fn no_writes(mut self) -> CallOptionsBuilder {
85 self.add_flag("W");
86 self
87 }
88
89 pub fn script_mode(mut self) -> CallOptionsBuilder {
94 self.add_flag("S");
95 self
96 }
97
98 pub fn verify_acl(mut self) -> CallOptionsBuilder {
101 self.add_flag("C");
102 self
103 }
104
105 pub fn verify_oom(mut self) -> CallOptionsBuilder {
107 self.add_flag("M");
108 self
109 }
110
111 pub fn errors_as_replies(mut self) -> CallOptionsBuilder {
114 self.add_flag("E");
115 self
116 }
117
118 pub fn replicate(mut self) -> CallOptionsBuilder {
120 self.add_flag("!");
121 self
122 }
123
124 pub fn resp(mut self, resp: CallOptionResp) -> CallOptionsBuilder {
126 match resp {
127 CallOptionResp::Auto => self.add_flag("0"),
128 CallOptionResp::Resp2 => (),
129 CallOptionResp::Resp3 => self.add_flag("3"),
130 }
131 self
132 }
133
134 pub fn build(self) -> CallOptions {
136 CallOptions {
137 options: CString::new(self.options).unwrap(), }
139 }
140
141 #[cfg(any(
145 feature = "min-redis-compatibility-version-7-4",
146 feature = "min-redis-compatibility-version-7-2"
147 ))]
148 pub fn build_blocking(mut self) -> BlockingCallOptions {
149 self.add_flag("K");
150 BlockingCallOptions {
151 options: CString::new(self.options).unwrap(), }
153 }
154}
155
156pub struct DetachedContext {
160 pub(crate) ctx: AtomicPtr<raw::RedisModuleCtx>,
161}
162
163impl DetachedContext {
164 pub const fn new() -> Self {
165 DetachedContext {
166 ctx: AtomicPtr::new(ptr::null_mut()),
167 }
168 }
169}
170
171impl Default for DetachedContext {
172 fn default() -> Self {
173 Self::new()
174 }
175}
176
177pub struct DetachedContextGuard {
184 pub(crate) ctx: Context,
185}
186
187unsafe impl RedisLockIndicator for DetachedContextGuard {}
188
189impl Drop for DetachedContextGuard {
190 fn drop(&mut self) {
191 unsafe {
192 raw::RedisModule_ThreadSafeContextUnlock.unwrap()(self.ctx.ctx);
193 };
194 }
195}
196
197impl Deref for DetachedContextGuard {
198 type Target = Context;
199
200 fn deref(&self) -> &Self::Target {
201 &self.ctx
202 }
203}
204
205impl DetachedContext {
206 pub fn log(&self, level: RedisLogLevel, message: &str) {
207 let c = self.ctx.load(Ordering::Relaxed);
208 crate::logging::log_internal(c, level, message);
209 }
210
211 pub fn log_debug(&self, message: &str) {
212 self.log(RedisLogLevel::Debug, message);
213 }
214
215 pub fn log_notice(&self, message: &str) {
216 self.log(RedisLogLevel::Notice, message);
217 }
218
219 pub fn log_verbose(&self, message: &str) {
220 self.log(RedisLogLevel::Verbose, message);
221 }
222
223 pub fn log_warning(&self, message: &str) {
224 self.log(RedisLogLevel::Warning, message);
225 }
226
227 pub fn set_context(&self, ctx: &Context) -> Result<(), RedisError> {
228 let c = self.ctx.load(Ordering::Relaxed);
229 if !c.is_null() {
230 return Err(RedisError::Str("Detached context is already set"));
231 }
232 let ctx = unsafe { raw::RedisModule_GetDetachedThreadSafeContext.unwrap()(ctx.ctx) };
233 self.ctx.store(ctx, Ordering::Relaxed);
234 Ok(())
235 }
236
237 pub fn lock(&self) -> DetachedContextGuard {
242 let c = self.ctx.load(Ordering::Relaxed);
243 unsafe { raw::RedisModule_ThreadSafeContextLock.unwrap()(c) };
244 let ctx = Context::new(c);
245 DetachedContextGuard { ctx }
246 }
247}
248
249unsafe impl Send for DetachedContext {}
250unsafe impl Sync for DetachedContext {}
251
252#[derive(Debug)]
255pub struct Context {
256 pub ctx: *mut raw::RedisModuleCtx,
257}
258
259#[derive(Debug)]
265pub struct ContextUserScope<'ctx> {
266 ctx: &'ctx Context,
267 user: *mut raw::RedisModuleUser,
268}
269
270impl<'ctx> Drop for ContextUserScope<'ctx> {
271 fn drop(&mut self) {
272 self.ctx.deautenticate_user();
273 unsafe { raw::RedisModule_FreeModuleUser.unwrap()(self.user) };
274 }
275}
276
277impl<'ctx> ContextUserScope<'ctx> {
278 fn new(ctx: &'ctx Context, user: *mut raw::RedisModuleUser) -> ContextUserScope<'ctx> {
279 ContextUserScope { ctx, user }
280 }
281}
282
283pub struct StrCallArgs<'a> {
284 is_owner: bool,
285 args: Vec<*mut raw::RedisModuleString>,
286 phantom: std::marker::PhantomData<&'a raw::RedisModuleString>,
288}
289
290impl<'a> Drop for StrCallArgs<'a> {
291 fn drop(&mut self) {
292 if self.is_owner {
293 self.args.iter_mut().for_each(|v| unsafe {
294 raw::RedisModule_FreeString.unwrap()(std::ptr::null_mut(), *v)
295 });
296 }
297 }
298}
299
300impl<'a, T: AsRef<[u8]> + ?Sized> From<&'a [&T]> for StrCallArgs<'a> {
301 fn from(vals: &'a [&T]) -> Self {
302 StrCallArgs {
303 is_owner: true,
304 args: vals
305 .iter()
306 .map(|v| RedisString::create_from_slice(std::ptr::null_mut(), v.as_ref()).take())
307 .collect(),
308 phantom: std::marker::PhantomData,
309 }
310 }
311}
312
313impl<'a> From<&'a [&RedisString]> for StrCallArgs<'a> {
314 fn from(vals: &'a [&RedisString]) -> Self {
315 StrCallArgs {
316 is_owner: false,
317 args: vals.iter().map(|v| v.inner).collect(),
318 phantom: std::marker::PhantomData,
319 }
320 }
321}
322
323impl<'a, const SIZE: usize, T: ?Sized> From<&'a [&T; SIZE]> for StrCallArgs<'a>
324where
325 for<'b> &'a [&'b T]: Into<StrCallArgs<'a>>,
326{
327 fn from(vals: &'a [&T; SIZE]) -> Self {
328 vals.as_ref().into()
329 }
330}
331
332impl<'a> StrCallArgs<'a> {
333 pub(crate) fn args_mut(&mut self) -> &mut [*mut raw::RedisModuleString] {
334 &mut self.args
335 }
336}
337
338impl Context {
339 pub const fn new(ctx: *mut raw::RedisModuleCtx) -> Self {
340 Self { ctx }
341 }
342
343 #[must_use]
344 pub const fn dummy() -> Self {
345 Self {
346 ctx: ptr::null_mut(),
347 }
348 }
349
350 pub fn log(&self, level: RedisLogLevel, message: &str) {
351 crate::logging::log_internal(self.ctx, level, message);
352 }
353
354 pub fn log_debug(&self, message: &str) {
355 self.log(RedisLogLevel::Debug, message);
356 }
357
358 pub fn log_notice(&self, message: &str) {
359 self.log(RedisLogLevel::Notice, message);
360 }
361
362 pub fn log_verbose(&self, message: &str) {
363 self.log(RedisLogLevel::Verbose, message);
364 }
365
366 pub fn log_warning(&self, message: &str) {
367 self.log(RedisLogLevel::Warning, message);
368 }
369
370 pub fn auto_memory(&self) {
374 unsafe {
375 raw::RedisModule_AutoMemory.unwrap()(self.ctx);
376 }
377 }
378
379 #[must_use]
383 pub fn is_keys_position_request(&self) -> bool {
384 if cfg!(test) {
386 return false;
387 }
388
389 (unsafe { raw::RedisModule_IsKeysPositionRequest.unwrap()(self.ctx) }) != 0
390 }
391
392 pub fn key_at_pos(&self, pos: i32) {
396 unsafe {
399 raw::RedisModule_KeyAtPos.unwrap()(self.ctx, pos as c_int);
400 }
401 }
402
403 fn call_internal<
404 'ctx,
405 'a,
406 T: Into<StrCallArgs<'a>>,
407 R: From<PromiseCallReply<'static, 'ctx>>,
408 >(
409 &'ctx self,
410 command: &str,
411 fmt: *const c_char,
412 args: T,
413 ) -> R {
414 let mut call_args: StrCallArgs = args.into();
415 let final_args = call_args.args_mut();
416
417 let cmd = CString::new(command).unwrap();
418 let reply: *mut raw::RedisModuleCallReply = unsafe {
419 let p_call = raw::RedisModule_Call.unwrap();
420 p_call(
421 self.ctx,
422 cmd.as_ptr(),
423 fmt,
424 final_args.as_mut_ptr(),
425 final_args.len(),
426 )
427 };
428 let promise = create_promise_call_reply(self, NonNull::new(reply));
429 R::from(promise)
430 }
431
432 pub fn call<'a, T: Into<StrCallArgs<'a>>>(&self, command: &str, args: T) -> RedisResult {
433 self.call_internal::<_, CallResult>(command, raw::FMT, args)
434 .map_or_else(|e| Err(e.into()), |v| Ok((&v).into()))
435 }
436
437 pub fn call_ext<'a, T: Into<StrCallArgs<'a>>, R: From<CallResult<'static>>>(
441 &self,
442 command: &str,
443 options: &CallOptions,
444 args: T,
445 ) -> R {
446 let res: CallResult<'static> =
447 self.call_internal(command, options.options.as_ptr() as *const c_char, args);
448 R::from(res)
449 }
450
451 #[cfg(any(
453 feature = "min-redis-compatibility-version-7-4",
454 feature = "min-redis-compatibility-version-7-2"
455 ))]
456 pub fn call_blocking<
457 'ctx,
458 'a,
459 T: Into<StrCallArgs<'a>>,
460 R: From<PromiseCallReply<'static, 'ctx>>,
461 >(
462 &'ctx self,
463 command: &str,
464 options: &BlockingCallOptions,
465 args: T,
466 ) -> R {
467 self.call_internal(command, options.options.as_ptr() as *const c_char, args)
468 }
469
470 #[must_use]
471 pub fn str_as_legal_resp_string(s: &str) -> CString {
472 CString::new(
473 s.chars()
474 .map(|c| match c {
475 '\r' | '\n' | '\0' => b' ',
476 _ => c as u8,
477 })
478 .collect::<Vec<_>>(),
479 )
480 .unwrap()
481 }
482
483 #[allow(clippy::must_use_candidate)]
484 pub fn reply_simple_string(&self, s: &str) -> raw::Status {
485 let msg = Self::str_as_legal_resp_string(s);
486 raw::reply_with_simple_string(self.ctx, msg.as_ptr())
487 }
488
489 #[allow(clippy::must_use_candidate)]
490 pub fn reply_error_string(&self, s: &str) -> raw::Status {
491 let msg = Self::str_as_legal_resp_string(s);
492 unsafe { raw::RedisModule_ReplyWithError.unwrap()(self.ctx, msg.as_ptr()).into() }
493 }
494
495 pub fn reply_with_key(&self, result: RedisValueKey) -> raw::Status {
496 match result {
497 RedisValueKey::Integer(i) => raw::reply_with_long_long(self.ctx, i),
498 RedisValueKey::String(s) => {
499 raw::reply_with_string_buffer(self.ctx, s.as_ptr().cast::<c_char>(), s.len())
500 }
501 RedisValueKey::BulkString(b) => {
502 raw::reply_with_string_buffer(self.ctx, b.as_ptr().cast::<c_char>(), b.len())
503 }
504 RedisValueKey::BulkRedisString(s) => raw::reply_with_string(self.ctx, s.inner),
505 RedisValueKey::Bool(b) => raw::reply_with_bool(self.ctx, b.into()),
506 }
507 }
508
509 #[allow(clippy::must_use_candidate)]
513 pub fn reply(&self, result: RedisResult) -> raw::Status {
514 match result {
515 Ok(RedisValue::Bool(v)) => raw::reply_with_bool(self.ctx, v.into()),
516 Ok(RedisValue::Integer(v)) => raw::reply_with_long_long(self.ctx, v),
517 Ok(RedisValue::Float(v)) => raw::reply_with_double(self.ctx, v),
518 Ok(RedisValue::SimpleStringStatic(s)) => {
519 let msg = CString::new(s).unwrap();
520 raw::reply_with_simple_string(self.ctx, msg.as_ptr())
521 }
522
523 Ok(RedisValue::SimpleString(s)) => {
524 let msg = CString::new(s).unwrap();
525 raw::reply_with_simple_string(self.ctx, msg.as_ptr())
526 }
527
528 Ok(RedisValue::BulkString(s)) => {
529 raw::reply_with_string_buffer(self.ctx, s.as_ptr().cast::<c_char>(), s.len())
530 }
531
532 Ok(RedisValue::BigNumber(s)) => {
533 raw::reply_with_big_number(self.ctx, s.as_ptr().cast::<c_char>(), s.len())
534 }
535
536 Ok(RedisValue::VerbatimString((format, data))) => raw::reply_with_verbatim_string(
537 self.ctx,
538 data.as_ptr().cast(),
539 data.len(),
540 format.0.as_ptr().cast(),
541 ),
542
543 Ok(RedisValue::BulkRedisString(s)) => raw::reply_with_string(self.ctx, s.inner),
544
545 Ok(RedisValue::StringBuffer(s)) => {
546 raw::reply_with_string_buffer(self.ctx, s.as_ptr().cast::<c_char>(), s.len())
547 }
548
549 Ok(RedisValue::Array(array)) => {
550 raw::reply_with_array(self.ctx, array.len() as c_long);
551
552 for elem in array {
553 self.reply(Ok(elem));
554 }
555
556 raw::Status::Ok
557 }
558
559 Ok(RedisValue::Map(map)) => {
560 raw::reply_with_map(self.ctx, map.len() as c_long);
561
562 for (key, value) in map {
563 self.reply_with_key(key);
564 self.reply(Ok(value));
565 }
566
567 raw::Status::Ok
568 }
569
570 Ok(RedisValue::OrderedMap(map)) => {
571 raw::reply_with_map(self.ctx, map.len() as c_long);
572
573 for (key, value) in map {
574 self.reply_with_key(key);
575 self.reply(Ok(value));
576 }
577
578 raw::Status::Ok
579 }
580
581 Ok(RedisValue::Set(set)) => {
582 raw::reply_with_set(self.ctx, set.len() as c_long);
583 set.into_iter().for_each(|e| {
584 self.reply_with_key(e);
585 });
586
587 raw::Status::Ok
588 }
589
590 Ok(RedisValue::OrderedSet(set)) => {
591 raw::reply_with_set(self.ctx, set.len() as c_long);
592 set.into_iter().for_each(|e| {
593 self.reply_with_key(e);
594 });
595
596 raw::Status::Ok
597 }
598
599 Ok(RedisValue::Null) => raw::reply_with_null(self.ctx),
600
601 Ok(RedisValue::NoReply) => raw::Status::Ok,
602
603 Ok(RedisValue::StaticError(s)) => self.reply_error_string(s),
604
605 Err(RedisError::WrongArity) => unsafe {
606 if self.is_keys_position_request() {
607 raw::Status::Err
609 } else {
610 raw::RedisModule_WrongArity.unwrap()(self.ctx).into()
611 }
612 },
613
614 Err(RedisError::WrongType) => {
615 self.reply_error_string(RedisError::WrongType.to_string().as_str())
616 }
617
618 Err(RedisError::String(s)) => self.reply_error_string(s.as_str()),
619
620 Err(RedisError::Str(s)) => self.reply_error_string(s),
621 }
622 }
623
624 #[must_use]
625 pub fn open_key(&self, key: &RedisString) -> RedisKey {
626 RedisKey::open(self.ctx, key)
627 }
628
629 #[must_use]
630 pub fn open_key_with_flags(&self, key: &RedisString, flags: KeyFlags) -> RedisKey {
631 RedisKey::open_with_flags(self.ctx, key, flags)
632 }
633
634 #[must_use]
635 pub fn open_key_writable(&self, key: &RedisString) -> RedisKeyWritable {
636 RedisKeyWritable::open(self.ctx, key)
637 }
638
639 #[must_use]
640 pub fn open_key_writable_with_flags(
641 &self,
642 key: &RedisString,
643 flags: KeyFlags,
644 ) -> RedisKeyWritable {
645 RedisKeyWritable::open_with_flags(self.ctx, key, flags)
646 }
647
648 pub fn replicate_verbatim(&self) {
649 raw::replicate_verbatim(self.ctx);
650 }
651
652 pub fn replicate<'a, T: Into<StrCallArgs<'a>>>(&self, command: &str, args: T) {
654 raw::replicate(self.ctx, command, args);
655 }
656
657 #[must_use]
658 pub fn create_string<T: Into<Vec<u8>>>(&self, s: T) -> RedisString {
659 RedisString::create(NonNull::new(self.ctx), s)
660 }
661
662 #[must_use]
663 pub const fn get_raw(&self) -> *mut raw::RedisModuleCtx {
664 self.ctx
665 }
666
667 pub unsafe fn export_shared_api(
671 &self,
672 func: *const ::std::os::raw::c_void,
673 name: *const ::std::os::raw::c_char,
674 ) {
675 raw::export_shared_api(self.ctx, func, name);
676 }
677
678 #[allow(clippy::must_use_candidate)]
682 pub fn notify_keyspace_event(
683 &self,
684 event_type: raw::NotifyEvent,
685 event: &str,
686 keyname: &RedisString,
687 ) -> raw::Status {
688 unsafe { raw::notify_keyspace_event(self.ctx, event_type, event, keyname) }
689 }
690
691 pub fn current_command_name(&self) -> Result<String, RedisError> {
692 unsafe {
693 match raw::RedisModule_GetCurrentCommandName {
694 Some(cmd) => Ok(CStr::from_ptr(cmd(self.ctx)).to_str().unwrap().to_string()),
695 None => Err(RedisError::Str(
696 "API RedisModule_GetCurrentCommandName is not available",
697 )),
698 }
699 }
700 }
701
702 pub fn get_redis_version(&self) -> Result<Version, RedisError> {
705 self.get_redis_version_internal(false)
706 }
707
708 pub fn get_redis_version_rm_call(&self) -> Result<Version, RedisError> {
710 self.get_redis_version_internal(true)
711 }
712
713 pub fn version_from_info(info: RedisValue) -> Result<Version, RedisError> {
714 if let RedisValue::SimpleString(info_str) = info {
715 if let Some(ver) = utils::get_regexp_captures(
716 info_str.as_str(),
717 r"(?m)\bredis_version:([0-9]+)\.([0-9]+)\.([0-9]+)\b",
718 ) {
719 return Ok(Version {
720 major: ver[0].parse::<c_int>().unwrap(),
721 minor: ver[1].parse::<c_int>().unwrap(),
722 patch: ver[2].parse::<c_int>().unwrap(),
723 });
724 }
725 }
726 Err(RedisError::Str("Error getting redis_version"))
727 }
728
729 #[allow(clippy::not_unsafe_ptr_arg_deref)]
730 fn get_redis_version_internal(&self, force_use_rm_call: bool) -> Result<Version, RedisError> {
731 match unsafe { raw::RedisModule_GetServerVersion } {
732 Some(api) if !force_use_rm_call => {
733 Ok(Version::from(unsafe { api() }))
735 }
736 _ => {
737 if let Ok(info) = self.call("info", &["server"]) {
739 Self::version_from_info(info)
740 } else {
741 Err(RedisError::Str("Error calling \"info server\""))
742 }
743 }
744 }
745 }
746 pub fn set_module_options(&self, options: ModuleOptions) {
747 unsafe { raw::RedisModule_SetModuleOptions.unwrap()(self.ctx, options.bits()) };
748 }
749
750 pub fn get_flags(&self) -> ContextFlags {
756 ContextFlags::from_bits_truncate(unsafe {
757 raw::RedisModule_GetContextFlags.unwrap()(self.ctx)
758 })
759 }
760
761 pub fn get_current_user(&self) -> RedisString {
763 let user = unsafe { raw::RedisModule_GetCurrentUserName.unwrap()(self.ctx) };
764 RedisString::from_redis_module_string(ptr::null_mut(), user)
765 }
766
767 pub fn authenticate_user(
772 &self,
773 user_name: &RedisString,
774 ) -> Result<ContextUserScope<'_>, RedisError> {
775 let user = unsafe { raw::RedisModule_GetModuleUserFromUserName.unwrap()(user_name.inner) };
776 if user.is_null() {
777 return Err(RedisError::Str("User does not exists or disabled"));
778 }
779 unsafe { raw::RedisModule_SetContextUser.unwrap()(self.ctx, user) };
780 Ok(ContextUserScope::new(self, user))
781 }
782
783 fn deautenticate_user(&self) {
784 unsafe { raw::RedisModule_SetContextUser.unwrap()(self.ctx, ptr::null_mut()) };
785 }
786
787 pub fn acl_check_key_permission(
791 &self,
792 user_name: &RedisString,
793 key_name: &RedisString,
794 permissions: &AclPermissions,
795 ) -> Result<(), RedisError> {
796 let user = unsafe { raw::RedisModule_GetModuleUserFromUserName.unwrap()(user_name.inner) };
797 if user.is_null() {
798 return Err(RedisError::Str("User does not exists or disabled"));
799 }
800 let acl_permission_result: raw::Status = unsafe {
801 raw::RedisModule_ACLCheckKeyPermissions.unwrap()(
802 user,
803 key_name.inner,
804 permissions.bits(),
805 )
806 }
807 .into();
808 unsafe { raw::RedisModule_FreeModuleUser.unwrap()(user) };
809 let acl_permission_result: Result<(), &str> = acl_permission_result.into();
810 acl_permission_result.map_err(|_e| RedisError::Str("User does not have permissions on key"))
811 }
812
813 api!(
814 [RedisModule_AddPostNotificationJob],
815 pub fn add_post_notification_job<F: FnOnce(&Context) + 'static>(
828 &self,
829 callback: F,
830 ) -> Status {
831 let callback = Box::into_raw(Box::new(Some(callback)));
832 unsafe {
833 RedisModule_AddPostNotificationJob(
834 self.ctx,
835 Some(post_notification_job::<F>),
836 callback as *mut c_void,
837 Some(post_notification_job_free_callback::<F>),
838 )
839 }
840 .into()
841 }
842 );
843
844 api!(
845 [RedisModule_AvoidReplicaTraffic],
846 pub fn avoid_replication_traffic(&self) -> bool {
862 unsafe { RedisModule_AvoidReplicaTraffic() == 1 }
863 }
864 );
865
866 fn is_enterprise_internal(&self) -> Result<bool, RedisError> {
869 let info_res = self.call("info", &["server"])?;
870 let info = match &info_res {
871 RedisValue::BulkRedisString(res) => res.try_as_str()?,
872 RedisValue::SimpleString(res) => res.as_str(),
873 _ => return Err(RedisError::Str("Mismatch call reply type")),
874 };
875 Ok(info.contains("rlec_version:"))
876 }
877
878 pub fn is_enterprise(&self) -> bool {
880 self.is_enterprise_internal().unwrap_or_else(|e| {
881 log::error!("Failed getting deployment type, assuming oss. Error: {e}.");
882 false
883 })
884 }
885}
886
887extern "C" fn post_notification_job_free_callback<F: FnOnce(&Context)>(pd: *mut c_void) {
888 drop(unsafe { Box::from_raw(pd as *mut Option<F>) });
889}
890
891extern "C" fn post_notification_job<F: FnOnce(&Context)>(
892 ctx: *mut raw::RedisModuleCtx,
893 pd: *mut c_void,
894) {
895 let callback = unsafe { &mut *(pd as *mut Option<F>) };
896 let ctx = Context::new(ctx);
897 callback.take().map_or_else(
898 || {
899 ctx.log(
900 RedisLogLevel::Warning,
901 "Got a None callback on post notification job.",
902 )
903 },
904 |callback| {
905 callback(&ctx);
906 },
907 );
908}
909
910unsafe impl RedisLockIndicator for Context {}
911
912bitflags! {
913 #[derive(Debug)]
916 pub struct AclPermissions : c_int {
917 const ACCESS = raw::REDISMODULE_CMD_KEY_ACCESS as c_int;
919
920 const INSERT = raw::REDISMODULE_CMD_KEY_INSERT as c_int;
922
923 const DELETE = raw::REDISMODULE_CMD_KEY_DELETE as c_int;
925
926 const UPDATE = raw::REDISMODULE_CMD_KEY_UPDATE as c_int;
928 }
929}
930
931#[derive(Debug, Clone)]
933pub enum InfoContextBuilderFieldBottomLevelValue {
934 String(String),
936 I64(i64),
938 U64(u64),
940 F64(f64),
942}
943
944impl From<String> for InfoContextBuilderFieldBottomLevelValue {
945 fn from(value: String) -> Self {
946 Self::String(value)
947 }
948}
949
950impl From<&str> for InfoContextBuilderFieldBottomLevelValue {
951 fn from(value: &str) -> Self {
952 Self::String(value.to_owned())
953 }
954}
955
956impl From<i64> for InfoContextBuilderFieldBottomLevelValue {
957 fn from(value: i64) -> Self {
958 Self::I64(value)
959 }
960}
961
962impl From<u64> for InfoContextBuilderFieldBottomLevelValue {
963 fn from(value: u64) -> Self {
964 Self::U64(value)
965 }
966}
967
968#[derive(Debug, Clone)]
969pub enum InfoContextBuilderFieldTopLevelValue {
970 Value(InfoContextBuilderFieldBottomLevelValue),
972 Dictionary {
996 name: String,
997 fields: InfoContextFieldBottomLevelData,
998 },
999}
1000
1001impl<T: Into<InfoContextBuilderFieldBottomLevelValue>> From<T>
1002 for InfoContextBuilderFieldTopLevelValue
1003{
1004 fn from(value: T) -> Self {
1005 Self::Value(value.into())
1006 }
1007}
1008
1009#[derive(Debug)]
1012pub struct InfoContextBuilderDictionaryBuilder<'a> {
1013 info_section_builder: InfoContextBuilderSectionBuilder<'a>,
1015 name: String,
1017 fields: InfoContextFieldBottomLevelData,
1019}
1020
1021impl<'a> InfoContextBuilderDictionaryBuilder<'a> {
1022 pub fn field<F: Into<InfoContextBuilderFieldBottomLevelValue>>(
1024 mut self,
1025 name: &str,
1026 value: F,
1027 ) -> RedisResult<Self> {
1028 if self.fields.iter().any(|k| k.0 .0 == name) {
1029 return Err(RedisError::String(format!(
1030 "Found duplicate key '{name}' in the info dictionary '{}'",
1031 self.name
1032 )));
1033 }
1034
1035 self.fields.push((name.to_owned(), value.into()).into());
1036 Ok(self)
1037 }
1038
1039 pub fn build_dictionary(self) -> RedisResult<InfoContextBuilderSectionBuilder<'a>> {
1041 let name = self.name;
1042 let name_ref = name.clone();
1043 self.info_section_builder.field(
1044 &name_ref,
1045 InfoContextBuilderFieldTopLevelValue::Dictionary {
1046 name,
1047 fields: self.fields.to_owned(),
1048 },
1049 )
1050 }
1051}
1052
1053#[derive(Debug)]
1055pub struct InfoContextBuilderSectionBuilder<'a> {
1056 info_builder: InfoContextBuilder<'a>,
1058 name: String,
1060 fields: InfoContextFieldTopLevelData,
1062}
1063
1064impl<'a> InfoContextBuilderSectionBuilder<'a> {
1065 pub fn field<F: Into<InfoContextBuilderFieldTopLevelValue>>(
1067 mut self,
1068 name: &str,
1069 value: F,
1070 ) -> RedisResult<Self> {
1071 if self.fields.iter().any(|(k, _)| k == name) {
1072 return Err(RedisError::String(format!(
1073 "Found duplicate key '{name}' in the info section '{}'",
1074 self.name
1075 )));
1076 }
1077 self.fields.push((name.to_owned(), value.into()));
1078 Ok(self)
1079 }
1080
1081 pub fn add_dictionary(self, dictionary_name: &str) -> InfoContextBuilderDictionaryBuilder<'a> {
1083 InfoContextBuilderDictionaryBuilder {
1084 info_section_builder: self,
1085 name: dictionary_name.to_owned(),
1086 fields: InfoContextFieldBottomLevelData::default(),
1087 }
1088 }
1089
1090 pub fn build_section(mut self) -> RedisResult<InfoContextBuilder<'a>> {
1092 if self
1093 .info_builder
1094 .sections
1095 .iter()
1096 .any(|(k, _)| k == &self.name)
1097 {
1098 return Err(RedisError::String(format!(
1099 "Found duplicate section in the Info reply: {}",
1100 self.name
1101 )));
1102 }
1103
1104 self.info_builder
1105 .sections
1106 .push((self.name.clone(), self.fields));
1107
1108 Ok(self.info_builder)
1109 }
1110}
1111
1112#[derive(Debug, Clone)]
1114#[repr(transparent)]
1115pub struct InfoContextBottomLevelFieldData(pub (String, InfoContextBuilderFieldBottomLevelValue));
1116impl Deref for InfoContextBottomLevelFieldData {
1117 type Target = (String, InfoContextBuilderFieldBottomLevelValue);
1118
1119 fn deref(&self) -> &Self::Target {
1120 &self.0
1121 }
1122}
1123impl std::ops::DerefMut for InfoContextBottomLevelFieldData {
1124 fn deref_mut(&mut self) -> &mut Self::Target {
1125 &mut self.0
1126 }
1127}
1128
1129impl<T: Into<InfoContextBuilderFieldBottomLevelValue>> From<(String, T)>
1130 for InfoContextBottomLevelFieldData
1131{
1132 fn from(value: (String, T)) -> Self {
1133 Self((value.0, value.1.into()))
1134 }
1135}
1136#[derive(Debug, Default, Clone)]
1139#[repr(transparent)]
1140pub struct InfoContextFieldBottomLevelData(pub Vec<InfoContextBottomLevelFieldData>);
1141impl Deref for InfoContextFieldBottomLevelData {
1142 type Target = Vec<InfoContextBottomLevelFieldData>;
1143
1144 fn deref(&self) -> &Self::Target {
1145 &self.0
1146 }
1147}
1148impl std::ops::DerefMut for InfoContextFieldBottomLevelData {
1149 fn deref_mut(&mut self) -> &mut Self::Target {
1150 &mut self.0
1151 }
1152}
1153
1154pub type InfoContextFieldTopLevelData = Vec<(String, InfoContextBuilderFieldTopLevelValue)>;
1157pub type OneInfoSectionData = (String, InfoContextFieldTopLevelData);
1159pub type InfoContextTreeData = Vec<OneInfoSectionData>;
1161
1162impl<T: Into<InfoContextBuilderFieldBottomLevelValue>> From<BTreeMap<String, T>>
1163 for InfoContextFieldBottomLevelData
1164{
1165 fn from(value: BTreeMap<String, T>) -> Self {
1166 Self(
1167 value
1168 .into_iter()
1169 .map(|e| (e.0, e.1.into()).into())
1170 .collect(),
1171 )
1172 }
1173}
1174
1175impl<T: Into<InfoContextBuilderFieldBottomLevelValue>> From<HashMap<String, T>>
1176 for InfoContextFieldBottomLevelData
1177{
1178 fn from(value: HashMap<String, T>) -> Self {
1179 Self(
1180 value
1181 .into_iter()
1182 .map(|e| (e.0, e.1.into()).into())
1183 .collect(),
1184 )
1185 }
1186}
1187
1188#[derive(Debug)]
1189pub struct InfoContextBuilder<'a> {
1190 context: &'a InfoContext,
1191 sections: InfoContextTreeData,
1192}
1193impl<'a> InfoContextBuilder<'a> {
1194 fn add_bottom_level_field(
1195 &self,
1196 key: &str,
1197 value: &InfoContextBuilderFieldBottomLevelValue,
1198 ) -> RedisResult<()> {
1199 use InfoContextBuilderFieldBottomLevelValue as BottomLevel;
1200
1201 match value {
1202 BottomLevel::String(string) => add_info_field_str(self.context.ctx, key, string),
1203 BottomLevel::I64(number) => add_info_field_long_long(self.context.ctx, key, *number),
1204 BottomLevel::U64(number) => {
1205 add_info_field_unsigned_long_long(self.context.ctx, key, *number)
1206 }
1207 BottomLevel::F64(number) => add_info_field_double(self.context.ctx, key, *number),
1208 }
1209 .into()
1210 }
1211 fn add_top_level_fields(&self, fields: &InfoContextFieldTopLevelData) -> RedisResult<()> {
1214 use InfoContextBuilderFieldTopLevelValue as TopLevel;
1215
1216 fields.iter().try_for_each(|(key, value)| match value {
1217 TopLevel::Value(bottom_level) => self.add_bottom_level_field(key, bottom_level),
1218 TopLevel::Dictionary { name, fields } => {
1219 std::convert::Into::<RedisResult<()>>::into(add_info_begin_dict_field(
1220 self.context.ctx,
1221 name,
1222 ))?;
1223 fields
1224 .iter()
1225 .try_for_each(|f| self.add_bottom_level_field(&f.0 .0, &f.0 .1))?;
1226 add_info_end_dict_field(self.context.ctx).into()
1227 }
1228 })
1229 }
1230
1231 fn finalise_data(&self) -> RedisResult<()> {
1232 self.sections
1233 .iter()
1234 .try_for_each(|(section_name, section_fields)| -> RedisResult<()> {
1235 if add_info_section(self.context.ctx, Some(section_name)) == Status::Ok {
1236 self.add_top_level_fields(section_fields)
1237 } else {
1238 Ok(())
1240 }
1241 })
1242 }
1243
1244 pub fn build_info(self) -> RedisResult<&'a InfoContext> {
1246 self.finalise_data().map(|_| self.context)
1247 }
1248
1249 pub fn add_section(self, name: &'a str) -> InfoContextBuilderSectionBuilder<'a> {
1251 InfoContextBuilderSectionBuilder {
1252 info_builder: self,
1253 name: name.to_owned(),
1254 fields: InfoContextFieldTopLevelData::new(),
1255 }
1256 }
1257
1258 pub(crate) fn add_section_unchecked(mut self, section: OneInfoSectionData) -> Self {
1261 self.sections.push(section);
1262 self
1263 }
1264}
1265
1266impl<'a> From<&'a InfoContext> for InfoContextBuilder<'a> {
1267 fn from(context: &'a InfoContext) -> Self {
1268 Self {
1269 context,
1270 sections: InfoContextTreeData::new(),
1271 }
1272 }
1273}
1274
1275#[derive(Debug)]
1276pub struct InfoContext {
1277 pub ctx: *mut raw::RedisModuleInfoCtx,
1278}
1279
1280impl InfoContext {
1281 pub const fn new(ctx: *mut raw::RedisModuleInfoCtx) -> Self {
1282 Self { ctx }
1283 }
1284
1285 pub fn builder(&self) -> InfoContextBuilder<'_> {
1287 InfoContextBuilder::from(self)
1288 }
1289
1290 pub fn build_one_section<T: Into<OneInfoSectionData>>(&self, data: T) -> RedisResult<()> {
1292 self.builder()
1293 .add_section_unchecked(data.into())
1294 .build_info()?;
1295 Ok(())
1296 }
1297
1298 #[deprecated = "Please use [`InfoContext::builder`] instead."]
1299 pub fn add_info_section(&self, name: Option<&str>) -> Status {
1302 add_info_section(self.ctx, name)
1303 }
1304
1305 #[deprecated = "Please use [`InfoContext::builder`] instead."]
1306 pub fn add_info_field_str(&self, name: &str, content: &str) -> Status {
1310 add_info_field_str(self.ctx, name, content)
1311 }
1312
1313 #[deprecated = "Please use [`InfoContext::builder`] instead."]
1314 pub fn add_info_field_long_long(&self, name: &str, value: c_longlong) -> Status {
1318 add_info_field_long_long(self.ctx, name, value)
1319 }
1320}
1321
1322bitflags! {
1323 pub struct ContextFlags : c_int {
1324 const LUA = raw::REDISMODULE_CTX_FLAGS_LUA as c_int;
1326
1327 const MULTI = raw::REDISMODULE_CTX_FLAGS_MULTI as c_int;
1329
1330 const MASTER = raw::REDISMODULE_CTX_FLAGS_MASTER as c_int;
1332
1333 const SLAVE = raw::REDISMODULE_CTX_FLAGS_SLAVE as c_int;
1335
1336 const READONLY = raw::REDISMODULE_CTX_FLAGS_READONLY as c_int;
1338
1339 const CLUSTER = raw::REDISMODULE_CTX_FLAGS_CLUSTER as c_int;
1341
1342 const AOF = raw::REDISMODULE_CTX_FLAGS_AOF as c_int;
1344
1345 const RDB = raw::REDISMODULE_CTX_FLAGS_RDB as c_int;
1347
1348 const MAXMEMORY = raw::REDISMODULE_CTX_FLAGS_MAXMEMORY as c_int;
1350
1351 const EVICTED = raw::REDISMODULE_CTX_FLAGS_EVICT as c_int;
1353
1354 const OOM = raw::REDISMODULE_CTX_FLAGS_OOM as c_int;
1356
1357 const OOM_WARNING = raw::REDISMODULE_CTX_FLAGS_OOM_WARNING as c_int;
1359
1360 const REPLICATED = raw::REDISMODULE_CTX_FLAGS_REPLICATED as c_int;
1362
1363 const LOADING = raw::REDISMODULE_CTX_FLAGS_LOADING as c_int;
1365
1366 const REPLICA_IS_STALE = raw::REDISMODULE_CTX_FLAGS_REPLICA_IS_STALE as c_int;
1368
1369 const REPLICA_IS_CONNECTING = raw::REDISMODULE_CTX_FLAGS_REPLICA_IS_CONNECTING as c_int;
1371
1372 const REPLICA_IS_TRANSFERRING = raw::REDISMODULE_CTX_FLAGS_REPLICA_IS_TRANSFERRING as c_int;
1374
1375 const REPLICA_IS_ONLINE = raw::REDISMODULE_CTX_FLAGS_REPLICA_IS_ONLINE as c_int;
1377
1378 const ACTIVE_CHILD = raw::REDISMODULE_CTX_FLAGS_ACTIVE_CHILD as c_int;
1380
1381 const IS_CHILD = raw::REDISMODULE_CTX_FLAGS_IS_CHILD as c_int;
1383
1384 const MULTI_DIRTY = raw::REDISMODULE_CTX_FLAGS_MULTI_DIRTY as c_int;
1386
1387 const DENY_BLOCKING = raw::REDISMODULE_CTX_FLAGS_DENY_BLOCKING as c_int;
1390
1391 const FLAGS_RESP3 = raw::REDISMODULE_CTX_FLAGS_RESP3 as c_int;
1393
1394 const ASYNC_LOADING = raw::REDISMODULE_CTX_FLAGS_ASYNC_LOADING as c_int;
1396 }
1397}