use crate::context::thread_safe::{RedisGILGuard, RedisLockIndicator};
use crate::{raw, CallOptionResp, CallOptionsBuilder, CallResult, RedisValue};
use crate::{Context, RedisError, RedisString};
use bitflags::bitflags;
use std::ffi::{CStr, CString};
use std::marker::PhantomData;
use std::os::raw::{c_char, c_int, c_longlong, c_void};
use std::sync::atomic::{AtomicBool, AtomicI64, Ordering};
use std::sync::Mutex;
bitflags! {
pub struct ConfigurationFlags : u32 {
const DEFAULT = raw::REDISMODULE_CONFIG_DEFAULT;
const IMMUTABLE = raw::REDISMODULE_CONFIG_IMMUTABLE;
const SENSITIVE = raw::REDISMODULE_CONFIG_SENSITIVE;
const HIDDEN = raw::REDISMODULE_CONFIG_HIDDEN;
const PROTECTED = raw::REDISMODULE_CONFIG_PROTECTED;
const DENY_LOADING = raw::REDISMODULE_CONFIG_DENY_LOADING;
const MEMORY = raw::REDISMODULE_CONFIG_MEMORY;
const BITFLAGS = raw::REDISMODULE_CONFIG_BITFLAGS;
}
}
#[macro_export]
macro_rules! enum_configuration {
($(#[$meta:meta])* $vis:vis enum $name:ident {
$($(#[$vmeta:meta])* $vname:ident = $val:expr,)*
}) => {
use $crate::configuration::EnumConfigurationValue;
$(#[$meta])*
$vis enum $name {
$($(#[$vmeta])* $vname = $val,)*
}
impl std::convert::TryFrom<i32> for $name {
type Error = $crate::RedisError;
fn try_from(v: i32) -> Result<Self, Self::Error> {
match v {
$(x if x == $name::$vname as i32 => Ok($name::$vname),)*
_ => Err($crate::RedisError::Str("Value is not supported")),
}
}
}
impl std::convert::From<$name> for i32 {
fn from(val: $name) -> Self {
val as i32
}
}
impl EnumConfigurationValue for $name {
fn get_options(&self) -> (Vec<String>, Vec<i32>) {
(vec![$(stringify!($vname).to_string(),)*], vec![$($val,)*])
}
}
impl Clone for $name {
fn clone(&self) -> Self {
match self {
$($name::$vname => $name::$vname,)*
}
}
}
}
}
pub struct ConfigurationContext {
_dummy: usize, }
impl ConfigurationContext {
fn new() -> ConfigurationContext {
ConfigurationContext { _dummy: 0 }
}
}
unsafe impl RedisLockIndicator for ConfigurationContext {}
pub trait ConfigurationValue<T>: Sync + Send {
fn get(&self, ctx: &ConfigurationContext) -> T;
fn set(&self, ctx: &ConfigurationContext, val: T) -> Result<(), RedisError>;
}
pub trait EnumConfigurationValue: TryFrom<i32, Error = RedisError> + Into<i32> + Clone {
fn get_options(&self) -> (Vec<String>, Vec<i32>);
}
impl<T: Clone> ConfigurationValue<T> for RedisGILGuard<T> {
fn get(&self, ctx: &ConfigurationContext) -> T {
let value = self.lock(ctx);
value.clone()
}
fn set(&self, ctx: &ConfigurationContext, val: T) -> Result<(), RedisError> {
let mut value = self.lock(ctx);
*value = val;
Ok(())
}
}
impl<T: Clone + Send> ConfigurationValue<T> for Mutex<T> {
fn get(&self, _ctx: &ConfigurationContext) -> T {
let value = self.lock().unwrap();
value.clone()
}
fn set(&self, _ctx: &ConfigurationContext, val: T) -> Result<(), RedisError> {
let mut value = self.lock().unwrap();
*value = val;
Ok(())
}
}
impl ConfigurationValue<i64> for AtomicI64 {
fn get(&self, _ctx: &ConfigurationContext) -> i64 {
self.load(Ordering::Relaxed)
}
fn set(&self, _ctx: &ConfigurationContext, val: i64) -> Result<(), RedisError> {
self.store(val, Ordering::Relaxed);
Ok(())
}
}
impl ConfigurationValue<RedisString> for RedisGILGuard<String> {
fn get(&self, ctx: &ConfigurationContext) -> RedisString {
let value = self.lock(ctx);
RedisString::create(None, value.as_str())
}
fn set(&self, ctx: &ConfigurationContext, val: RedisString) -> Result<(), RedisError> {
let mut value = self.lock(ctx);
*value = val.try_as_str()?.to_string();
Ok(())
}
}
impl ConfigurationValue<RedisString> for Mutex<String> {
fn get(&self, _ctx: &ConfigurationContext) -> RedisString {
let value = self.lock().unwrap();
RedisString::create(None, value.as_str())
}
fn set(&self, _ctx: &ConfigurationContext, val: RedisString) -> Result<(), RedisError> {
let mut value = self.lock().unwrap();
*value = val.try_as_str()?.to_string();
Ok(())
}
}
impl ConfigurationValue<bool> for AtomicBool {
fn get(&self, _ctx: &ConfigurationContext) -> bool {
self.load(Ordering::Relaxed)
}
fn set(&self, _ctx: &ConfigurationContext, val: bool) -> Result<(), RedisError> {
self.store(val, Ordering::Relaxed);
Ok(())
}
}
type OnUpdatedCallback<T> = Box<dyn Fn(&ConfigurationContext, &str, &'static T)>;
struct ConfigrationPrivateData<G, T: ConfigurationValue<G> + 'static> {
variable: &'static T,
on_changed: Option<OnUpdatedCallback<T>>,
phantom: PhantomData<G>,
}
impl<G, T: ConfigurationValue<G> + 'static> ConfigrationPrivateData<G, T> {
fn set_val(&self, name: *const c_char, val: G, err: *mut *mut raw::RedisModuleString) -> c_int {
let configuration_ctx = ConfigurationContext::new();
if let Err(e) = self.variable.set(&configuration_ctx, val) {
let error_msg = RedisString::create(None, e.to_string().as_str());
unsafe { *err = error_msg.take() };
return raw::REDISMODULE_ERR as i32;
}
let c_str_name = unsafe { CStr::from_ptr(name) };
if let Some(v) = self.on_changed.as_ref() {
v(
&configuration_ctx,
c_str_name.to_str().unwrap(),
self.variable,
)
}
raw::REDISMODULE_OK as i32
}
fn get_val(&self) -> G {
self.variable.get(&ConfigurationContext::new())
}
}
extern "C" fn i64_configuration_set<T: ConfigurationValue<i64> + 'static>(
name: *const c_char,
val: c_longlong,
privdata: *mut c_void,
err: *mut *mut raw::RedisModuleString,
) -> c_int {
let private_data = unsafe { &*(privdata as *const ConfigrationPrivateData<i64, T>) };
private_data.set_val(name, val, err)
}
extern "C" fn i64_configuration_get<T: ConfigurationValue<i64> + 'static>(
_name: *const c_char,
privdata: *mut c_void,
) -> c_longlong {
let private_data = unsafe { &*(privdata as *const ConfigrationPrivateData<i64, T>) };
private_data.get_val()
}
pub fn register_i64_configuration<T: ConfigurationValue<i64>>(
ctx: &Context,
name: &str,
variable: &'static T,
default: i64,
min: i64,
max: i64,
flags: ConfigurationFlags,
on_changed: Option<OnUpdatedCallback<T>>,
) {
let name = CString::new(name).unwrap();
let config_private_data = ConfigrationPrivateData {
variable,
on_changed,
phantom: PhantomData::<i64>,
};
unsafe {
raw::RedisModule_RegisterNumericConfig.unwrap()(
ctx.ctx,
name.as_ptr(),
default,
flags.bits(),
min,
max,
Some(i64_configuration_get::<T>),
Some(i64_configuration_set::<T>),
None,
Box::into_raw(Box::new(config_private_data)) as *mut c_void,
);
}
}
fn find_config_value<'a>(args: &'a [RedisString], name: &str) -> Option<&'a RedisString> {
args.iter()
.skip_while(|item| !item.as_slice().eq(name.as_bytes()))
.nth(1)
}
pub fn get_i64_default_config_value(
args: &[RedisString],
name: &str,
default: i64,
) -> Result<i64, RedisError> {
find_config_value(args, name).map_or(Ok(default), |arg| {
arg.try_as_str()?
.parse::<i64>()
.map_err(|e| RedisError::String(e.to_string()))
})
}
extern "C" fn string_configuration_set<T: ConfigurationValue<RedisString> + 'static>(
name: *const c_char,
val: *mut raw::RedisModuleString,
privdata: *mut c_void,
err: *mut *mut raw::RedisModuleString,
) -> c_int {
let new_val = RedisString::new(None, val);
let private_data = unsafe { &*(privdata as *const ConfigrationPrivateData<RedisString, T>) };
private_data.set_val(name, new_val, err)
}
extern "C" fn string_configuration_get<T: ConfigurationValue<RedisString> + 'static>(
_name: *const c_char,
privdata: *mut c_void,
) -> *mut raw::RedisModuleString {
let private_data = unsafe { &*(privdata as *const ConfigrationPrivateData<RedisString, T>) };
private_data
.variable
.get(&ConfigurationContext::new())
.take()
}
pub fn register_string_configuration<T: ConfigurationValue<RedisString>>(
ctx: &Context,
name: &str,
variable: &'static T,
default: &str,
flags: ConfigurationFlags,
on_changed: Option<OnUpdatedCallback<T>>,
) {
let name = CString::new(name).unwrap();
let default = CString::new(default).unwrap();
let config_private_data = ConfigrationPrivateData {
variable,
on_changed,
phantom: PhantomData::<RedisString>,
};
unsafe {
raw::RedisModule_RegisterStringConfig.unwrap()(
ctx.ctx,
name.as_ptr(),
default.as_ptr(),
flags.bits(),
Some(string_configuration_get::<T>),
Some(string_configuration_set::<T>),
None,
Box::into_raw(Box::new(config_private_data)) as *mut c_void,
);
}
}
pub fn get_string_default_config_value<'a>(
args: &'a [RedisString],
name: &str,
default: &'a str,
) -> Result<&'a str, RedisError> {
find_config_value(args, name).map_or(Ok(default), |arg| arg.try_as_str())
}
extern "C" fn bool_configuration_set<T: ConfigurationValue<bool> + 'static>(
name: *const c_char,
val: i32,
privdata: *mut c_void,
err: *mut *mut raw::RedisModuleString,
) -> c_int {
let private_data = unsafe { &*(privdata as *const ConfigrationPrivateData<bool, T>) };
private_data.set_val(name, val != 0, err)
}
extern "C" fn bool_configuration_get<T: ConfigurationValue<bool> + 'static>(
_name: *const c_char,
privdata: *mut c_void,
) -> c_int {
let private_data = unsafe { &*(privdata as *const ConfigrationPrivateData<bool, T>) };
private_data.get_val() as i32
}
pub fn register_bool_configuration<T: ConfigurationValue<bool>>(
ctx: &Context,
name: &str,
variable: &'static T,
default: bool,
flags: ConfigurationFlags,
on_changed: Option<OnUpdatedCallback<T>>,
) {
let name = CString::new(name).unwrap();
let config_private_data = ConfigrationPrivateData {
variable,
on_changed,
phantom: PhantomData::<bool>,
};
unsafe {
raw::RedisModule_RegisterBoolConfig.unwrap()(
ctx.ctx,
name.as_ptr(),
default as i32,
flags.bits(),
Some(bool_configuration_get::<T>),
Some(bool_configuration_set::<T>),
None,
Box::into_raw(Box::new(config_private_data)) as *mut c_void,
);
}
}
pub fn get_bool_default_config_value(
args: &[RedisString],
name: &str,
default: bool,
) -> Result<bool, RedisError> {
find_config_value(args, name).map_or(Ok(default), |arg| Ok(arg.try_as_str()? == "yes"))
}
extern "C" fn enum_configuration_set<
G: EnumConfigurationValue,
T: ConfigurationValue<G> + 'static,
>(
name: *const c_char,
val: i32,
privdata: *mut c_void,
err: *mut *mut raw::RedisModuleString,
) -> c_int {
let private_data = unsafe { &*(privdata as *const ConfigrationPrivateData<G, T>) };
let val: Result<G, _> = val.try_into();
match val {
Ok(val) => private_data.set_val(name, val, err),
Err(e) => {
let error_msg = RedisString::create(None, e.to_string().as_str());
unsafe { *err = error_msg.take() };
raw::REDISMODULE_ERR as i32
}
}
}
extern "C" fn enum_configuration_get<
G: EnumConfigurationValue,
T: ConfigurationValue<G> + 'static,
>(
_name: *const c_char,
privdata: *mut c_void,
) -> c_int {
let private_data = unsafe { &*(privdata as *const ConfigrationPrivateData<G, T>) };
private_data.get_val().into()
}
pub fn register_enum_configuration<G: EnumConfigurationValue, T: ConfigurationValue<G>>(
ctx: &Context,
name: &str,
variable: &'static T,
default: G,
flags: ConfigurationFlags,
on_changed: Option<OnUpdatedCallback<T>>,
) {
let name = CString::new(name).unwrap();
let (names, vals) = default.get_options();
assert_eq!(names.len(), vals.len());
let names: Vec<CString> = names
.into_iter()
.map(|v| CString::new(v).unwrap())
.collect();
let config_private_data = ConfigrationPrivateData {
variable,
on_changed,
phantom: PhantomData::<G>,
};
unsafe {
raw::RedisModule_RegisterEnumConfig.unwrap()(
ctx.ctx,
name.as_ptr(),
default.into(),
flags.bits(),
names
.iter()
.map(|v| v.as_ptr())
.collect::<Vec<*const c_char>>()
.as_mut_ptr(),
vals.as_ptr(),
names.len() as i32,
Some(enum_configuration_get::<G, T>),
Some(enum_configuration_set::<G, T>),
None,
Box::into_raw(Box::new(config_private_data)) as *mut c_void,
);
}
}
pub fn get_enum_default_config_value<G: EnumConfigurationValue>(
args: &[RedisString],
name: &str,
default: G,
) -> Result<G, RedisError> {
find_config_value(args, name).map_or(Ok(default.clone()), |arg| {
let (names, vals) = default.get_options();
let (index, _name) = names
.into_iter()
.enumerate()
.find(|(_index, item)| item.as_bytes().eq(arg.as_slice()))
.ok_or(RedisError::String(format!(
"Enum '{}' not exists",
arg.to_string_lossy()
)))?;
G::try_from(vals[index])
})
}
pub fn module_config_get(
ctx: &Context,
args: Vec<RedisString>,
name: &str,
) -> Result<RedisValue, RedisError> {
let mut args: Vec<String> = args
.into_iter()
.skip(1)
.map(|e| format!("{}.{}", name, e.to_string_lossy()))
.collect();
args.insert(0, "get".into());
let res: CallResult = ctx.call_ext(
"config",
&CallOptionsBuilder::new()
.errors_as_replies()
.resp(CallOptionResp::Auto)
.build(),
args.iter()
.map(|v| v.as_str())
.collect::<Vec<&str>>()
.as_slice(),
);
let res = res.map_err(|e| {
RedisError::String(
e.to_utf8_string()
.unwrap_or("Failed converting error to utf8".into()),
)
})?;
Ok((&res).into())
}
pub fn module_config_set(
ctx: &Context,
args: Vec<RedisString>,
name: &str,
) -> Result<RedisValue, RedisError> {
let mut args: Vec<String> = args
.into_iter()
.skip(1)
.enumerate()
.map(|(index, e)| {
if index % 2 == 0 {
format!("{}.{}", name, e.to_string_lossy())
} else {
e.to_string_lossy()
}
})
.collect();
args.insert(0, "set".into());
let res: CallResult = ctx.call_ext(
"config",
&CallOptionsBuilder::new()
.errors_as_replies()
.resp(CallOptionResp::Auto)
.build(),
args.iter()
.map(|v| v.as_str())
.collect::<Vec<&str>>()
.as_slice(),
);
let res = res.map_err(|e| {
RedisError::String(
e.to_utf8_string()
.unwrap_or("Failed converting error to utf8".into()),
)
})?;
Ok((&res).into())
}