valkey_module/context/
filter.rs

1use crate::{
2    Context, RedisModuleCommandFilter, RedisModuleCommandFilterCtx, RedisModuleString,
3    RedisModule_CommandFilterArgDelete, RedisModule_CommandFilterArgGet,
4    RedisModule_CommandFilterArgInsert, RedisModule_CommandFilterArgReplace,
5    RedisModule_CommandFilterArgsCount, RedisModule_CommandFilterGetClientId,
6    RedisModule_RegisterCommandFilter, RedisModule_UnregisterCommandFilter, ValkeyString,
7};
8use std::ffi::c_int;
9use std::str::Utf8Error;
10
11#[derive(Debug, Clone, Copy)]
12pub struct CommandFilter {
13    inner: *mut RedisModuleCommandFilter,
14}
15
16// otherwise you get error:     cannot be shared between threads safely
17unsafe impl Send for CommandFilter {}
18unsafe impl Sync for CommandFilter {}
19
20/// CommandFilter is a wrapper around the RedisModuleCommandFilter pointer
21///
22/// It provides a way to check if the filter is null and to create a new CommandFilter
23impl CommandFilter {
24    pub fn new(inner: *mut RedisModuleCommandFilter) -> Self {
25        CommandFilter { inner }
26    }
27
28    pub fn is_null(&self) -> bool {
29        self.inner.is_null()
30    }
31}
32
33pub struct CommandFilterCtx {
34    inner: *mut RedisModuleCommandFilterCtx,
35}
36
37/// wrapping the RedisModuleCommandFilterCtx to provide a higher level interface to call RedisModule_CommandFilter*
38///
39/// provides functions to interact with the command filter context, such as getting the number of arguments, getting and replacing arguments, and deleting arguments.
40impl CommandFilterCtx {
41    pub fn new(inner: *mut RedisModuleCommandFilterCtx) -> Self {
42        CommandFilterCtx { inner }
43    }
44
45    /// wrapper for RedisModule_CommandFilterArgsCount
46    pub fn args_count(&self) -> c_int {
47        unsafe { RedisModule_CommandFilterArgsCount.unwrap()(self.inner) }
48    }
49
50    /// wrapper for RedisModule_CommandFilterArgGet
51    pub fn arg_get(&self, pos: c_int) -> *mut RedisModuleString {
52        unsafe { RedisModule_CommandFilterArgGet.unwrap()(self.inner, pos) }
53    }
54
55    /// wrapper to get argument as a Result<&str, Utf8Error> instead of RedisModuleString
56    pub fn arg_get_try_as_str<'a>(&self, pos: c_int) -> Result<&'a str, Utf8Error> {
57        let arg = self.arg_get(pos);
58        ValkeyString::from_ptr(arg)
59    }
60
61    /// wrapper to get 0 argument, the command which is always present and return as &str
62    pub fn cmd_get_try_as_str<'a>(&self) -> Result<&'a str, Utf8Error> {
63        let cmd = self.arg_get(0);
64        ValkeyString::from_ptr(cmd)
65    }
66
67    /// wrapper to get Vector of all args minus the command (0th arg)
68    pub fn get_all_args_wo_cmd(&self) -> Vec<&str> {
69        let mut output = Vec::new();
70        for pos in 1..self.args_count() {
71            match self.arg_get_try_as_str(pos) {
72                Ok(arg) => output.push(arg),
73                Err(_) => continue, // skip invalid args
74            }
75        }
76        output
77    }
78
79    /// wrapper for RedisModule_CommandFilterArgReplace, accepts simple &str and casts it to *mut RedisModuleString
80    pub fn arg_replace(&self, pos: c_int, arg: &str) {
81        unsafe {
82            RedisModule_CommandFilterArgReplace.unwrap()(
83                self.inner,
84                pos,
85                ValkeyString::create_and_retain(arg).inner,
86            )
87        };
88    }
89
90    /// wrapper for RedisModule_CommandFilterArgInsert, accepts simple &str and casts it to *mut RedisModuleString
91    pub fn arg_insert(&self, pos: c_int, arg: &str) {
92        unsafe {
93            RedisModule_CommandFilterArgInsert.unwrap()(
94                self.inner,
95                pos,
96                ValkeyString::create_and_retain(arg).inner,
97            )
98        };
99    }
100
101    /// wrapper for RedisModule_CommandFilterArgDelete
102    pub fn arg_delete(&self, pos: c_int) {
103        unsafe { RedisModule_CommandFilterArgDelete.unwrap()(self.inner, pos) };
104    }
105
106    /// wrapper for RedisModule_CommandFilterGetClientId, not supported in Redis 7.0
107    #[cfg(all(any(
108        feature = "min-redis-compatibility-version-7-2",
109        feature = "min-valkey-compatibility-version-8-0"
110    ),))]
111    pub fn get_client_id(&self) -> u64 {
112        unsafe { RedisModule_CommandFilterGetClientId.unwrap()(self.inner) }
113    }
114}
115
116/// adding functions to the Context struct to provide a higher level interface to register and unregister filters
117impl Context {
118    /// wrapper for RedisModule_RegisterCommandFilter to directly register a filter, likely in init
119    pub fn register_command_filter(
120        &self,
121        module_cmd_filter_func: extern "C" fn(*mut RedisModuleCommandFilterCtx),
122        flags: u32,
123    ) -> CommandFilter {
124        let module_cmd_filter = unsafe {
125            RedisModule_RegisterCommandFilter.unwrap()(
126                self.ctx,
127                Some(module_cmd_filter_func),
128                flags as c_int,
129            )
130        };
131        CommandFilter::new(module_cmd_filter)
132    }
133
134    /// wrapper for RedisModule_UnregisterCommandFilter to directly unregister filter, likely in deinit
135    pub fn unregister_command_filter(&self, cmd_filter: &CommandFilter) {
136        unsafe {
137            RedisModule_UnregisterCommandFilter.unwrap()(self.ctx, cmd_filter.inner);
138        }
139    }
140}