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}
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::RedisError;
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::RedisError::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 RedisLockIndicator 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<(), RedisError>;
102}
103
104pub trait EnumConfigurationValue: TryFrom<i32, Error = RedisError> + Into<i32> + Clone {
105 fn get_options(&self) -> (Vec<String>, Vec<i32>);
106}
107
108impl<T: Clone> ConfigurationValue<T> for RedisGILGuard<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<(), RedisError> {
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<(), RedisError> {
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<(), RedisError> {
137 self.store(val, Ordering::Relaxed);
138 Ok(())
139 }
140}
141
142impl ConfigurationValue<RedisString> for RedisGILGuard<String> {
143 fn get(&self, ctx: &ConfigurationContext) -> RedisString {
144 let value = self.lock(ctx);
145 RedisString::create(None, value.as_str())
146 }
147 fn set(&self, ctx: &ConfigurationContext, val: RedisString) -> Result<(), RedisError> {
148 let mut value = self.lock(ctx);
149 *value = val.try_as_str()?.to_string();
150 Ok(())
151 }
152}
153
154impl ConfigurationValue<RedisString> for Mutex<String> {
155 fn get(&self, _ctx: &ConfigurationContext) -> RedisString {
156 let value = self.lock().unwrap();
157 RedisString::create(None, value.as_str())
158 }
159 fn set(&self, _ctx: &ConfigurationContext, val: RedisString) -> Result<(), RedisError> {
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<(), RedisError> {
171 self.store(val, Ordering::Relaxed);
172 Ok(())
173 }
174}
175
176type OnUpdatedCallback<T> = Box<dyn Fn(&ConfigurationContext, &str, &'static T)>;
177
178struct ConfigrationPrivateData<G, T: ConfigurationValue<G> + 'static> {
179 variable: &'static T,
180 on_changed: Option<OnUpdatedCallback<T>>,
181 phantom: PhantomData<G>,
182}
183
184impl<G, T: ConfigurationValue<G> + 'static> ConfigrationPrivateData<G, T> {
185 fn set_val(&self, name: *const c_char, val: G, err: *mut *mut raw::RedisModuleString) -> c_int {
186 let configuration_ctx = ConfigurationContext::new();
188 if let Err(e) = self.variable.set(&configuration_ctx, val) {
189 let error_msg = RedisString::create(None, e.to_string().as_str());
190 unsafe { *err = error_msg.take() };
191 return raw::REDISMODULE_ERR as i32;
192 }
193 let c_str_name = unsafe { CStr::from_ptr(name) };
194 if let Some(v) = self.on_changed.as_ref() {
195 v(
196 &configuration_ctx,
197 c_str_name.to_str().unwrap(),
198 self.variable,
199 )
200 }
201 raw::REDISMODULE_OK as i32
202 }
203
204 fn get_val(&self) -> G {
205 self.variable.get(&ConfigurationContext::new())
206 }
207}
208
209extern "C" fn i64_configuration_set<T: ConfigurationValue<i64> + 'static>(
210 name: *const c_char,
211 val: c_longlong,
212 privdata: *mut c_void,
213 err: *mut *mut raw::RedisModuleString,
214) -> c_int {
215 let private_data = unsafe { &*(privdata as *const ConfigrationPrivateData<i64, T>) };
216 private_data.set_val(name, val, err)
217}
218
219extern "C" fn i64_configuration_get<T: ConfigurationValue<i64> + 'static>(
220 _name: *const c_char,
221 privdata: *mut c_void,
222) -> c_longlong {
223 let private_data = unsafe { &*(privdata as *const ConfigrationPrivateData<i64, T>) };
224 private_data.get_val()
225}
226
227pub fn register_i64_configuration<T: ConfigurationValue<i64>>(
228 ctx: &Context,
229 name: &str,
230 variable: &'static T,
231 default: i64,
232 min: i64,
233 max: i64,
234 flags: ConfigurationFlags,
235 on_changed: Option<OnUpdatedCallback<T>>,
236) {
237 let name = CString::new(name).unwrap();
238 let config_private_data = ConfigrationPrivateData {
239 variable,
240 on_changed,
241 phantom: PhantomData::<i64>,
242 };
243 unsafe {
244 raw::RedisModule_RegisterNumericConfig.unwrap()(
245 ctx.ctx,
246 name.as_ptr(),
247 default,
248 flags.bits(),
249 min,
250 max,
251 Some(i64_configuration_get::<T>),
252 Some(i64_configuration_set::<T>),
253 None,
254 Box::into_raw(Box::new(config_private_data)) as *mut c_void,
255 );
256 }
257}
258
259fn find_config_value<'a>(args: &'a [RedisString], name: &str) -> Option<&'a RedisString> {
260 args.iter()
261 .skip_while(|item| !item.as_slice().eq(name.as_bytes()))
262 .nth(1)
263}
264
265pub fn get_i64_default_config_value(
266 args: &[RedisString],
267 name: &str,
268 default: i64,
269) -> Result<i64, RedisError> {
270 find_config_value(args, name).map_or(Ok(default), |arg| {
271 arg.try_as_str()?
272 .parse::<i64>()
273 .map_err(|e| RedisError::String(e.to_string()))
274 })
275}
276
277extern "C" fn string_configuration_set<T: ConfigurationValue<RedisString> + 'static>(
278 name: *const c_char,
279 val: *mut raw::RedisModuleString,
280 privdata: *mut c_void,
281 err: *mut *mut raw::RedisModuleString,
282) -> c_int {
283 let new_val = RedisString::new(None, val);
284 let private_data = unsafe { &*(privdata as *const ConfigrationPrivateData<RedisString, T>) };
285 private_data.set_val(name, new_val, err)
286}
287
288extern "C" fn string_configuration_get<T: ConfigurationValue<RedisString> + 'static>(
289 _name: *const c_char,
290 privdata: *mut c_void,
291) -> *mut raw::RedisModuleString {
292 let private_data = unsafe { &*(privdata as *const ConfigrationPrivateData<RedisString, T>) };
293 private_data
295 .variable
296 .get(&ConfigurationContext::new())
297 .take()
298}
299
300pub fn register_string_configuration<T: ConfigurationValue<RedisString>>(
301 ctx: &Context,
302 name: &str,
303 variable: &'static T,
304 default: &str,
305 flags: ConfigurationFlags,
306 on_changed: Option<OnUpdatedCallback<T>>,
307) {
308 let name = CString::new(name).unwrap();
309 let default = CString::new(default).unwrap();
310 let config_private_data = ConfigrationPrivateData {
311 variable,
312 on_changed,
313 phantom: PhantomData::<RedisString>,
314 };
315 unsafe {
316 raw::RedisModule_RegisterStringConfig.unwrap()(
317 ctx.ctx,
318 name.as_ptr(),
319 default.as_ptr(),
320 flags.bits(),
321 Some(string_configuration_get::<T>),
322 Some(string_configuration_set::<T>),
323 None,
324 Box::into_raw(Box::new(config_private_data)) as *mut c_void,
325 );
326 }
327}
328
329pub fn get_string_default_config_value<'a>(
330 args: &'a [RedisString],
331 name: &str,
332 default: &'a str,
333) -> Result<&'a str, RedisError> {
334 find_config_value(args, name).map_or(Ok(default), |arg| arg.try_as_str())
335}
336
337extern "C" fn bool_configuration_set<T: ConfigurationValue<bool> + 'static>(
338 name: *const c_char,
339 val: i32,
340 privdata: *mut c_void,
341 err: *mut *mut raw::RedisModuleString,
342) -> c_int {
343 let private_data = unsafe { &*(privdata as *const ConfigrationPrivateData<bool, T>) };
344 private_data.set_val(name, val != 0, err)
345}
346
347extern "C" fn bool_configuration_get<T: ConfigurationValue<bool> + 'static>(
348 _name: *const c_char,
349 privdata: *mut c_void,
350) -> c_int {
351 let private_data = unsafe { &*(privdata as *const ConfigrationPrivateData<bool, T>) };
352 private_data.get_val() as i32
353}
354
355pub fn register_bool_configuration<T: ConfigurationValue<bool>>(
356 ctx: &Context,
357 name: &str,
358 variable: &'static T,
359 default: bool,
360 flags: ConfigurationFlags,
361 on_changed: Option<OnUpdatedCallback<T>>,
362) {
363 let name = CString::new(name).unwrap();
364 let config_private_data = ConfigrationPrivateData {
365 variable,
366 on_changed,
367 phantom: PhantomData::<bool>,
368 };
369 unsafe {
370 raw::RedisModule_RegisterBoolConfig.unwrap()(
371 ctx.ctx,
372 name.as_ptr(),
373 default as i32,
374 flags.bits(),
375 Some(bool_configuration_get::<T>),
376 Some(bool_configuration_set::<T>),
377 None,
378 Box::into_raw(Box::new(config_private_data)) as *mut c_void,
379 );
380 }
381}
382
383pub fn get_bool_default_config_value(
384 args: &[RedisString],
385 name: &str,
386 default: bool,
387) -> Result<bool, RedisError> {
388 find_config_value(args, name).map_or(Ok(default), |arg| Ok(arg.try_as_str()? == "yes"))
389}
390
391extern "C" fn enum_configuration_set<
392 G: EnumConfigurationValue,
393 T: ConfigurationValue<G> + 'static,
394>(
395 name: *const c_char,
396 val: i32,
397 privdata: *mut c_void,
398 err: *mut *mut raw::RedisModuleString,
399) -> c_int {
400 let private_data = unsafe { &*(privdata as *const ConfigrationPrivateData<G, T>) };
401 let val: Result<G, _> = val.try_into();
402 match val {
403 Ok(val) => private_data.set_val(name, val, err),
404 Err(e) => {
405 let error_msg = RedisString::create(None, e.to_string().as_str());
406 unsafe { *err = error_msg.take() };
407 raw::REDISMODULE_ERR as i32
408 }
409 }
410}
411
412extern "C" fn enum_configuration_get<
413 G: EnumConfigurationValue,
414 T: ConfigurationValue<G> + 'static,
415>(
416 _name: *const c_char,
417 privdata: *mut c_void,
418) -> c_int {
419 let private_data = unsafe { &*(privdata as *const ConfigrationPrivateData<G, T>) };
420 private_data.get_val().into()
421}
422
423pub fn register_enum_configuration<G: EnumConfigurationValue, T: ConfigurationValue<G>>(
424 ctx: &Context,
425 name: &str,
426 variable: &'static T,
427 default: G,
428 flags: ConfigurationFlags,
429 on_changed: Option<OnUpdatedCallback<T>>,
430) {
431 let name = CString::new(name).unwrap();
432 let (names, vals) = default.get_options();
433 assert_eq!(names.len(), vals.len());
434 let names: Vec<CString> = names
435 .into_iter()
436 .map(|v| CString::new(v).unwrap())
437 .collect();
438 let config_private_data = ConfigrationPrivateData {
439 variable,
440 on_changed,
441 phantom: PhantomData::<G>,
442 };
443 unsafe {
444 raw::RedisModule_RegisterEnumConfig.unwrap()(
445 ctx.ctx,
446 name.as_ptr(),
447 default.into(),
448 flags.bits(),
449 names
450 .iter()
451 .map(|v| v.as_ptr())
452 .collect::<Vec<*const c_char>>()
453 .as_mut_ptr(),
454 vals.as_ptr(),
455 names.len() as i32,
456 Some(enum_configuration_get::<G, T>),
457 Some(enum_configuration_set::<G, T>),
458 None,
459 Box::into_raw(Box::new(config_private_data)) as *mut c_void,
460 );
461 }
462}
463
464pub fn get_enum_default_config_value<G: EnumConfigurationValue>(
465 args: &[RedisString],
466 name: &str,
467 default: G,
468) -> Result<G, RedisError> {
469 find_config_value(args, name).map_or(Ok(default.clone()), |arg| {
470 let (names, vals) = default.get_options();
471 let (index, _name) = names
472 .into_iter()
473 .enumerate()
474 .find(|(_index, item)| item.as_bytes().eq(arg.as_slice()))
475 .ok_or(RedisError::String(format!(
476 "Enum '{}' not exists",
477 arg.to_string_lossy()
478 )))?;
479 G::try_from(vals[index])
480 })
481}
482
483pub fn module_config_get(
484 ctx: &Context,
485 args: Vec<RedisString>,
486 name: &str,
487) -> Result<RedisValue, RedisError> {
488 let mut args: Vec<String> = args
489 .into_iter()
490 .skip(1)
491 .map(|e| format!("{}.{}", name, e.to_string_lossy()))
492 .collect();
493 args.insert(0, "get".into());
494 let res: CallResult = ctx.call_ext(
495 "config",
496 &CallOptionsBuilder::new()
497 .errors_as_replies()
498 .resp(CallOptionResp::Auto)
499 .build(),
500 args.iter()
501 .map(|v| v.as_str())
502 .collect::<Vec<&str>>()
503 .as_slice(),
504 );
505 let res = res.map_err(|e| {
506 RedisError::String(
507 e.to_utf8_string()
508 .unwrap_or("Failed converting error to utf8".into()),
509 )
510 })?;
511 Ok((&res).into())
512}
513
514pub fn module_config_set(
515 ctx: &Context,
516 args: Vec<RedisString>,
517 name: &str,
518) -> Result<RedisValue, RedisError> {
519 let mut args: Vec<String> = args
520 .into_iter()
521 .skip(1)
522 .enumerate()
523 .map(|(index, e)| {
524 if index % 2 == 0 {
525 format!("{}.{}", name, e.to_string_lossy())
526 } else {
527 e.to_string_lossy()
528 }
529 })
530 .collect();
531 args.insert(0, "set".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 RedisError::String(
545 e.to_utf8_string()
546 .unwrap_or("Failed converting error to utf8".into()),
547 )
548 })?;
549 Ok((&res).into())
550}