Skip to main content

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;
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    /// Enable this option will not allow RM_Call to perform write commands
86    pub fn no_writes(mut self) -> CallOptionsBuilder {
87        self.add_flag("W");
88        self
89    }
90
91    /// Enable this option will run RM_Call is script mode.
92    /// This mean that Valkey will enable the following protections:
93    /// 1. Not allow running dangerous commands like 'shutdown'
94    /// 2. Not allow running write commands on OOM or if there are not enough good replica's connected
95    pub fn script_mode(mut self) -> CallOptionsBuilder {
96        self.add_flag("S");
97        self
98    }
99
100    /// Enable this option will perform ACL validation on the user attached to the context that
101    /// is used to invoke the call.
102    pub fn verify_acl(mut self) -> CallOptionsBuilder {
103        self.add_flag("C");
104        self
105    }
106
107    /// Enable this option will OOM validation before running the command
108    pub fn verify_oom(mut self) -> CallOptionsBuilder {
109        self.add_flag("M");
110        self
111    }
112
113    /// Enable this option will return error as CallReply object instead of setting errno (it is
114    /// usually recommend to enable it)
115    pub fn errors_as_replies(mut self) -> CallOptionsBuilder {
116        self.add_flag("E");
117        self
118    }
119
120    /// Enable this option will cause the command to be replicated to the replica and AOF
121    pub fn replicate(mut self) -> CallOptionsBuilder {
122        self.add_flag("!");
123        self
124    }
125
126    /// Allow control the protocol version in which the replies will be returned.
127    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    /// Construct a CallOption object that can be used to run commands using call_ext
137    pub fn build(self) -> CallOptions {
138        CallOptions {
139            options: CString::new(self.options).unwrap(), // the data will never contains internal \0 so it is safe to unwrap.
140        }
141    }
142
143    /// Construct a CallOption object that can be used to run commands using call_blocking.
144    /// The commands can be either blocking or none blocking. In case the command are blocking
145    /// (like `blpop`) a [FutureCallReply] will be returned.
146    #[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(), // the data will never contains internal \0 so it is safe to unwrap.
154        }
155    }
156}
157
158/// This struct allows logging when the Valkey GIL is not acquired.
159/// It is implemented `Send` and `Sync` so it can safely be used
160/// from within different threads.
161pub 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
179/// This object is returned after locking Valkey from [DetachedContext].
180/// On dispose, Valkey will be unlocked.
181/// This object implements [Deref] for [Context] so it can be used
182/// just like any Valkey [Context] for command invocation.
183/// **This object should not be used to return replies** because there is
184/// no real client behind this context to return replies to.
185pub 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    /// Lock Valkey for command invocation. Returns [DetachedContextGuard] which will unlock Valkey when dispose.
240    /// [DetachedContextGuard] implements [Deref<Target = Context>] so it can be used just like any Valkey [Context] for command invocation.
241    /// Locking Valkey when Valkey is already locked by the current thread is left unspecified.
242    /// However, this function will not return on the second call (it might panic or deadlock, for example)..
243    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/// `Context` is a structure that's designed to give us a high-level interface to
255/// the Valkey module API by abstracting away the raw C FFI calls.
256#[derive(Debug)]
257pub struct Context {
258    pub ctx: *mut raw::RedisModuleCtx,
259}
260
261/// A guerd that protected a user that has
262/// been set on a context using `autenticate_user`.
263/// This guerd make sure to unset the user when freed.
264/// It prevent privilege escalation security issues
265/// that can happened by forgeting to unset the user.
266#[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 is used to make sure the object will not live longer than actual arguments slice
289    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    /// # Panics
373    ///
374    /// Will panic if `RedisModule_AutoMemory` is missing in redismodule.h
375    pub fn auto_memory(&self) {
376        unsafe {
377            raw::RedisModule_AutoMemory.unwrap()(self.ctx);
378        }
379    }
380
381    /// # Panics
382    ///
383    /// Will panic if `RedisModule_IsKeysPositionRequest` is missing in redismodule.h
384    #[must_use]
385    pub fn is_keys_position_request(&self) -> bool {
386        // We want this to be available in tests where we don't have an actual Valkey to call
387        if cfg!(test) {
388            return false;
389        }
390
391        (unsafe { raw::RedisModule_IsKeysPositionRequest.unwrap()(self.ctx) }) != 0
392    }
393
394    /// # Panics
395    ///
396    /// Will panic if `RedisModule_KeyAtPos` is missing in redismodule.h
397    pub fn key_at_pos(&self, pos: i32) {
398        // TODO: This will crash valkey if `pos` is out of range.
399        // Think of a way to make this safe by checking the range.
400        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        // Test contexts return configured replies here because stable Rust cannot implement the
421        // C-variadic RedisModule_Call API for the test shim.
422        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    /// Invoke a command on Valkey and return the result
448    /// Unlike 'call' this API also allow to pass a CallOption to control different aspects
449    /// of the command invocation.
450    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    /// Same as [call_ext] but also allow to perform blocking commands like BLPOP.
462    #[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    /// # Panics
541    ///
542    /// Will panic if methods used are missing in redismodule.h
543    #[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                    // We can't return a result since we don't have a client
639                    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    /// Replicate command to the replica and AOF.
684    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    /// # Safety
699    ///
700    /// See [raw::export_shared_api].
701    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    /// # Safety
710    ///
711    /// See [raw::notify_keyspace_event].
712    #[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    /// Returns the valkey version either by calling `RedisModule_GetServerVersion` API,
734    /// Or if it is not available, by calling "info server" API and parsing the reply
735    pub fn get_server_version(&self) -> Result<Version, ValkeyError> {
736        self.get_server_version_internal(false)
737    }
738
739    /// Returns the valkey version by calling "info server" API and parsing the reply
740    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                // Call existing API
765                Ok(Version::from(unsafe { api() }))
766            }
767            _ => {
768                // Call "info server"
769                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    /// Return ContextFlags object that allows to check properties related to the state of
782    /// the current Valkey instance such as:
783    /// * Role (master/slave)
784    /// * Loading RDB/AOF
785    /// * Execution mode such as multi exec or Lua
786    pub fn get_flags(&self) -> ContextFlags {
787        ContextFlags::from_bits_truncate(unsafe {
788            raw::RedisModule_GetContextFlags.unwrap()(self.ctx)
789        })
790    }
791
792    /// Return the current user name attached to the context
793    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    /// Attach the given user to the current context so each operation performed from
799    /// now on using this context will be validated againts this new user.
800    /// Return [ContextUserScope] which make sure to unset the user when freed and
801    /// can not outlive the current [Context].
802    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    /// Verify the the given user has the give ACL permission on the given key.
819    /// Return Ok(()) if the user has the permissions or error (with relevant error message)
820    /// if the validation failed.
821    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        /// When running inside a key space notification callback, it is dangerous and highly discouraged to perform any write
848        /// operation. In order to still perform write actions in this scenario, Valkey provides this API ([add_post_notification_job])
849        /// that allows to register a job callback which Valkey will call when the following condition holds:
850        ///
851        /// 1. It is safe to perform any write operation.
852        /// 2. The job will be called atomically along side the key space notification.
853        ///
854        /// Notice, one job might trigger key space notifications that will trigger more jobs.
855        /// This raises a concerns of entering an infinite loops, we consider infinite loops
856        /// as a logical bug that need to be fixed in the module, an attempt to protect against
857        /// infinite loops by halting the execution could result in violation of the feature correctness
858        /// and so Valkey will make no attempt to protect the module from infinite loops.
859        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        /// Returns true if a client sent the CLIENT PAUSE command to the server or
879        /// if Valkey Cluster does a manual failover, pausing the clients.
880        /// This is needed when we have a master with replicas, and want to write,
881        /// without adding further data to the replication channel, that the replicas
882        /// replication offset, match the one of the master. When this happens, it is
883        /// safe to failover the master without data loss.
884        ///
885        /// However modules may generate traffic by calling commands or directly send
886        /// data to the replication stream.
887        ///
888        /// So modules may want to try to avoid very heavy background work that has
889        /// the effect of creating data to the replication channel, when this function
890        /// returns true. This is mostly useful for modules that have background
891        /// garbage collection tasks, or that do writes and replicate such writes
892        /// periodically in timer callbacks or other periodic callbacks.
893        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    /// An object represent ACL permissions.
928    /// Used to check ACL permission using `acl_check_key_permission`.
929    #[derive(Debug)]
930    pub struct AclPermissions : c_int {
931        /// User can look at the content of the value, either return it or copy it.
932        const ACCESS = raw::REDISMODULE_CMD_KEY_ACCESS as c_int;
933
934        /// User can insert more data to the key, without deleting or modify existing data.
935        const INSERT = raw::REDISMODULE_CMD_KEY_INSERT as c_int;
936
937        /// User can delete content from the key.
938        const DELETE = raw::REDISMODULE_CMD_KEY_DELETE as c_int;
939
940        /// User can update existing data inside the key.
941        const UPDATE = raw::REDISMODULE_CMD_KEY_UPDATE as c_int;
942    }
943}
944
945/// The values allowed in the "info" sections and dictionaries.
946#[derive(Debug, Clone)]
947pub enum InfoContextBuilderFieldBottomLevelValue {
948    /// A simple string value.
949    String(String),
950    /// A numeric value ([`i64`]).
951    I64(i64),
952    /// A numeric value ([`u64`]).
953    U64(u64),
954    /// A numeric value ([`f64`]).
955    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    /// A simple bottom-level value.
985    Value(InfoContextBuilderFieldBottomLevelValue),
986    /// A dictionary value.
987    ///
988    /// An example of what it looks like:
989    /// ```no_run,ignore,
990    /// > redis-cli: INFO
991    /// >
992    /// > # <section name>
993    /// <dictionary name>:<key 1>=<value 1>,<key 2>=<value 2>
994    /// ```
995    ///
996    /// Let's suppose we added a section `"my_info"`. Then into this
997    /// section we can add a dictionary. Let's add a dictionary named
998    /// `"module"`, with with fields `"name"` which is equal to
999    /// `"redisgears_2"` and `"ver"` with a value of `999999`. If our
1000    /// module is named "redisgears_2", we can call `INFO redisgears_2`
1001    /// to obtain this information:
1002    ///
1003    /// ```no_run,ignore,
1004    /// > redis-cli: INFO redisgears_2
1005    /// >
1006    /// > # redisgears_2_my_info
1007    /// module:name=redisgears_2,ver=999999
1008    /// ```
1009    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/// Builds a dictionary within the [`InfoContext`], similar to
1024/// `INFO KEYSPACE`.
1025#[derive(Debug)]
1026pub struct InfoContextBuilderDictionaryBuilder<'a> {
1027    /// The info section builder this dictionary builder is for.
1028    info_section_builder: InfoContextBuilderSectionBuilder<'a>,
1029    /// The name of the section to build.
1030    name: String,
1031    /// The fields this section contains.
1032    fields: InfoContextFieldBottomLevelData,
1033}
1034
1035impl<'a> InfoContextBuilderDictionaryBuilder<'a> {
1036    /// Adds a field within this section.
1037    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    /// Builds the dictionary with the fields provided.
1054    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/// Builds a section within the [`InfoContext`].
1068#[derive(Debug)]
1069pub struct InfoContextBuilderSectionBuilder<'a> {
1070    /// The info builder this section builder is for.
1071    info_builder: InfoContextBuilder<'a>,
1072    /// The name of the section to build.
1073    name: String,
1074    /// The fields this section contains.
1075    fields: InfoContextFieldTopLevelData,
1076}
1077
1078impl<'a> InfoContextBuilderSectionBuilder<'a> {
1079    /// Adds a field within this section.
1080    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    /// Adds a new dictionary.
1096    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    /// Builds the section with the fields provided.
1105    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/// A single info context's bottom level field data.
1127#[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/// A type for the `key => bottom-level-value` storage of an info
1151/// section.
1152#[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
1168/// A type alias for the `key => top-level-value` storage of an info
1169/// section.
1170pub type InfoContextFieldTopLevelData = Vec<(String, InfoContextBuilderFieldTopLevelValue)>;
1171/// One section contents: name and children.
1172pub type OneInfoSectionData = (String, InfoContextFieldTopLevelData);
1173/// A type alias for the section data, associated with the info section.
1174pub 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    /// Adds fields. Make sure that the corresponding section/dictionary
1226    /// have been added before calling this method.
1227    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                    // This section wasn't requested.
1253                    Ok(())
1254                }
1255            })
1256    }
1257
1258    /// Sends the info accumulated so far to the [`InfoContext`].
1259    pub fn build_info(self) -> ValkeyResult<&'a InfoContext> {
1260        self.finalise_data().map(|_| self.context)
1261    }
1262
1263    /// Returns a section builder.
1264    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    /// Adds the section data without checks for the values already
1273    /// being present. In this case, the values will be overwritten.
1274    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    /// Returns a builder for the [`InfoContext`].
1300    pub fn builder(&self) -> InfoContextBuilder<'_> {
1301        InfoContextBuilder::from(self)
1302    }
1303
1304    /// Returns a build result for the passed [`OneInfoSectionData`].
1305    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    /// The `name` of the section will be prefixed with the module name
1314    /// and an underscore: `<module name>_<name>`.
1315    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    /// The `name` will be prefixed with the module name and an
1321    /// underscore: `<module name>_<name>`. The `content` pass is left
1322    /// "as is".
1323    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    /// The `name` will be prefixed with the module name and an
1329    /// underscore: `<module name>_<name>`. The `value` pass is left
1330    /// "as is".
1331    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        /// The command is running in the context of a Lua script
1339        const LUA = raw::REDISMODULE_CTX_FLAGS_LUA as c_int;
1340
1341        /// The command is running inside a Valkey transaction
1342        const MULTI = raw::REDISMODULE_CTX_FLAGS_MULTI as c_int;
1343
1344        /// The instance is a master
1345        const MASTER = raw::REDISMODULE_CTX_FLAGS_MASTER as c_int;
1346
1347        /// The instance is a SLAVE
1348        const SLAVE = raw::REDISMODULE_CTX_FLAGS_SLAVE as c_int;
1349
1350        /// The instance is read-only (usually meaning it's a slave as well)
1351        const READONLY = raw::REDISMODULE_CTX_FLAGS_READONLY as c_int;
1352
1353        /// The instance is running in cluster mode
1354        const CLUSTER = raw::REDISMODULE_CTX_FLAGS_CLUSTER as c_int;
1355
1356        /// The instance has AOF enabled
1357        const AOF = raw::REDISMODULE_CTX_FLAGS_AOF as c_int;
1358
1359        /// The instance has RDB enabled
1360        const RDB = raw::REDISMODULE_CTX_FLAGS_RDB as c_int;
1361
1362        /// The instance has Maxmemory set
1363        const MAXMEMORY = raw::REDISMODULE_CTX_FLAGS_MAXMEMORY as c_int;
1364
1365        /// Maxmemory is set and has an eviction policy that may delete keys
1366        const EVICTED = raw::REDISMODULE_CTX_FLAGS_EVICT as c_int;
1367
1368        /// Valkey is out of memory according to the maxmemory flag.
1369        const OOM = raw::REDISMODULE_CTX_FLAGS_OOM as c_int;
1370
1371        /// Less than 25% of memory available according to maxmemory.
1372        const OOM_WARNING = raw::REDISMODULE_CTX_FLAGS_OOM_WARNING as c_int;
1373
1374        /// The command was sent over the replication link.
1375        const REPLICATED = raw::REDISMODULE_CTX_FLAGS_REPLICATED as c_int;
1376
1377        /// Valkey is currently loading either from AOF or RDB.
1378        const LOADING = raw::REDISMODULE_CTX_FLAGS_LOADING as c_int;
1379
1380        /// The replica has no link with its master
1381        const REPLICA_IS_STALE = raw::REDISMODULE_CTX_FLAGS_REPLICA_IS_STALE as c_int;
1382
1383        /// The replica is trying to connect with the master
1384        const REPLICA_IS_CONNECTING = raw::REDISMODULE_CTX_FLAGS_REPLICA_IS_CONNECTING as c_int;
1385
1386        /// The replica is receiving an RDB file from its master.
1387        const REPLICA_IS_TRANSFERRING = raw::REDISMODULE_CTX_FLAGS_REPLICA_IS_TRANSFERRING as c_int;
1388
1389        /// The replica is online, receiving updates from its master
1390        const REPLICA_IS_ONLINE = raw::REDISMODULE_CTX_FLAGS_REPLICA_IS_ONLINE as c_int;
1391
1392        /// There is currently some background process active.
1393        const ACTIVE_CHILD = raw::REDISMODULE_CTX_FLAGS_ACTIVE_CHILD as c_int;
1394
1395        /// Valkey is currently running inside background child process.
1396        const IS_CHILD = raw::REDISMODULE_CTX_FLAGS_IS_CHILD as c_int;
1397
1398        /// The next EXEC will fail due to dirty CAS (touched keys).
1399        const MULTI_DIRTY = raw::REDISMODULE_CTX_FLAGS_MULTI_DIRTY as c_int;
1400
1401        /// The current client does not allow blocking, either called from
1402        /// within multi, lua, or from another module using RM_Call
1403        const DENY_BLOCKING = raw::REDISMODULE_CTX_FLAGS_DENY_BLOCKING as c_int;
1404
1405        /// The current client uses RESP3 protocol
1406        const FLAGS_RESP3 = raw::REDISMODULE_CTX_FLAGS_RESP3 as c_int;
1407
1408        /// Valkey is currently async loading database for diskless replication.
1409        const ASYNC_LOADING = raw::REDISMODULE_CTX_FLAGS_ASYNC_LOADING as c_int;
1410    }
1411}