1use crate::context::thread_safe::{RedisGILGuard, RedisLockIndicator};
2use crate::{raw, CallOptionResp, CallOptionsBuilder, CallResult, RedisValue};
3use crate::{Context, RedisError, RedisString};
4use bitflags::bitflags;
5use std::ffi::{CStr, CString};
6use std::marker::PhantomData;
7use std::os::raw::{c_char, c_int, c_longlong, c_void};
8use std::sync::atomic::{AtomicBool, AtomicI64, Ordering};
9use std::sync::Mutex;
10
11bitflags! {
12 pub struct ConfigurationFlags : u32 {
14 const DEFAULT = raw::REDISMODULE_CONFIG_DEFAULT;
16
17 const IMMUTABLE = raw::REDISMODULE_CONFIG_IMMUTABLE;
19
20 const SENSITIVE = raw::REDISMODULE_CONFIG_SENSITIVE;
22
23 const HIDDEN = raw::REDISMODULE_CONFIG_HIDDEN;
25
26 const PROTECTED = raw::REDISMODULE_CONFIG_PROTECTED;
28
29 const DENY_LOADING = raw::REDISMODULE_CONFIG_DENY_LOADING;
31
32 const MEMORY = raw::REDISMODULE_CONFIG_MEMORY;
34
35 const BITFLAGS = raw::REDISMODULE_CONFIG_BITFLAGS;
37
38 const UNPREFIXED = raw::REDISMODULE_CONFIG_UNPREFIXED;
40 }
41}
42
43#[macro_export]
44macro_rules! enum_configuration {
45 ($(#[$meta:meta])* $vis:vis enum $name:ident {
46 $($(#[$vmeta:meta])* $vname:ident = $val:expr,)*
47 }) => {
48 use $crate::configuration::EnumConfigurationValue;
49 $(#[$meta])*
50 $vis enum $name {
51 $($(#[$vmeta])* $vname = $val,)*
52 }
53
54 impl std::convert::TryFrom<i32> for $name {
55 type Error = $crate::RedisError;
56
57 fn try_from(v: i32) -> Result<Self, Self::Error> {
58 match v {
59 $(x if x == $name::$vname as i32 => Ok($name::$vname),)*
60 _ => Err($crate::RedisError::Str("Value is not supported")),
61 }
62 }
63 }
64
65 impl std::convert::From<$name> for i32 {
66 fn from(val: $name) -> Self {
67 val as i32
68 }
69 }
70
71 impl EnumConfigurationValue for $name {
72 fn get_options(&self) -> (Vec<String>, Vec<i32>) {
73 (vec![$(stringify!($vname).to_string(),)*], vec![$($val,)*])
74 }
75 }
76
77 impl Clone for $name {
78 fn clone(&self) -> Self {
79 match self {
80 $($name::$vname => $name::$vname,)*
81 }
82 }
83 }
84 }
85}
86
87pub struct ConfigurationContext {
91 _dummy: usize, }
93
94impl ConfigurationContext {
95 fn new() -> ConfigurationContext {
96 ConfigurationContext { _dummy: 0 }
97 }
98}
99
100unsafe impl RedisLockIndicator for ConfigurationContext {}
101
102pub trait ConfigurationValue<T>: Sync + Send {
103 fn get(&self, ctx: &ConfigurationContext) -> T;
104 fn set(&self, ctx: &ConfigurationContext, val: T) -> Result<(), RedisError>;
105}
106
107pub trait EnumConfigurationValue: TryFrom<i32, Error = RedisError> + Into<i32> + Clone {
108 fn get_options(&self) -> (Vec<String>, Vec<i32>);
109}
110
111impl<T: Clone> ConfigurationValue<T> for RedisGILGuard<T> {
112 fn get(&self, ctx: &ConfigurationContext) -> T {
113 let value = self.lock(ctx);
114 value.clone()
115 }
116 fn set(&self, ctx: &ConfigurationContext, val: T) -> Result<(), RedisError> {
117 let mut value = self.lock(ctx);
118 *value = val;
119 Ok(())
120 }
121}
122
123impl<T: Clone + Send> ConfigurationValue<T> for Mutex<T> {
124 fn get(&self, _ctx: &ConfigurationContext) -> T {
125 let value = self.lock().unwrap();
126 value.clone()
127 }
128 fn set(&self, _ctx: &ConfigurationContext, val: T) -> Result<(), RedisError> {
129 let mut value = self.lock().unwrap();
130 *value = val;
131 Ok(())
132 }
133}
134
135impl ConfigurationValue<i64> for AtomicI64 {
136 fn get(&self, _ctx: &ConfigurationContext) -> i64 {
137 self.load(Ordering::Relaxed)
138 }
139 fn set(&self, _ctx: &ConfigurationContext, val: i64) -> Result<(), RedisError> {
140 self.store(val, Ordering::Relaxed);
141 Ok(())
142 }
143}
144
145impl ConfigurationValue<RedisString> for RedisGILGuard<String> {
146 fn get(&self, ctx: &ConfigurationContext) -> RedisString {
147 let value = self.lock(ctx);
148 RedisString::create(None, value.as_str())
149 }
150 fn set(&self, ctx: &ConfigurationContext, val: RedisString) -> Result<(), RedisError> {
151 let mut value = self.lock(ctx);
152 *value = val.try_as_str()?.to_string();
153 Ok(())
154 }
155}
156
157impl ConfigurationValue<RedisString> for Mutex<String> {
158 fn get(&self, _ctx: &ConfigurationContext) -> RedisString {
159 let value = self.lock().unwrap();
160 RedisString::create(None, value.as_str())
161 }
162 fn set(&self, _ctx: &ConfigurationContext, val: RedisString) -> Result<(), RedisError> {
163 let mut value = self.lock().unwrap();
164 *value = val.try_as_str()?.to_string();
165 Ok(())
166 }
167}
168
169impl ConfigurationValue<bool> for AtomicBool {
170 fn get(&self, _ctx: &ConfigurationContext) -> bool {
171 self.load(Ordering::Relaxed)
172 }
173 fn set(&self, _ctx: &ConfigurationContext, val: bool) -> Result<(), RedisError> {
174 self.store(val, Ordering::Relaxed);
175 Ok(())
176 }
177}
178
179type OnUpdatedCallback<T> = Box<dyn Fn(&ConfigurationContext, &str, &'static T)>;
180
181struct ConfigrationPrivateData<G, T: ConfigurationValue<G> + 'static> {
182 variable: &'static T,
183 on_changed: Option<OnUpdatedCallback<T>>,
184 phantom: PhantomData<G>,
185}
186
187impl<G, T: ConfigurationValue<G> + 'static> ConfigrationPrivateData<G, T> {
188 fn set_val(&self, name: *const c_char, val: G, err: *mut *mut raw::RedisModuleString) -> c_int {
189 let configuration_ctx = ConfigurationContext::new();
191 if let Err(e) = self.variable.set(&configuration_ctx, val) {
192 let error_msg = RedisString::create(None, e.to_string().as_str());
193 unsafe { *err = error_msg.take() };
194 return raw::REDISMODULE_ERR as i32;
195 }
196 let c_str_name = unsafe { CStr::from_ptr(name) };
197 if let Some(v) = self.on_changed.as_ref() {
198 v(
199 &configuration_ctx,
200 c_str_name.to_str().unwrap(),
201 self.variable,
202 )
203 }
204 raw::REDISMODULE_OK as i32
205 }
206
207 fn get_val(&self) -> G {
208 self.variable.get(&ConfigurationContext::new())
209 }
210}
211
212extern "C" fn i64_configuration_set<T: ConfigurationValue<i64> + 'static>(
213 name: *const c_char,
214 val: c_longlong,
215 privdata: *mut c_void,
216 err: *mut *mut raw::RedisModuleString,
217) -> c_int {
218 let private_data = unsafe { &*(privdata as *const ConfigrationPrivateData<i64, T>) };
219 private_data.set_val(name, val, err)
220}
221
222extern "C" fn i64_configuration_get<T: ConfigurationValue<i64> + 'static>(
223 _name: *const c_char,
224 privdata: *mut c_void,
225) -> c_longlong {
226 let private_data = unsafe { &*(privdata as *const ConfigrationPrivateData<i64, T>) };
227 private_data.get_val()
228}
229
230#[expect(clippy::too_many_arguments)]
231pub fn register_i64_configuration<T: ConfigurationValue<i64>>(
232 ctx: &Context,
233 name: &str,
234 variable: &'static T,
235 default: i64,
236 min: i64,
237 max: i64,
238 flags: ConfigurationFlags,
239 on_changed: Option<OnUpdatedCallback<T>>,
240) {
241 let name = CString::new(name).unwrap();
242 let config_private_data = ConfigrationPrivateData {
243 variable,
244 on_changed,
245 phantom: PhantomData::<i64>,
246 };
247 unsafe {
248 raw::RedisModule_RegisterNumericConfig.unwrap()(
249 ctx.ctx,
250 name.as_ptr(),
251 default,
252 flags.bits(),
253 min,
254 max,
255 Some(i64_configuration_get::<T>),
256 Some(i64_configuration_set::<T>),
257 None,
258 Box::into_raw(Box::new(config_private_data)) as *mut c_void,
259 );
260 }
261}
262
263fn find_config_value<'a>(args: &'a [RedisString], name: &str) -> Option<&'a RedisString> {
264 args.iter()
265 .skip_while(|item| !item.as_slice().eq(name.as_bytes()))
266 .nth(1)
267}
268
269pub fn get_i64_default_config_value(
270 args: &[RedisString],
271 name: &str,
272 default: i64,
273) -> Result<i64, RedisError> {
274 find_config_value(args, name).map_or(Ok(default), |arg| {
275 arg.try_as_str()?
276 .parse::<i64>()
277 .map_err(|e| RedisError::String(e.to_string()))
278 })
279}
280
281extern "C" fn string_configuration_set<T: ConfigurationValue<RedisString> + 'static>(
282 name: *const c_char,
283 val: *mut raw::RedisModuleString,
284 privdata: *mut c_void,
285 err: *mut *mut raw::RedisModuleString,
286) -> c_int {
287 let new_val = RedisString::new(None, val);
288 let private_data = unsafe { &*(privdata as *const ConfigrationPrivateData<RedisString, T>) };
289 private_data.set_val(name, new_val, err)
290}
291
292extern "C" fn string_configuration_get<T: ConfigurationValue<RedisString> + 'static>(
293 _name: *const c_char,
294 privdata: *mut c_void,
295) -> *mut raw::RedisModuleString {
296 let private_data = unsafe { &*(privdata as *const ConfigrationPrivateData<RedisString, T>) };
297 private_data
299 .variable
300 .get(&ConfigurationContext::new())
301 .take()
302}
303
304pub fn register_string_configuration<T: ConfigurationValue<RedisString>>(
305 ctx: &Context,
306 name: &str,
307 variable: &'static T,
308 default: &str,
309 flags: ConfigurationFlags,
310 on_changed: Option<OnUpdatedCallback<T>>,
311) {
312 let name = CString::new(name).unwrap();
313 let default = CString::new(default).unwrap();
314 let config_private_data = ConfigrationPrivateData {
315 variable,
316 on_changed,
317 phantom: PhantomData::<RedisString>,
318 };
319 unsafe {
320 raw::RedisModule_RegisterStringConfig.unwrap()(
321 ctx.ctx,
322 name.as_ptr(),
323 default.as_ptr(),
324 flags.bits(),
325 Some(string_configuration_get::<T>),
326 Some(string_configuration_set::<T>),
327 None,
328 Box::into_raw(Box::new(config_private_data)) as *mut c_void,
329 );
330 }
331}
332
333pub fn get_string_default_config_value<'a>(
334 args: &'a [RedisString],
335 name: &str,
336 default: &'a str,
337) -> Result<&'a str, RedisError> {
338 find_config_value(args, name).map_or(Ok(default), |arg| arg.try_as_str())
339}
340
341extern "C" fn bool_configuration_set<T: ConfigurationValue<bool> + 'static>(
342 name: *const c_char,
343 val: i32,
344 privdata: *mut c_void,
345 err: *mut *mut raw::RedisModuleString,
346) -> c_int {
347 let private_data = unsafe { &*(privdata as *const ConfigrationPrivateData<bool, T>) };
348 private_data.set_val(name, val != 0, err)
349}
350
351extern "C" fn bool_configuration_get<T: ConfigurationValue<bool> + 'static>(
352 _name: *const c_char,
353 privdata: *mut c_void,
354) -> c_int {
355 let private_data = unsafe { &*(privdata as *const ConfigrationPrivateData<bool, T>) };
356 private_data.get_val() as i32
357}
358
359pub fn register_bool_configuration<T: ConfigurationValue<bool>>(
360 ctx: &Context,
361 name: &str,
362 variable: &'static T,
363 default: bool,
364 flags: ConfigurationFlags,
365 on_changed: Option<OnUpdatedCallback<T>>,
366) {
367 let name = CString::new(name).unwrap();
368 let config_private_data = ConfigrationPrivateData {
369 variable,
370 on_changed,
371 phantom: PhantomData::<bool>,
372 };
373 unsafe {
374 raw::RedisModule_RegisterBoolConfig.unwrap()(
375 ctx.ctx,
376 name.as_ptr(),
377 default as i32,
378 flags.bits(),
379 Some(bool_configuration_get::<T>),
380 Some(bool_configuration_set::<T>),
381 None,
382 Box::into_raw(Box::new(config_private_data)) as *mut c_void,
383 );
384 }
385}
386
387pub fn get_bool_default_config_value(
388 args: &[RedisString],
389 name: &str,
390 default: bool,
391) -> Result<bool, RedisError> {
392 find_config_value(args, name).map_or(Ok(default), |arg| Ok(arg.try_as_str()? == "yes"))
393}
394
395extern "C" fn enum_configuration_set<
396 G: EnumConfigurationValue,
397 T: ConfigurationValue<G> + 'static,
398>(
399 name: *const c_char,
400 val: i32,
401 privdata: *mut c_void,
402 err: *mut *mut raw::RedisModuleString,
403) -> c_int {
404 let private_data = unsafe { &*(privdata as *const ConfigrationPrivateData<G, T>) };
405 let val: Result<G, _> = val.try_into();
406 match val {
407 Ok(val) => private_data.set_val(name, val, err),
408 Err(e) => {
409 let error_msg = RedisString::create(None, e.to_string().as_str());
410 unsafe { *err = error_msg.take() };
411 raw::REDISMODULE_ERR as i32
412 }
413 }
414}
415
416extern "C" fn enum_configuration_get<
417 G: EnumConfigurationValue,
418 T: ConfigurationValue<G> + 'static,
419>(
420 _name: *const c_char,
421 privdata: *mut c_void,
422) -> c_int {
423 let private_data = unsafe { &*(privdata as *const ConfigrationPrivateData<G, T>) };
424 private_data.get_val().into()
425}
426
427pub fn register_enum_configuration<G: EnumConfigurationValue, T: ConfigurationValue<G>>(
428 ctx: &Context,
429 name: &str,
430 variable: &'static T,
431 default: G,
432 flags: ConfigurationFlags,
433 on_changed: Option<OnUpdatedCallback<T>>,
434) {
435 let name = CString::new(name).unwrap();
436 let (names, vals) = default.get_options();
437 assert_eq!(names.len(), vals.len());
438 let names: Vec<CString> = names
439 .into_iter()
440 .map(|v| CString::new(v).unwrap())
441 .collect();
442 let config_private_data = ConfigrationPrivateData {
443 variable,
444 on_changed,
445 phantom: PhantomData::<G>,
446 };
447 unsafe {
448 raw::RedisModule_RegisterEnumConfig.unwrap()(
449 ctx.ctx,
450 name.as_ptr(),
451 default.into(),
452 flags.bits(),
453 names
454 .iter()
455 .map(|v| v.as_ptr())
456 .collect::<Vec<*const c_char>>()
457 .as_mut_ptr(),
458 vals.as_ptr(),
459 names.len() as i32,
460 Some(enum_configuration_get::<G, T>),
461 Some(enum_configuration_set::<G, T>),
462 None,
463 Box::into_raw(Box::new(config_private_data)) as *mut c_void,
464 );
465 }
466}
467
468pub fn get_enum_default_config_value<G: EnumConfigurationValue>(
469 args: &[RedisString],
470 name: &str,
471 default: G,
472) -> Result<G, RedisError> {
473 find_config_value(args, name).map_or(Ok(default.clone()), |arg| {
474 let (names, vals) = default.get_options();
475 let (index, _name) = names
476 .into_iter()
477 .enumerate()
478 .find(|(_index, item)| item.as_bytes().eq(arg.as_slice()))
479 .ok_or(RedisError::String(format!(
480 "Enum '{}' not exists",
481 arg.to_string_lossy()
482 )))?;
483 G::try_from(vals[index])
484 })
485}
486
487pub fn module_config_get(
488 ctx: &Context,
489 args: Vec<RedisString>,
490 name: &str,
491) -> Result<RedisValue, RedisError> {
492 let mut args: Vec<String> = args
493 .into_iter()
494 .skip(1)
495 .map(|e| format!("{}.{}", name, e.to_string_lossy()))
496 .collect();
497 args.insert(0, "get".into());
498 let res: CallResult = ctx.call_ext(
499 "config",
500 &CallOptionsBuilder::new()
501 .errors_as_replies()
502 .resp(CallOptionResp::Auto)
503 .build(),
504 args.iter()
505 .map(|v| v.as_str())
506 .collect::<Vec<&str>>()
507 .as_slice(),
508 );
509 let res = res.map_err(|e| {
510 RedisError::String(
511 e.to_utf8_string()
512 .unwrap_or("Failed converting error to utf8".into()),
513 )
514 })?;
515 Ok((&res).into())
516}
517
518pub fn module_config_set(
519 ctx: &Context,
520 args: Vec<RedisString>,
521 name: &str,
522) -> Result<RedisValue, RedisError> {
523 let mut args: Vec<String> = args
524 .into_iter()
525 .skip(1)
526 .enumerate()
527 .map(|(index, e)| {
528 if index % 2 == 0 {
529 format!("{}.{}", name, e.to_string_lossy())
530 } else {
531 e.to_string_lossy()
532 }
533 })
534 .collect();
535 args.insert(0, "set".into());
536 let res: CallResult = ctx.call_ext(
537 "config",
538 &CallOptionsBuilder::new()
539 .errors_as_replies()
540 .resp(CallOptionResp::Auto)
541 .build(),
542 args.iter()
543 .map(|v| v.as_str())
544 .collect::<Vec<&str>>()
545 .as_slice(),
546 );
547 let res = res.map_err(|e| {
548 RedisError::String(
549 e.to_utf8_string()
550 .unwrap_or("Failed converting error to utf8".into()),
551 )
552 })?;
553 Ok((&res).into())
554}