1use crate::context::thread_safe::{ValkeyGILGuard, ValkeyLockIndicator};
2use crate::{raw, CallOptionResp, CallOptionsBuilder, CallResult, ValkeyValue};
3use crate::{Context, ValkeyError, ValkeyString};
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;
10use std::any::TypeId;
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 { &*(privdata as *const ConfigrationPrivateData<ValkeyString, ValkeyGILGuard<ValkeyString>>) };
316 let valkey_gilguard = private_data.variable;
317 let ctx = &ConfigurationContext::new();
318 let value = valkey_gilguard.lock(ctx);
319 value.safe_clone(&Context::dummy()).inner
320 },
321 _ => {
322 let private_data = unsafe { &*(privdata as *const ConfigrationPrivateData<ValkeyString, T>) };
324 private_data
325 .variable
326 .get(&ConfigurationContext::new())
327 .take()
328 }
329 }
330}
331
332pub fn register_string_configuration<T: ConfigurationValue<ValkeyString>>(
333 ctx: &Context,
334 name: &str,
335 variable: &'static T,
336 default: &str,
337 flags: ConfigurationFlags,
338 on_changed: Option<OnUpdatedCallback<T>>,
339 on_set: Option<OnSetCallback<T>>,
340) {
341 let name = CString::new(name).unwrap();
342 let default = CString::new(default).unwrap();
343 let config_private_data = ConfigrationPrivateData {
344 variable,
345 on_changed,
346 on_set,
347 phantom: PhantomData::<ValkeyString>,
348 };
349 unsafe {
350 raw::RedisModule_RegisterStringConfig.unwrap()(
351 ctx.ctx,
352 name.as_ptr(),
353 default.as_ptr(),
354 flags.bits(),
355 Some(string_configuration_get::<T>),
356 Some(string_configuration_set::<T>),
357 None,
358 Box::into_raw(Box::new(config_private_data)) as *mut c_void,
359 );
360 }
361}
362
363pub fn get_string_default_config_value<'a>(
364 args: &'a [ValkeyString],
365 name: &str,
366 default: &'a str,
367) -> Result<&'a str, ValkeyError> {
368 find_config_value(args, name).map_or(Ok(default), |arg| arg.try_as_str())
369}
370
371extern "C" fn bool_configuration_set<T: ConfigurationValue<bool> + 'static>(
372 name: *const c_char,
373 val: i32,
374 privdata: *mut c_void,
375 err: *mut *mut raw::RedisModuleString,
376) -> c_int {
377 let private_data = unsafe { &*(privdata as *const ConfigrationPrivateData<bool, T>) };
378 private_data.set_val(name, val != 0, err)
379}
380
381extern "C" fn bool_configuration_get<T: ConfigurationValue<bool> + 'static>(
382 _name: *const c_char,
383 privdata: *mut c_void,
384) -> c_int {
385 let private_data = unsafe { &*(privdata as *const ConfigrationPrivateData<bool, T>) };
386 private_data.get_val() as i32
387}
388
389pub fn register_bool_configuration<T: ConfigurationValue<bool>>(
390 ctx: &Context,
391 name: &str,
392 variable: &'static T,
393 default: bool,
394 flags: ConfigurationFlags,
395 on_changed: Option<OnUpdatedCallback<T>>,
396 on_set: Option<OnSetCallback<T>>,
397) {
398 let name = CString::new(name).unwrap();
399 let config_private_data = ConfigrationPrivateData {
400 variable,
401 on_changed,
402 on_set,
403 phantom: PhantomData::<bool>,
404 };
405 unsafe {
406 raw::RedisModule_RegisterBoolConfig.unwrap()(
407 ctx.ctx,
408 name.as_ptr(),
409 default as i32,
410 flags.bits(),
411 Some(bool_configuration_get::<T>),
412 Some(bool_configuration_set::<T>),
413 None,
414 Box::into_raw(Box::new(config_private_data)) as *mut c_void,
415 );
416 }
417}
418
419pub fn get_bool_default_config_value(
420 args: &[ValkeyString],
421 name: &str,
422 default: bool,
423) -> Result<bool, ValkeyError> {
424 find_config_value(args, name).map_or(Ok(default), |arg| Ok(arg.try_as_str()? == "yes"))
425}
426
427extern "C" fn enum_configuration_set<
428 G: EnumConfigurationValue,
429 T: ConfigurationValue<G> + 'static,
430>(
431 name: *const c_char,
432 val: i32,
433 privdata: *mut c_void,
434 err: *mut *mut raw::RedisModuleString,
435) -> c_int {
436 let private_data = unsafe { &*(privdata as *const ConfigrationPrivateData<G, T>) };
437 let val: Result<G, _> = val.try_into();
438 match val {
439 Ok(val) => private_data.set_val(name, val, err),
440 Err(e) => {
441 let error_msg = ValkeyString::create(None, e.to_string().as_str());
442 unsafe { *err = error_msg.take() };
443 raw::REDISMODULE_ERR as i32
444 }
445 }
446}
447
448extern "C" fn enum_configuration_get<
449 G: EnumConfigurationValue,
450 T: ConfigurationValue<G> + 'static,
451>(
452 _name: *const c_char,
453 privdata: *mut c_void,
454) -> c_int {
455 let private_data = unsafe { &*(privdata as *const ConfigrationPrivateData<G, T>) };
456 private_data.get_val().into()
457}
458
459pub fn register_enum_configuration<G: EnumConfigurationValue, T: ConfigurationValue<G>>(
460 ctx: &Context,
461 name: &str,
462 variable: &'static T,
463 default: G,
464 flags: ConfigurationFlags,
465 on_changed: Option<OnUpdatedCallback<T>>,
466 on_set: Option<OnSetCallback<T>>,
467) {
468 let name = CString::new(name).unwrap();
469 let (names, vals) = default.get_options();
470 assert_eq!(names.len(), vals.len());
471 let names: Vec<CString> = names
472 .into_iter()
473 .map(|v| CString::new(v).unwrap())
474 .collect();
475 let config_private_data = ConfigrationPrivateData {
476 variable,
477 on_changed,
478 on_set,
479 phantom: PhantomData::<G>,
480 };
481 unsafe {
482 raw::RedisModule_RegisterEnumConfig.unwrap()(
483 ctx.ctx,
484 name.as_ptr(),
485 default.into(),
486 flags.bits(),
487 names
488 .iter()
489 .map(|v| v.as_ptr())
490 .collect::<Vec<*const c_char>>()
491 .as_mut_ptr(),
492 vals.as_ptr(),
493 names.len() as i32,
494 Some(enum_configuration_get::<G, T>),
495 Some(enum_configuration_set::<G, T>),
496 None,
497 Box::into_raw(Box::new(config_private_data)) as *mut c_void,
498 );
499 }
500}
501
502pub fn get_enum_default_config_value<G: EnumConfigurationValue>(
503 args: &[ValkeyString],
504 name: &str,
505 default: G,
506) -> Result<G, ValkeyError> {
507 find_config_value(args, name).map_or(Ok(default.clone()), |arg| {
508 let (names, vals) = default.get_options();
509 let (index, _name) = names
510 .into_iter()
511 .enumerate()
512 .find(|(_index, item)| item.as_bytes().eq(arg.as_slice()))
513 .ok_or(ValkeyError::String(format!(
514 "Enum '{}' not exists",
515 arg.to_string_lossy()
516 )))?;
517 G::try_from(vals[index])
518 })
519}
520
521pub fn module_config_get(
522 ctx: &Context,
523 args: Vec<ValkeyString>,
524 name: &str,
525) -> Result<ValkeyValue, ValkeyError> {
526 let mut args: Vec<String> = args
527 .into_iter()
528 .skip(1)
529 .map(|e| format!("{}.{}", name, e.to_string_lossy()))
530 .collect();
531 args.insert(0, "get".into());
532 let res: CallResult = ctx.call_ext(
533 "config",
534 &CallOptionsBuilder::new()
535 .errors_as_replies()
536 .resp(CallOptionResp::Auto)
537 .build(),
538 args.iter()
539 .map(|v| v.as_str())
540 .collect::<Vec<&str>>()
541 .as_slice(),
542 );
543 let res = res.map_err(|e| {
544 ValkeyError::String(
545 e.to_utf8_string()
546 .unwrap_or("Failed converting error to utf8".into()),
547 )
548 })?;
549 Ok((&res).into())
550}
551
552pub fn module_config_set(
553 ctx: &Context,
554 args: Vec<ValkeyString>,
555 name: &str,
556) -> Result<ValkeyValue, ValkeyError> {
557 let mut args: Vec<String> = args
558 .into_iter()
559 .skip(1)
560 .enumerate()
561 .map(|(index, e)| {
562 if index % 2 == 0 {
563 format!("{}.{}", name, e.to_string_lossy())
564 } else {
565 e.to_string_lossy()
566 }
567 })
568 .collect();
569 args.insert(0, "set".into());
570 let res: CallResult = ctx.call_ext(
571 "config",
572 &CallOptionsBuilder::new()
573 .errors_as_replies()
574 .resp(CallOptionResp::Auto)
575 .build(),
576 args.iter()
577 .map(|v| v.as_str())
578 .collect::<Vec<&str>>()
579 .as_slice(),
580 );
581 let res = res.map_err(|e| {
582 ValkeyError::String(
583 e.to_utf8_string()
584 .unwrap_or("Failed converting error to utf8".into()),
585 )
586 })?;
587 Ok((&res).into())
588}