valkey_module/context/
mod.rs

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    /// Enable this option will not allow RM_Call to perform write commands
81    pub fn no_writes(mut self) -> CallOptionsBuilder {
82        self.add_flag("W");
83        self
84    }
85
86    /// Enable this option will run RM_Call is script mode.
87    /// This mean that Valkey will enable the following protections:
88    /// 1. Not allow running dangerous commands like 'shutdown'
89    /// 2. Not allow running write commands on OOM or if there are not enough good replica's connected
90    pub fn script_mode(mut self) -> CallOptionsBuilder {
91        self.add_flag("S");
92        self
93    }
94
95    /// Enable this option will perform ACL validation on the user attached to the context that
96    /// is used to invoke the call.
97    pub fn verify_acl(mut self) -> CallOptionsBuilder {
98        self.add_flag("C");
99        self
100    }
101
102    /// Enable this option will OOM validation before running the command
103    pub fn verify_oom(mut self) -> CallOptionsBuilder {
104        self.add_flag("M");
105        self
106    }
107
108    /// Enable this option will return error as CallReply object instead of setting errno (it is
109    /// usually recommend to enable it)
110    pub fn errors_as_replies(mut self) -> CallOptionsBuilder {
111        self.add_flag("E");
112        self
113    }
114
115    /// Enable this option will cause the command to be replicated to the replica and AOF
116    pub fn replicate(mut self) -> CallOptionsBuilder {
117        self.add_flag("!");
118        self
119    }
120
121    /// Allow control the protocol version in which the replies will be returned.
122    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    /// Construct a CallOption object that can be used to run commands using call_ext
132    pub fn build(self) -> CallOptions {
133        CallOptions {
134            options: CString::new(self.options).unwrap(), // the data will never contains internal \0 so it is safe to unwrap.
135        }
136    }
137
138    /// Construct a CallOption object that can be used to run commands using call_blocking.
139    /// The commands can be either blocking or none blocking. In case the command are blocking
140    /// (like `blpop`) a [FutureCallReply] will be returned.
141    #[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(), // the data will never contains internal \0 so it is safe to unwrap.
149        }
150    }
151}
152
153/// This struct allows logging when the Valkey GIL is not acquired.
154/// It is implemented `Send` and `Sync` so it can safely be used
155/// from within different threads.
156pub 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
174/// This object is returned after locking Valkey from [DetachedContext].
175/// On dispose, Valkey will be unlocked.
176/// This object implements [Deref] for [Context] so it can be used
177/// just like any Valkey [Context] for command invocation.
178/// **This object should not be used to return replies** because there is
179/// no real client behind this context to return replies to.
180pub 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    /// Lock Valkey for command invocation. Returns [DetachedContextGuard] which will unlock Valkey when dispose.
235    /// [DetachedContextGuard] implements [Deref<Target = Context>] so it can be used just like any Valkey [Context] for command invocation.
236    /// Locking Valkey when Valkey is already locked by the current thread is left unspecified.
237    /// However, this function will not return on the second call (it might panic or deadlock, for example)..
238    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/// `Context` is a structure that's designed to give us a high-level interface to
250/// the Valkey module API by abstracting away the raw C FFI calls.
251#[derive(Debug)]
252pub struct Context {
253    pub ctx: *mut raw::RedisModuleCtx,
254}
255
256/// A guerd that protected a user that has
257/// been set on a context using `autenticate_user`.
258/// This guerd make sure to unset the user when freed.
259/// It prevent privilege escalation security issues
260/// that can happened by forgeting to unset the user.
261#[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 is used to make sure the object will not live longer than actual arguments slice
284    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    /// # Panics
368    ///
369    /// Will panic if `RedisModule_AutoMemory` is missing in redismodule.h
370    pub fn auto_memory(&self) {
371        unsafe {
372            raw::RedisModule_AutoMemory.unwrap()(self.ctx);
373        }
374    }
375
376    /// # Panics
377    ///
378    /// Will panic if `RedisModule_IsKeysPositionRequest` is missing in redismodule.h
379    #[must_use]
380    pub fn is_keys_position_request(&self) -> bool {
381        // We want this to be available in tests where we don't have an actual Valkey to call
382        if cfg!(test) {
383            return false;
384        }
385
386        (unsafe { raw::RedisModule_IsKeysPositionRequest.unwrap()(self.ctx) }) != 0
387    }
388
389    /// # Panics
390    ///
391    /// Will panic if `RedisModule_KeyAtPos` is missing in redismodule.h
392    pub fn key_at_pos(&self, pos: i32) {
393        // TODO: This will crash valkey if `pos` is out of range.
394        // Think of a way to make this safe by checking the range.
395        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    /// Invoke a command on Valkey and return the result
435    /// Unlike 'call' this API also allow to pass a CallOption to control different aspects
436    /// of the command invocation.
437    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    /// Same as [call_ext] but also allow to perform blocking commands like BLPOP.
449    #[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    /// # Panics
528    ///
529    /// Will panic if methods used are missing in redismodule.h
530    #[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                    // We can't return a result since we don't have a client
626                    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    /// Replicate command to the replica and AOF.
671    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    /// # Safety
686    ///
687    /// See [raw::export_shared_api].
688    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    /// # Safety
697    ///
698    /// See [raw::notify_keyspace_event].
699    #[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    /// Returns the valkey version either by calling `RedisModule_GetServerVersion` API,
721    /// Or if it is not available, by calling "info server" API and parsing the reply
722    pub fn get_redis_version(&self) -> Result<Version, ValkeyError> {
723        self.get_redis_version_internal(false)
724    }
725
726    /// Returns the valkey version by calling "info server" API and parsing the reply
727    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                // Call existing API
752                Ok(Version::from(unsafe { api() }))
753            }
754            _ => {
755                // Call "info server"
756                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    /// Return ContextFlags object that allows to check properties related to the state of
769    /// the current Valkey instance such as:
770    /// * Role (master/slave)
771    /// * Loading RDB/AOF
772    /// * Execution mode such as multi exec or Lua
773    pub fn get_flags(&self) -> ContextFlags {
774        ContextFlags::from_bits_truncate(unsafe {
775            raw::RedisModule_GetContextFlags.unwrap()(self.ctx)
776        })
777    }
778
779    /// Return the current user name attached to the context
780    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    /// Attach the given user to the current context so each operation performed from
786    /// now on using this context will be validated againts this new user.
787    /// Return [ContextUserScope] which make sure to unset the user when freed and
788    /// can not outlive the current [Context].
789    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    /// Verify the the given user has the give ACL permission on the given key.
806    /// Return Ok(()) if the user has the permissions or error (with relevant error message)
807    /// if the validation failed.
808    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        /// When running inside a key space notification callback, it is dangerous and highly discouraged to perform any write
835        /// operation. In order to still perform write actions in this scenario, Valkey provides this API ([add_post_notification_job])
836        /// that allows to register a job callback which Valkey will call when the following condition holds:
837        ///
838        /// 1. It is safe to perform any write operation.
839        /// 2. The job will be called atomically along side the key space notification.
840        ///
841        /// Notice, one job might trigger key space notifications that will trigger more jobs.
842        /// This raises a concerns of entering an infinite loops, we consider infinite loops
843        /// as a logical bug that need to be fixed in the module, an attempt to protect against
844        /// infinite loops by halting the execution could result in violation of the feature correctness
845        /// and so Valkey will make no attempt to protect the module from infinite loops.
846        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        /// Returns true if a client sent the CLIENT PAUSE command to the server or
866        /// if Valkey Cluster does a manual failover, pausing the clients.
867        /// This is needed when we have a master with replicas, and want to write,
868        /// without adding further data to the replication channel, that the replicas
869        /// replication offset, match the one of the master. When this happens, it is
870        /// safe to failover the master without data loss.
871        ///
872        /// However modules may generate traffic by calling commands or directly send
873        /// data to the replication stream.
874        ///
875        /// So modules may want to try to avoid very heavy background work that has
876        /// the effect of creating data to the replication channel, when this function
877        /// returns true. This is mostly useful for modules that have background
878        /// garbage collection tasks, or that do writes and replicate such writes
879        /// periodically in timer callbacks or other periodic callbacks.
880        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    /// An object represent ACL permissions.
915    /// Used to check ACL permission using `acl_check_key_permission`.
916    #[derive(Debug)]
917    pub struct AclPermissions : c_int {
918        /// User can look at the content of the value, either return it or copy it.
919        const ACCESS = raw::REDISMODULE_CMD_KEY_ACCESS as c_int;
920
921        /// User can insert more data to the key, without deleting or modify existing data.
922        const INSERT = raw::REDISMODULE_CMD_KEY_INSERT as c_int;
923
924        /// User can delete content from the key.
925        const DELETE = raw::REDISMODULE_CMD_KEY_DELETE as c_int;
926
927        /// User can update existing data inside the key.
928        const UPDATE = raw::REDISMODULE_CMD_KEY_UPDATE as c_int;
929    }
930}
931
932/// The values allowed in the "info" sections and dictionaries.
933#[derive(Debug, Clone)]
934pub enum InfoContextBuilderFieldBottomLevelValue {
935    /// A simple string value.
936    String(String),
937    /// A numeric value ([`i64`]).
938    I64(i64),
939    /// A numeric value ([`u64`]).
940    U64(u64),
941    /// A numeric value ([`f64`]).
942    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    /// A simple bottom-level value.
972    Value(InfoContextBuilderFieldBottomLevelValue),
973    /// A dictionary value.
974    ///
975    /// An example of what it looks like:
976    /// ```no_run,ignore,
977    /// > redis-cli: INFO
978    /// >
979    /// > # <section name>
980    /// <dictionary name>:<key 1>=<value 1>,<key 2>=<value 2>
981    /// ```
982    ///
983    /// Let's suppose we added a section `"my_info"`. Then into this
984    /// section we can add a dictionary. Let's add a dictionary named
985    /// `"module"`, with with fields `"name"` which is equal to
986    /// `"redisgears_2"` and `"ver"` with a value of `999999`. If our
987    /// module is named "redisgears_2", we can call `INFO redisgears_2`
988    /// to obtain this information:
989    ///
990    /// ```no_run,ignore,
991    /// > redis-cli: INFO redisgears_2
992    /// >
993    /// > # redisgears_2_my_info
994    /// module:name=redisgears_2,ver=999999
995    /// ```
996    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/// Builds a dictionary within the [`InfoContext`], similar to
1011/// `INFO KEYSPACE`.
1012#[derive(Debug)]
1013pub struct InfoContextBuilderDictionaryBuilder<'a> {
1014    /// The info section builder this dictionary builder is for.
1015    info_section_builder: InfoContextBuilderSectionBuilder<'a>,
1016    /// The name of the section to build.
1017    name: String,
1018    /// The fields this section contains.
1019    fields: InfoContextFieldBottomLevelData,
1020}
1021
1022impl<'a> InfoContextBuilderDictionaryBuilder<'a> {
1023    /// Adds a field within this section.
1024    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    /// Builds the dictionary with the fields provided.
1041    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/// Builds a section within the [`InfoContext`].
1055#[derive(Debug)]
1056pub struct InfoContextBuilderSectionBuilder<'a> {
1057    /// The info builder this section builder is for.
1058    info_builder: InfoContextBuilder<'a>,
1059    /// The name of the section to build.
1060    name: String,
1061    /// The fields this section contains.
1062    fields: InfoContextFieldTopLevelData,
1063}
1064
1065impl<'a> InfoContextBuilderSectionBuilder<'a> {
1066    /// Adds a field within this section.
1067    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    /// Adds a new dictionary.
1083    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    /// Builds the section with the fields provided.
1092    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/// A single info context's bottom level field data.
1114#[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/// A type for the `key => bottom-level-value` storage of an info
1138/// section.
1139#[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
1155/// A type alias for the `key => top-level-value` storage of an info
1156/// section.
1157pub type InfoContextFieldTopLevelData = Vec<(String, InfoContextBuilderFieldTopLevelValue)>;
1158/// One section contents: name and children.
1159pub type OneInfoSectionData = (String, InfoContextFieldTopLevelData);
1160/// A type alias for the section data, associated with the info section.
1161pub 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    /// Adds fields. Make sure that the corresponding section/dictionary
1213    /// have been added before calling this method.
1214    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                    // This section wasn't requested.
1240                    Ok(())
1241                }
1242            })
1243    }
1244
1245    /// Sends the info accumulated so far to the [`InfoContext`].
1246    pub fn build_info(self) -> ValkeyResult<&'a InfoContext> {
1247        self.finalise_data().map(|_| self.context)
1248    }
1249
1250    /// Returns a section builder.
1251    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    /// Adds the section data without checks for the values already
1260    /// being present. In this case, the values will be overwritten.
1261    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    /// Returns a builder for the [`InfoContext`].
1287    pub fn builder(&self) -> InfoContextBuilder<'_> {
1288        InfoContextBuilder::from(self)
1289    }
1290
1291    /// Returns a build result for the passed [`OneInfoSectionData`].
1292    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    /// The `name` of the section will be prefixed with the module name
1301    /// and an underscore: `<module name>_<name>`.
1302    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    /// The `name` will be prefixed with the module name and an
1308    /// underscore: `<module name>_<name>`. The `content` pass is left
1309    /// "as is".
1310    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    /// The `name` will be prefixed with the module name and an
1316    /// underscore: `<module name>_<name>`. The `value` pass is left
1317    /// "as is".
1318    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        /// The command is running in the context of a Lua script
1326        const LUA = raw::REDISMODULE_CTX_FLAGS_LUA as c_int;
1327
1328        /// The command is running inside a Valkey transaction
1329        const MULTI = raw::REDISMODULE_CTX_FLAGS_MULTI as c_int;
1330
1331        /// The instance is a master
1332        const MASTER = raw::REDISMODULE_CTX_FLAGS_MASTER as c_int;
1333
1334        /// The instance is a SLAVE
1335        const SLAVE = raw::REDISMODULE_CTX_FLAGS_SLAVE as c_int;
1336
1337        /// The instance is read-only (usually meaning it's a slave as well)
1338        const READONLY = raw::REDISMODULE_CTX_FLAGS_READONLY as c_int;
1339
1340        /// The instance is running in cluster mode
1341        const CLUSTER = raw::REDISMODULE_CTX_FLAGS_CLUSTER as c_int;
1342
1343        /// The instance has AOF enabled
1344        const AOF = raw::REDISMODULE_CTX_FLAGS_AOF as c_int;
1345
1346        /// The instance has RDB enabled
1347        const RDB = raw::REDISMODULE_CTX_FLAGS_RDB as c_int;
1348
1349        /// The instance has Maxmemory set
1350        const MAXMEMORY = raw::REDISMODULE_CTX_FLAGS_MAXMEMORY as c_int;
1351
1352        /// Maxmemory is set and has an eviction policy that may delete keys
1353        const EVICTED = raw::REDISMODULE_CTX_FLAGS_EVICT as c_int;
1354
1355        /// Valkey is out of memory according to the maxmemory flag.
1356        const OOM = raw::REDISMODULE_CTX_FLAGS_OOM as c_int;
1357
1358        /// Less than 25% of memory available according to maxmemory.
1359        const OOM_WARNING = raw::REDISMODULE_CTX_FLAGS_OOM_WARNING as c_int;
1360
1361        /// The command was sent over the replication link.
1362        const REPLICATED = raw::REDISMODULE_CTX_FLAGS_REPLICATED as c_int;
1363
1364        /// Valkey is currently loading either from AOF or RDB.
1365        const LOADING = raw::REDISMODULE_CTX_FLAGS_LOADING as c_int;
1366
1367        /// The replica has no link with its master
1368        const REPLICA_IS_STALE = raw::REDISMODULE_CTX_FLAGS_REPLICA_IS_STALE as c_int;
1369
1370        /// The replica is trying to connect with the master
1371        const REPLICA_IS_CONNECTING = raw::REDISMODULE_CTX_FLAGS_REPLICA_IS_CONNECTING as c_int;
1372
1373        /// The replica is receiving an RDB file from its master.
1374        const REPLICA_IS_TRANSFERRING = raw::REDISMODULE_CTX_FLAGS_REPLICA_IS_TRANSFERRING as c_int;
1375
1376        /// The replica is online, receiving updates from its master
1377        const REPLICA_IS_ONLINE = raw::REDISMODULE_CTX_FLAGS_REPLICA_IS_ONLINE as c_int;
1378
1379        /// There is currently some background process active.
1380        const ACTIVE_CHILD = raw::REDISMODULE_CTX_FLAGS_ACTIVE_CHILD as c_int;
1381
1382        /// Valkey is currently running inside background child process.
1383        const IS_CHILD = raw::REDISMODULE_CTX_FLAGS_IS_CHILD as c_int;
1384
1385        /// The next EXEC will fail due to dirty CAS (touched keys).
1386        const MULTI_DIRTY = raw::REDISMODULE_CTX_FLAGS_MULTI_DIRTY as c_int;
1387
1388        /// The current client does not allow blocking, either called from
1389        /// within multi, lua, or from another module using RM_Call
1390        const DENY_BLOCKING = raw::REDISMODULE_CTX_FLAGS_DENY_BLOCKING as c_int;
1391
1392        /// The current client uses RESP3 protocol
1393        const FLAGS_RESP3 = raw::REDISMODULE_CTX_FLAGS_RESP3 as c_int;
1394
1395        /// Valkey is currently async loading database for diskless replication.
1396        const ASYNC_LOADING = raw::REDISMODULE_CTX_FLAGS_ASYNC_LOADING as c_int;
1397    }
1398}