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