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