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