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;
14#[cfg(any(test, feature = "test-shims"))]
15use crate::test_shims::try_call;
16use crate::{
17 add_info_begin_dict_field, add_info_end_dict_field, add_info_field_double,
18 add_info_field_long_long, add_info_field_str, add_info_field_unsigned_long_long, raw, utils,
19 Status,
20};
21use crate::{add_info_section, ValkeyResult};
22use crate::{ValkeyError, ValkeyString, ValkeyValue};
23use std::ops::Deref;
24
25use std::ffi::CStr;
26
27use self::call_reply::{create_promise_call_reply, CallResult, PromiseCallReply};
28use self::thread_safe::ValkeyLockIndicator;
29
30mod timer;
31
32pub mod auth;
33pub mod blocked;
34pub mod call_reply;
35pub mod client;
36pub mod commands;
37pub mod filter;
38pub mod info;
39pub mod keys_cursor;
40pub mod server_events;
41pub mod thread_safe;
42
43pub struct CallOptionsBuilder {
44 options: String,
45}
46
47impl Default for CallOptionsBuilder {
48 fn default() -> Self {
49 CallOptionsBuilder {
50 options: "v".to_string(),
51 }
52 }
53}
54
55#[derive(Clone)]
56pub struct CallOptions {
57 options: CString,
58}
59
60#[derive(Clone)]
61#[cfg(all(any(
62 feature = "min-valkey-compatibility-version-8-0",
63 feature = "min-redis-compatibility-version-7-2"
64)))]
65pub struct BlockingCallOptions {
66 options: CString,
67}
68
69#[derive(Copy, Clone)]
70pub enum CallOptionResp {
71 Resp2,
72 Resp3,
73 Auto,
74}
75
76impl CallOptionsBuilder {
77 pub fn new() -> CallOptionsBuilder {
78 Self::default()
79 }
80
81 fn add_flag(&mut self, flag: &str) {
82 self.options.push_str(flag);
83 }
84
85 pub fn no_writes(mut self) -> CallOptionsBuilder {
87 self.add_flag("W");
88 self
89 }
90
91 pub fn script_mode(mut self) -> CallOptionsBuilder {
96 self.add_flag("S");
97 self
98 }
99
100 pub fn verify_acl(mut self) -> CallOptionsBuilder {
103 self.add_flag("C");
104 self
105 }
106
107 pub fn verify_oom(mut self) -> CallOptionsBuilder {
109 self.add_flag("M");
110 self
111 }
112
113 pub fn errors_as_replies(mut self) -> CallOptionsBuilder {
116 self.add_flag("E");
117 self
118 }
119
120 pub fn replicate(mut self) -> CallOptionsBuilder {
122 self.add_flag("!");
123 self
124 }
125
126 pub fn resp(mut self, resp: CallOptionResp) -> CallOptionsBuilder {
128 match resp {
129 CallOptionResp::Auto => self.add_flag("0"),
130 CallOptionResp::Resp2 => (),
131 CallOptionResp::Resp3 => self.add_flag("3"),
132 }
133 self
134 }
135
136 pub fn build(self) -> CallOptions {
138 CallOptions {
139 options: CString::new(self.options).unwrap(), }
141 }
142
143 #[cfg(all(any(
147 feature = "min-valkey-compatibility-version-8-0",
148 feature = "min-redis-compatibility-version-7-2"
149 )))]
150 pub fn build_blocking(mut self) -> BlockingCallOptions {
151 self.add_flag("K");
152 BlockingCallOptions {
153 options: CString::new(self.options).unwrap(), }
155 }
156}
157
158pub struct DetachedContext {
162 pub(crate) ctx: AtomicPtr<raw::RedisModuleCtx>,
163}
164
165impl DetachedContext {
166 pub const fn new() -> Self {
167 DetachedContext {
168 ctx: AtomicPtr::new(ptr::null_mut()),
169 }
170 }
171}
172
173impl Default for DetachedContext {
174 fn default() -> Self {
175 Self::new()
176 }
177}
178
179pub struct DetachedContextGuard {
186 pub(crate) ctx: Context,
187}
188
189unsafe impl ValkeyLockIndicator for DetachedContextGuard {}
190
191impl Drop for DetachedContextGuard {
192 fn drop(&mut self) {
193 unsafe {
194 raw::RedisModule_ThreadSafeContextUnlock.unwrap()(self.ctx.ctx);
195 };
196 }
197}
198
199impl Deref for DetachedContextGuard {
200 type Target = Context;
201
202 fn deref(&self) -> &Self::Target {
203 &self.ctx
204 }
205}
206
207impl DetachedContext {
208 pub fn log(&self, level: ValkeyLogLevel, message: &str) {
209 let c = self.ctx.load(Ordering::Relaxed);
210 crate::logging::log_internal(c, level, message);
211 }
212
213 pub fn log_debug(&self, message: &str) {
214 self.log(ValkeyLogLevel::Debug, message);
215 }
216
217 pub fn log_notice(&self, message: &str) {
218 self.log(ValkeyLogLevel::Notice, message);
219 }
220
221 pub fn log_verbose(&self, message: &str) {
222 self.log(ValkeyLogLevel::Verbose, message);
223 }
224
225 pub fn log_warning(&self, message: &str) {
226 self.log(ValkeyLogLevel::Warning, message);
227 }
228
229 pub fn set_context(&self, ctx: &Context) -> Result<(), ValkeyError> {
230 let c = self.ctx.load(Ordering::Relaxed);
231 if !c.is_null() {
232 return Err(ValkeyError::Str("Detached context is already set"));
233 }
234 let ctx = unsafe { raw::RedisModule_GetDetachedThreadSafeContext.unwrap()(ctx.ctx) };
235 self.ctx.store(ctx, Ordering::Relaxed);
236 Ok(())
237 }
238
239 pub fn lock(&self) -> DetachedContextGuard {
244 let c = self.ctx.load(Ordering::Relaxed);
245 unsafe { raw::RedisModule_ThreadSafeContextLock.unwrap()(c) };
246 let ctx = Context::new(c);
247 DetachedContextGuard { ctx }
248 }
249}
250
251unsafe impl Send for DetachedContext {}
252unsafe impl Sync for DetachedContext {}
253
254#[derive(Debug)]
257pub struct Context {
258 pub ctx: *mut raw::RedisModuleCtx,
259}
260
261#[derive(Debug)]
267pub struct ContextUserScope<'ctx> {
268 ctx: &'ctx Context,
269 user: *mut raw::RedisModuleUser,
270}
271
272impl<'ctx> Drop for ContextUserScope<'ctx> {
273 fn drop(&mut self) {
274 self.ctx.deautenticate_user();
275 unsafe { raw::RedisModule_FreeModuleUser.unwrap()(self.user) };
276 }
277}
278
279impl<'ctx> ContextUserScope<'ctx> {
280 fn new(ctx: &'ctx Context, user: *mut raw::RedisModuleUser) -> ContextUserScope<'ctx> {
281 ContextUserScope { ctx, user }
282 }
283}
284
285pub struct StrCallArgs<'a> {
286 is_owner: bool,
287 args: Vec<*mut raw::RedisModuleString>,
288 phantom: std::marker::PhantomData<&'a raw::RedisModuleString>,
290}
291
292impl<'a> Drop for StrCallArgs<'a> {
293 fn drop(&mut self) {
294 if self.is_owner {
295 self.args.iter_mut().for_each(|v| unsafe {
296 raw::RedisModule_FreeString.unwrap()(std::ptr::null_mut(), *v)
297 });
298 }
299 }
300}
301
302impl<'a, T: AsRef<[u8]> + ?Sized> From<&'a [&T]> for StrCallArgs<'a> {
303 fn from(vals: &'a [&T]) -> Self {
304 StrCallArgs {
305 is_owner: true,
306 args: vals
307 .iter()
308 .map(|v| ValkeyString::create_from_slice(std::ptr::null_mut(), v.as_ref()).take())
309 .collect(),
310 phantom: std::marker::PhantomData,
311 }
312 }
313}
314
315impl<'a> From<&'a [&ValkeyString]> for StrCallArgs<'a> {
316 fn from(vals: &'a [&ValkeyString]) -> Self {
317 StrCallArgs {
318 is_owner: false,
319 args: vals.iter().map(|v| v.inner).collect(),
320 phantom: std::marker::PhantomData,
321 }
322 }
323}
324
325impl<'a, const SIZE: usize, T: ?Sized> From<&'a [&T; SIZE]> for StrCallArgs<'a>
326where
327 for<'b> &'a [&'b T]: Into<StrCallArgs<'a>>,
328{
329 fn from(vals: &'a [&T; SIZE]) -> Self {
330 vals.as_ref().into()
331 }
332}
333
334impl<'a> StrCallArgs<'a> {
335 pub(crate) fn args_mut(&mut self) -> &mut [*mut raw::RedisModuleString] {
336 &mut self.args
337 }
338}
339
340impl Context {
341 pub const fn new(ctx: *mut raw::RedisModuleCtx) -> Self {
342 Self { ctx }
343 }
344
345 #[must_use]
346 pub const fn dummy() -> Self {
347 Self {
348 ctx: ptr::null_mut(),
349 }
350 }
351
352 pub fn log(&self, level: ValkeyLogLevel, message: &str) {
353 crate::logging::log_internal(self.ctx, level, message);
354 }
355
356 pub fn log_debug(&self, message: &str) {
357 self.log(ValkeyLogLevel::Debug, message);
358 }
359
360 pub fn log_notice(&self, message: &str) {
361 self.log(ValkeyLogLevel::Notice, message);
362 }
363
364 pub fn log_verbose(&self, message: &str) {
365 self.log(ValkeyLogLevel::Verbose, message);
366 }
367
368 pub fn log_warning(&self, message: &str) {
369 self.log(ValkeyLogLevel::Warning, message);
370 }
371
372 pub fn auto_memory(&self) {
376 unsafe {
377 raw::RedisModule_AutoMemory.unwrap()(self.ctx);
378 }
379 }
380
381 #[must_use]
385 pub fn is_keys_position_request(&self) -> bool {
386 if cfg!(test) {
388 return false;
389 }
390
391 (unsafe { raw::RedisModule_IsKeysPositionRequest.unwrap()(self.ctx) }) != 0
392 }
393
394 pub fn key_at_pos(&self, pos: i32) {
398 unsafe {
401 raw::RedisModule_KeyAtPos.unwrap()(self.ctx, pos as c_int);
402 }
403 }
404
405 fn call_internal<
406 'ctx,
407 'a,
408 T: Into<StrCallArgs<'a>>,
409 R: From<PromiseCallReply<'static, 'ctx>>,
410 >(
411 &'ctx self,
412 command: &str,
413 fmt: *const c_char,
414 args: T,
415 ) -> R {
416 let mut call_args: StrCallArgs = args.into();
417 let final_args = call_args.args_mut();
418
419 #[cfg(any(test, feature = "test-shims"))]
420 if let Some(reply) = try_call(self.ctx, command, final_args) {
423 let promise = create_promise_call_reply(self, NonNull::new(reply));
424 return R::from(promise);
425 }
426
427 let cmd = CString::new(command).unwrap();
428 let reply: *mut raw::RedisModuleCallReply = unsafe {
429 let p_call = raw::RedisModule_Call.unwrap();
430 p_call(
431 self.ctx,
432 cmd.as_ptr(),
433 fmt,
434 final_args.as_mut_ptr(),
435 final_args.len(),
436 )
437 };
438 let promise = create_promise_call_reply(self, NonNull::new(reply));
439 R::from(promise)
440 }
441
442 pub fn call<'a, T: Into<StrCallArgs<'a>>>(&self, command: &str, args: T) -> ValkeyResult {
443 self.call_internal::<_, CallResult>(command, raw::FMT, args)
444 .map_or_else(|e| Err(e.into()), |v| Ok((&v).into()))
445 }
446
447 pub fn call_ext<'a, T: Into<StrCallArgs<'a>>, R: From<CallResult<'static>>>(
451 &self,
452 command: &str,
453 options: &CallOptions,
454 args: T,
455 ) -> R {
456 let res: CallResult<'static> =
457 self.call_internal(command, options.options.as_ptr() as *const c_char, args);
458 R::from(res)
459 }
460
461 #[cfg(all(any(
463 feature = "min-valkey-compatibility-version-8-0",
464 feature = "min-redis-compatibility-version-7-2"
465 )))]
466 pub fn call_blocking<
467 'ctx,
468 'a,
469 T: Into<StrCallArgs<'a>>,
470 R: From<PromiseCallReply<'static, 'ctx>>,
471 >(
472 &'ctx self,
473 command: &str,
474 options: &BlockingCallOptions,
475 args: T,
476 ) -> R {
477 self.call_internal(command, options.options.as_ptr() as *const c_char, args)
478 }
479
480 #[must_use]
481 pub fn str_as_legal_resp_string(s: &str) -> CString {
482 CString::new(
483 s.chars()
484 .map(|c| match c {
485 '\r' | '\n' | '\0' => b' ',
486 _ => c as u8,
487 })
488 .collect::<Vec<_>>(),
489 )
490 .unwrap()
491 }
492
493 #[allow(clippy::must_use_candidate)]
494 pub fn reply_simple_string(&self, s: &str) -> raw::Status {
495 let msg = Self::str_as_legal_resp_string(s);
496 raw::reply_with_simple_string(self.ctx, msg.as_ptr())
497 }
498
499 #[allow(clippy::must_use_candidate)]
500 pub fn reply_error_string(&self, s: &str) -> raw::Status {
501 let msg = Self::str_as_legal_resp_string(s);
502 unsafe { raw::RedisModule_ReplyWithError.unwrap()(self.ctx, msg.as_ptr()).into() }
503 }
504
505 #[cfg(feature = "min-valkey-compatibility-version-8-0")]
506 pub fn add_acl_category(&self, s: &str) -> raw::Status {
507 let acl_flags = Self::str_as_legal_resp_string(s);
508 unsafe { raw::RedisModule_AddACLCategory.unwrap()(self.ctx, acl_flags.as_ptr()).into() }
509 }
510
511 #[cfg(all(any(
512 feature = "min-redis-compatibility-version-7-2",
513 feature = "min-valkey-compatibility-version-8-0"
514 ),))]
515 pub fn set_acl_category(
516 &self,
517 command_name: *const c_char,
518 acl_flags: *const c_char,
519 ) -> raw::Status {
520 unsafe {
521 let command = raw::RedisModule_GetCommand.unwrap()(self.ctx, command_name);
522 raw::RedisModule_SetCommandACLCategories.unwrap()(command, acl_flags).into()
523 }
524 }
525
526 pub fn reply_with_key(&self, result: ValkeyValueKey) -> raw::Status {
527 match result {
528 ValkeyValueKey::Integer(i) => raw::reply_with_long_long(self.ctx, i),
529 ValkeyValueKey::String(s) => {
530 raw::reply_with_string_buffer(self.ctx, s.as_ptr().cast::<c_char>(), s.len())
531 }
532 ValkeyValueKey::BulkString(b) => {
533 raw::reply_with_string_buffer(self.ctx, b.as_ptr().cast::<c_char>(), b.len())
534 }
535 ValkeyValueKey::BulkValkeyString(s) => raw::reply_with_string(self.ctx, s.inner),
536 ValkeyValueKey::Bool(b) => raw::reply_with_bool(self.ctx, b.into()),
537 }
538 }
539
540 #[allow(clippy::must_use_candidate)]
544 pub fn reply(&self, result: ValkeyResult) -> raw::Status {
545 match result {
546 Ok(ValkeyValue::Bool(v)) => raw::reply_with_bool(self.ctx, v.into()),
547 Ok(ValkeyValue::Integer(v)) => raw::reply_with_long_long(self.ctx, v),
548 Ok(ValkeyValue::Float(v)) => raw::reply_with_double(self.ctx, v),
549 Ok(ValkeyValue::SimpleStringStatic(s)) => {
550 let msg = CString::new(s).unwrap();
551 raw::reply_with_simple_string(self.ctx, msg.as_ptr())
552 }
553
554 Ok(ValkeyValue::SimpleString(s)) => {
555 let msg = CString::new(s).unwrap();
556 raw::reply_with_simple_string(self.ctx, msg.as_ptr())
557 }
558
559 Ok(ValkeyValue::BulkString(s)) => {
560 raw::reply_with_string_buffer(self.ctx, s.as_ptr().cast::<c_char>(), s.len())
561 }
562
563 Ok(ValkeyValue::BigNumber(s)) => {
564 raw::reply_with_big_number(self.ctx, s.as_ptr().cast::<c_char>(), s.len())
565 }
566
567 Ok(ValkeyValue::VerbatimString((format, data))) => raw::reply_with_verbatim_string(
568 self.ctx,
569 data.as_ptr().cast(),
570 data.len(),
571 format.0.as_ptr().cast(),
572 ),
573
574 Ok(ValkeyValue::BulkValkeyString(s)) => raw::reply_with_string(self.ctx, s.inner),
575
576 Ok(ValkeyValue::StringBuffer(s)) => {
577 raw::reply_with_string_buffer(self.ctx, s.as_ptr().cast::<c_char>(), s.len())
578 }
579
580 Ok(ValkeyValue::Array(array)) => {
581 raw::reply_with_array(self.ctx, array.len() as c_long);
582
583 for elem in array {
584 self.reply(Ok(elem));
585 }
586
587 raw::Status::Ok
588 }
589
590 Ok(ValkeyValue::Map(map)) => {
591 raw::reply_with_map(self.ctx, map.len() as c_long);
592
593 for (key, value) in map {
594 self.reply_with_key(key);
595 self.reply(Ok(value));
596 }
597
598 raw::Status::Ok
599 }
600
601 Ok(ValkeyValue::OrderedMap(map)) => {
602 raw::reply_with_map(self.ctx, map.len() as c_long);
603
604 for (key, value) in map {
605 self.reply_with_key(key);
606 self.reply(Ok(value));
607 }
608
609 raw::Status::Ok
610 }
611
612 Ok(ValkeyValue::Set(set)) => {
613 raw::reply_with_set(self.ctx, set.len() as c_long);
614 set.into_iter().for_each(|e| {
615 self.reply_with_key(e);
616 });
617
618 raw::Status::Ok
619 }
620
621 Ok(ValkeyValue::OrderedSet(set)) => {
622 raw::reply_with_set(self.ctx, set.len() as c_long);
623 set.into_iter().for_each(|e| {
624 self.reply_with_key(e);
625 });
626
627 raw::Status::Ok
628 }
629
630 Ok(ValkeyValue::Null) => raw::reply_with_null(self.ctx),
631
632 Ok(ValkeyValue::NoReply) => raw::Status::Ok,
633
634 Ok(ValkeyValue::StaticError(s)) => self.reply_error_string(s),
635
636 Err(ValkeyError::WrongArity) => unsafe {
637 if self.is_keys_position_request() {
638 raw::Status::Err
640 } else {
641 raw::RedisModule_WrongArity.unwrap()(self.ctx).into()
642 }
643 },
644
645 Err(ValkeyError::WrongType) => {
646 self.reply_error_string(ValkeyError::WrongType.to_string().as_str())
647 }
648
649 Err(ValkeyError::String(s)) => self.reply_error_string(s.as_str()),
650
651 Err(ValkeyError::Str(s)) => self.reply_error_string(s),
652 }
653 }
654
655 #[must_use]
656 pub fn open_key(&self, key: &ValkeyString) -> ValkeyKey {
657 ValkeyKey::open(self.ctx, key)
658 }
659
660 #[must_use]
661 pub fn open_key_with_flags(&self, key: &ValkeyString, flags: KeyFlags) -> ValkeyKey {
662 ValkeyKey::open_with_flags(self.ctx, key, flags)
663 }
664
665 #[must_use]
666 pub fn open_key_writable(&self, key: &ValkeyString) -> ValkeyKeyWritable {
667 ValkeyKeyWritable::open(self.ctx, key)
668 }
669
670 #[must_use]
671 pub fn open_key_writable_with_flags(
672 &self,
673 key: &ValkeyString,
674 flags: KeyFlags,
675 ) -> ValkeyKeyWritable {
676 ValkeyKeyWritable::open_with_flags(self.ctx, key, flags)
677 }
678
679 pub fn replicate_verbatim(&self) {
680 raw::replicate_verbatim(self.ctx);
681 }
682
683 pub fn replicate<'a, T: Into<StrCallArgs<'a>>>(&self, command: &str, args: T) {
685 raw::replicate(self.ctx, command, args);
686 }
687
688 #[must_use]
689 pub fn create_string<T: Into<Vec<u8>>>(&self, s: T) -> ValkeyString {
690 ValkeyString::create(NonNull::new(self.ctx), s)
691 }
692
693 #[must_use]
694 pub const fn get_raw(&self) -> *mut raw::RedisModuleCtx {
695 self.ctx
696 }
697
698 pub unsafe fn export_shared_api(
702 &self,
703 func: *const ::std::os::raw::c_void,
704 name: *const ::std::os::raw::c_char,
705 ) {
706 raw::export_shared_api(self.ctx, func, name);
707 }
708
709 #[allow(clippy::must_use_candidate)]
713 pub fn notify_keyspace_event(
714 &self,
715 event_type: raw::NotifyEvent,
716 event: &str,
717 keyname: &ValkeyString,
718 ) -> raw::Status {
719 unsafe { raw::notify_keyspace_event(self.ctx, event_type, event, keyname) }
720 }
721
722 pub fn current_command_name(&self) -> Result<String, ValkeyError> {
723 unsafe {
724 match raw::RedisModule_GetCurrentCommandName {
725 Some(cmd) => Ok(CStr::from_ptr(cmd(self.ctx)).to_str().unwrap().to_string()),
726 None => Err(ValkeyError::Str(
727 "API RedisModule_GetCurrentCommandName is not available",
728 )),
729 }
730 }
731 }
732
733 pub fn get_server_version(&self) -> Result<Version, ValkeyError> {
736 self.get_server_version_internal(false)
737 }
738
739 pub fn get_server_version_rm_call(&self) -> Result<Version, ValkeyError> {
741 self.get_server_version_internal(true)
742 }
743
744 pub fn version_from_info(info: ValkeyValue) -> Result<Version, ValkeyError> {
745 if let ValkeyValue::SimpleString(info_str) = info {
746 if let Some(ver) = utils::get_regexp_captures(
747 info_str.as_str(),
748 r"(?m)\bredis_version:([0-9]+)\.([0-9]+)\.([0-9]+)\b",
749 ) {
750 return Ok(Version {
751 major: ver[0].parse::<c_int>().unwrap(),
752 minor: ver[1].parse::<c_int>().unwrap(),
753 patch: ver[2].parse::<c_int>().unwrap(),
754 });
755 }
756 }
757 Err(ValkeyError::Str("Error getting redis_version"))
758 }
759
760 #[allow(clippy::not_unsafe_ptr_arg_deref)]
761 fn get_server_version_internal(&self, force_use_rm_call: bool) -> Result<Version, ValkeyError> {
762 match unsafe { raw::RedisModule_GetServerVersion } {
763 Some(api) if !force_use_rm_call => {
764 Ok(Version::from(unsafe { api() }))
766 }
767 _ => {
768 if let Ok(info) = self.call("info", &["server"]) {
770 Self::version_from_info(info)
771 } else {
772 Err(ValkeyError::Str("Error calling \"info server\""))
773 }
774 }
775 }
776 }
777 pub fn set_module_options(&self, options: ModuleOptions) {
778 unsafe { raw::RedisModule_SetModuleOptions.unwrap()(self.ctx, options.bits()) };
779 }
780
781 pub fn get_flags(&self) -> ContextFlags {
787 ContextFlags::from_bits_truncate(unsafe {
788 raw::RedisModule_GetContextFlags.unwrap()(self.ctx)
789 })
790 }
791
792 pub fn get_current_user(&self) -> ValkeyString {
794 let user = unsafe { raw::RedisModule_GetCurrentUserName.unwrap()(self.ctx) };
795 ValkeyString::from_redis_module_string(ptr::null_mut(), user)
796 }
797
798 pub fn authenticate_user(
803 &self,
804 user_name: &ValkeyString,
805 ) -> Result<ContextUserScope<'_>, ValkeyError> {
806 let user = unsafe { raw::RedisModule_GetModuleUserFromUserName.unwrap()(user_name.inner) };
807 if user.is_null() {
808 return Err(ValkeyError::Str("User does not exists or disabled"));
809 }
810 unsafe { raw::RedisModule_SetContextUser.unwrap()(self.ctx, user) };
811 Ok(ContextUserScope::new(self, user))
812 }
813
814 fn deautenticate_user(&self) {
815 unsafe { raw::RedisModule_SetContextUser.unwrap()(self.ctx, ptr::null_mut()) };
816 }
817
818 pub fn acl_check_key_permission(
822 &self,
823 user_name: &ValkeyString,
824 key_name: &ValkeyString,
825 permissions: &AclPermissions,
826 ) -> Result<(), ValkeyError> {
827 let user = unsafe { raw::RedisModule_GetModuleUserFromUserName.unwrap()(user_name.inner) };
828 if user.is_null() {
829 return Err(ValkeyError::Str("User does not exists or disabled"));
830 }
831 let acl_permission_result: raw::Status = unsafe {
832 raw::RedisModule_ACLCheckKeyPermissions.unwrap()(
833 user,
834 key_name.inner,
835 permissions.bits(),
836 )
837 }
838 .into();
839 unsafe { raw::RedisModule_FreeModuleUser.unwrap()(user) };
840 let acl_permission_result: Result<(), &str> = acl_permission_result.into();
841 acl_permission_result
842 .map_err(|_e| ValkeyError::Str("User does not have permissions on key"))
843 }
844
845 api!(
846 [RedisModule_AddPostNotificationJob],
847 pub fn add_post_notification_job<F: FnOnce(&Context) + 'static>(
860 &self,
861 callback: F,
862 ) -> Status {
863 let callback = Box::into_raw(Box::new(Some(callback)));
864 unsafe {
865 RedisModule_AddPostNotificationJob(
866 self.ctx,
867 Some(post_notification_job::<F>),
868 callback as *mut c_void,
869 Some(post_notification_job_free_callback::<F>),
870 )
871 }
872 .into()
873 }
874 );
875
876 api!(
877 [RedisModule_AvoidReplicaTraffic],
878 pub fn avoid_replication_traffic(&self) -> bool {
894 unsafe { RedisModule_AvoidReplicaTraffic() == 1 }
895 }
896 );
897}
898
899extern "C" fn post_notification_job_free_callback<F: FnOnce(&Context)>(pd: *mut c_void) {
900 unsafe {
901 drop(Box::from_raw(pd as *mut Option<F>));
902 };
903}
904
905extern "C" fn post_notification_job<F: FnOnce(&Context)>(
906 ctx: *mut raw::RedisModuleCtx,
907 pd: *mut c_void,
908) {
909 let callback = unsafe { &mut *(pd as *mut Option<F>) };
910 let ctx = Context::new(ctx);
911 callback.take().map_or_else(
912 || {
913 ctx.log(
914 ValkeyLogLevel::Warning,
915 "Got a None callback on post notification job.",
916 )
917 },
918 |callback| {
919 callback(&ctx);
920 },
921 );
922}
923
924unsafe impl ValkeyLockIndicator for Context {}
925
926bitflags! {
927 #[derive(Debug)]
930 pub struct AclPermissions : c_int {
931 const ACCESS = raw::REDISMODULE_CMD_KEY_ACCESS as c_int;
933
934 const INSERT = raw::REDISMODULE_CMD_KEY_INSERT as c_int;
936
937 const DELETE = raw::REDISMODULE_CMD_KEY_DELETE as c_int;
939
940 const UPDATE = raw::REDISMODULE_CMD_KEY_UPDATE as c_int;
942 }
943}
944
945#[derive(Debug, Clone)]
947pub enum InfoContextBuilderFieldBottomLevelValue {
948 String(String),
950 I64(i64),
952 U64(u64),
954 F64(f64),
956}
957
958impl From<String> for InfoContextBuilderFieldBottomLevelValue {
959 fn from(value: String) -> Self {
960 Self::String(value)
961 }
962}
963
964impl From<&str> for InfoContextBuilderFieldBottomLevelValue {
965 fn from(value: &str) -> Self {
966 Self::String(value.to_owned())
967 }
968}
969
970impl From<i64> for InfoContextBuilderFieldBottomLevelValue {
971 fn from(value: i64) -> Self {
972 Self::I64(value)
973 }
974}
975
976impl From<u64> for InfoContextBuilderFieldBottomLevelValue {
977 fn from(value: u64) -> Self {
978 Self::U64(value)
979 }
980}
981
982#[derive(Debug, Clone)]
983pub enum InfoContextBuilderFieldTopLevelValue {
984 Value(InfoContextBuilderFieldBottomLevelValue),
986 Dictionary {
1010 name: String,
1011 fields: InfoContextFieldBottomLevelData,
1012 },
1013}
1014
1015impl<T: Into<InfoContextBuilderFieldBottomLevelValue>> From<T>
1016 for InfoContextBuilderFieldTopLevelValue
1017{
1018 fn from(value: T) -> Self {
1019 Self::Value(value.into())
1020 }
1021}
1022
1023#[derive(Debug)]
1026pub struct InfoContextBuilderDictionaryBuilder<'a> {
1027 info_section_builder: InfoContextBuilderSectionBuilder<'a>,
1029 name: String,
1031 fields: InfoContextFieldBottomLevelData,
1033}
1034
1035impl<'a> InfoContextBuilderDictionaryBuilder<'a> {
1036 pub fn field<F: Into<InfoContextBuilderFieldBottomLevelValue>>(
1038 mut self,
1039 name: &str,
1040 value: F,
1041 ) -> ValkeyResult<Self> {
1042 if self.fields.iter().any(|k| k.0 .0 == name) {
1043 return Err(ValkeyError::String(format!(
1044 "Found duplicate key '{name}' in the info dictionary '{}'",
1045 self.name
1046 )));
1047 }
1048
1049 self.fields.push((name.to_owned(), value.into()).into());
1050 Ok(self)
1051 }
1052
1053 pub fn build_dictionary(self) -> ValkeyResult<InfoContextBuilderSectionBuilder<'a>> {
1055 let name = self.name;
1056 let name_ref = name.clone();
1057 self.info_section_builder.field(
1058 &name_ref,
1059 InfoContextBuilderFieldTopLevelValue::Dictionary {
1060 name,
1061 fields: self.fields.to_owned(),
1062 },
1063 )
1064 }
1065}
1066
1067#[derive(Debug)]
1069pub struct InfoContextBuilderSectionBuilder<'a> {
1070 info_builder: InfoContextBuilder<'a>,
1072 name: String,
1074 fields: InfoContextFieldTopLevelData,
1076}
1077
1078impl<'a> InfoContextBuilderSectionBuilder<'a> {
1079 pub fn field<F: Into<InfoContextBuilderFieldTopLevelValue>>(
1081 mut self,
1082 name: &str,
1083 value: F,
1084 ) -> ValkeyResult<Self> {
1085 if self.fields.iter().any(|(k, _)| k == name) {
1086 return Err(ValkeyError::String(format!(
1087 "Found duplicate key '{name}' in the info section '{}'",
1088 self.name
1089 )));
1090 }
1091 self.fields.push((name.to_owned(), value.into()));
1092 Ok(self)
1093 }
1094
1095 pub fn add_dictionary(self, dictionary_name: &str) -> InfoContextBuilderDictionaryBuilder<'a> {
1097 InfoContextBuilderDictionaryBuilder {
1098 info_section_builder: self,
1099 name: dictionary_name.to_owned(),
1100 fields: InfoContextFieldBottomLevelData::default(),
1101 }
1102 }
1103
1104 pub fn build_section(mut self) -> ValkeyResult<InfoContextBuilder<'a>> {
1106 if self
1107 .info_builder
1108 .sections
1109 .iter()
1110 .any(|(k, _)| k == &self.name)
1111 {
1112 return Err(ValkeyError::String(format!(
1113 "Found duplicate section in the Info reply: {}",
1114 self.name
1115 )));
1116 }
1117
1118 self.info_builder
1119 .sections
1120 .push((self.name.clone(), self.fields));
1121
1122 Ok(self.info_builder)
1123 }
1124}
1125
1126#[derive(Debug, Clone)]
1128#[repr(transparent)]
1129pub struct InfoContextBottomLevelFieldData(pub (String, InfoContextBuilderFieldBottomLevelValue));
1130impl Deref for InfoContextBottomLevelFieldData {
1131 type Target = (String, InfoContextBuilderFieldBottomLevelValue);
1132
1133 fn deref(&self) -> &Self::Target {
1134 &self.0
1135 }
1136}
1137impl std::ops::DerefMut for InfoContextBottomLevelFieldData {
1138 fn deref_mut(&mut self) -> &mut Self::Target {
1139 &mut self.0
1140 }
1141}
1142
1143impl<T: Into<InfoContextBuilderFieldBottomLevelValue>> From<(String, T)>
1144 for InfoContextBottomLevelFieldData
1145{
1146 fn from(value: (String, T)) -> Self {
1147 Self((value.0, value.1.into()))
1148 }
1149}
1150#[derive(Debug, Default, Clone)]
1153#[repr(transparent)]
1154pub struct InfoContextFieldBottomLevelData(pub Vec<InfoContextBottomLevelFieldData>);
1155impl Deref for InfoContextFieldBottomLevelData {
1156 type Target = Vec<InfoContextBottomLevelFieldData>;
1157
1158 fn deref(&self) -> &Self::Target {
1159 &self.0
1160 }
1161}
1162impl std::ops::DerefMut for InfoContextFieldBottomLevelData {
1163 fn deref_mut(&mut self) -> &mut Self::Target {
1164 &mut self.0
1165 }
1166}
1167
1168pub type InfoContextFieldTopLevelData = Vec<(String, InfoContextBuilderFieldTopLevelValue)>;
1171pub type OneInfoSectionData = (String, InfoContextFieldTopLevelData);
1173pub type InfoContextTreeData = Vec<OneInfoSectionData>;
1175
1176impl<T: Into<InfoContextBuilderFieldBottomLevelValue>> From<BTreeMap<String, T>>
1177 for InfoContextFieldBottomLevelData
1178{
1179 fn from(value: BTreeMap<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
1189impl<T: Into<InfoContextBuilderFieldBottomLevelValue>> From<HashMap<String, T>>
1190 for InfoContextFieldBottomLevelData
1191{
1192 fn from(value: HashMap<String, T>) -> Self {
1193 Self(
1194 value
1195 .into_iter()
1196 .map(|e| (e.0, e.1.into()).into())
1197 .collect(),
1198 )
1199 }
1200}
1201
1202#[derive(Debug)]
1203pub struct InfoContextBuilder<'a> {
1204 context: &'a InfoContext,
1205 sections: InfoContextTreeData,
1206}
1207impl<'a> InfoContextBuilder<'a> {
1208 fn add_bottom_level_field(
1209 &self,
1210 key: &str,
1211 value: &InfoContextBuilderFieldBottomLevelValue,
1212 ) -> ValkeyResult<()> {
1213 use InfoContextBuilderFieldBottomLevelValue as BottomLevel;
1214
1215 match value {
1216 BottomLevel::String(string) => add_info_field_str(self.context.ctx, key, string),
1217 BottomLevel::I64(number) => add_info_field_long_long(self.context.ctx, key, *number),
1218 BottomLevel::U64(number) => {
1219 add_info_field_unsigned_long_long(self.context.ctx, key, *number)
1220 }
1221 BottomLevel::F64(number) => add_info_field_double(self.context.ctx, key, *number),
1222 }
1223 .into()
1224 }
1225 fn add_top_level_fields(&self, fields: &InfoContextFieldTopLevelData) -> ValkeyResult<()> {
1228 use InfoContextBuilderFieldTopLevelValue as TopLevel;
1229
1230 fields.iter().try_for_each(|(key, value)| match value {
1231 TopLevel::Value(bottom_level) => self.add_bottom_level_field(key, bottom_level),
1232 TopLevel::Dictionary { name, fields } => {
1233 std::convert::Into::<ValkeyResult<()>>::into(add_info_begin_dict_field(
1234 self.context.ctx,
1235 name,
1236 ))?;
1237 fields
1238 .iter()
1239 .try_for_each(|f| self.add_bottom_level_field(&f.0 .0, &f.0 .1))?;
1240 add_info_end_dict_field(self.context.ctx).into()
1241 }
1242 })
1243 }
1244
1245 fn finalise_data(&self) -> ValkeyResult<()> {
1246 self.sections
1247 .iter()
1248 .try_for_each(|(section_name, section_fields)| -> ValkeyResult<()> {
1249 if add_info_section(self.context.ctx, Some(section_name)) == Status::Ok {
1250 self.add_top_level_fields(section_fields)
1251 } else {
1252 Ok(())
1254 }
1255 })
1256 }
1257
1258 pub fn build_info(self) -> ValkeyResult<&'a InfoContext> {
1260 self.finalise_data().map(|_| self.context)
1261 }
1262
1263 pub fn add_section(self, name: &'a str) -> InfoContextBuilderSectionBuilder<'a> {
1265 InfoContextBuilderSectionBuilder {
1266 info_builder: self,
1267 name: name.to_owned(),
1268 fields: InfoContextFieldTopLevelData::new(),
1269 }
1270 }
1271
1272 pub(crate) fn add_section_unchecked(mut self, section: OneInfoSectionData) -> Self {
1275 self.sections.push(section);
1276 self
1277 }
1278}
1279
1280impl<'a> From<&'a InfoContext> for InfoContextBuilder<'a> {
1281 fn from(context: &'a InfoContext) -> Self {
1282 Self {
1283 context,
1284 sections: InfoContextTreeData::new(),
1285 }
1286 }
1287}
1288
1289#[derive(Debug)]
1290pub struct InfoContext {
1291 pub ctx: *mut raw::RedisModuleInfoCtx,
1292}
1293
1294impl InfoContext {
1295 pub const fn new(ctx: *mut raw::RedisModuleInfoCtx) -> Self {
1296 Self { ctx }
1297 }
1298
1299 pub fn builder(&self) -> InfoContextBuilder<'_> {
1301 InfoContextBuilder::from(self)
1302 }
1303
1304 pub fn build_one_section<T: Into<OneInfoSectionData>>(&self, data: T) -> ValkeyResult<()> {
1306 self.builder()
1307 .add_section_unchecked(data.into())
1308 .build_info()?;
1309 Ok(())
1310 }
1311
1312 #[deprecated = "Please use [`InfoContext::builder`] instead."]
1313 pub fn add_info_section(&self, name: Option<&str>) -> Status {
1316 add_info_section(self.ctx, name)
1317 }
1318
1319 #[deprecated = "Please use [`InfoContext::builder`] instead."]
1320 pub fn add_info_field_str(&self, name: &str, content: &str) -> Status {
1324 add_info_field_str(self.ctx, name, content)
1325 }
1326
1327 #[deprecated = "Please use [`InfoContext::builder`] instead."]
1328 pub fn add_info_field_long_long(&self, name: &str, value: c_longlong) -> Status {
1332 add_info_field_long_long(self.ctx, name, value)
1333 }
1334}
1335
1336bitflags! {
1337 pub struct ContextFlags : c_int {
1338 const LUA = raw::REDISMODULE_CTX_FLAGS_LUA as c_int;
1340
1341 const MULTI = raw::REDISMODULE_CTX_FLAGS_MULTI as c_int;
1343
1344 const MASTER = raw::REDISMODULE_CTX_FLAGS_MASTER as c_int;
1346
1347 const SLAVE = raw::REDISMODULE_CTX_FLAGS_SLAVE as c_int;
1349
1350 const READONLY = raw::REDISMODULE_CTX_FLAGS_READONLY as c_int;
1352
1353 const CLUSTER = raw::REDISMODULE_CTX_FLAGS_CLUSTER as c_int;
1355
1356 const AOF = raw::REDISMODULE_CTX_FLAGS_AOF as c_int;
1358
1359 const RDB = raw::REDISMODULE_CTX_FLAGS_RDB as c_int;
1361
1362 const MAXMEMORY = raw::REDISMODULE_CTX_FLAGS_MAXMEMORY as c_int;
1364
1365 const EVICTED = raw::REDISMODULE_CTX_FLAGS_EVICT as c_int;
1367
1368 const OOM = raw::REDISMODULE_CTX_FLAGS_OOM as c_int;
1370
1371 const OOM_WARNING = raw::REDISMODULE_CTX_FLAGS_OOM_WARNING as c_int;
1373
1374 const REPLICATED = raw::REDISMODULE_CTX_FLAGS_REPLICATED as c_int;
1376
1377 const LOADING = raw::REDISMODULE_CTX_FLAGS_LOADING as c_int;
1379
1380 const REPLICA_IS_STALE = raw::REDISMODULE_CTX_FLAGS_REPLICA_IS_STALE as c_int;
1382
1383 const REPLICA_IS_CONNECTING = raw::REDISMODULE_CTX_FLAGS_REPLICA_IS_CONNECTING as c_int;
1385
1386 const REPLICA_IS_TRANSFERRING = raw::REDISMODULE_CTX_FLAGS_REPLICA_IS_TRANSFERRING as c_int;
1388
1389 const REPLICA_IS_ONLINE = raw::REDISMODULE_CTX_FLAGS_REPLICA_IS_ONLINE as c_int;
1391
1392 const ACTIVE_CHILD = raw::REDISMODULE_CTX_FLAGS_ACTIVE_CHILD as c_int;
1394
1395 const IS_CHILD = raw::REDISMODULE_CTX_FLAGS_IS_CHILD as c_int;
1397
1398 const MULTI_DIRTY = raw::REDISMODULE_CTX_FLAGS_MULTI_DIRTY as c_int;
1400
1401 const DENY_BLOCKING = raw::REDISMODULE_CTX_FLAGS_DENY_BLOCKING as c_int;
1404
1405 const FLAGS_RESP3 = raw::REDISMODULE_CTX_FLAGS_RESP3 as c_int;
1407
1408 const ASYNC_LOADING = raw::REDISMODULE_CTX_FLAGS_ASYNC_LOADING as c_int;
1410 }
1411}