use crate::app_system_error;
use crate::tina::data::AppResult;
use crate::tina::server::application::Application;
use crate::tina::util::json::JsonUtil;
use crate::tina::util::not_empty::INotEmpty;
use dashmap::DashMap;
use once_cell::sync::Lazy;
use regex::{Regex, RegexBuilder};
use serde_json::Value;
use std::fmt::Debug;
use std::future::Future;
use std::ops::Deref;
use std::pin::Pin;
use std::sync::Arc;
type Func = Arc<
dyn Fn(Application, Value) -> Pin<Box<dyn Future<Output = AppResult<Box<dyn Debug + Send + Sync + 'static>>> + Send + Sync>>
+ Send
+ Sync
+ 'static,
>;
static METHOD_MAP: Lazy<DashMap<String, Func>> = Lazy::new(DashMap::new);
pub struct InvokeUtil;
impl InvokeUtil {
pub async fn invoke_target_name(application: Application, invoke_target: &str) -> AppResult<Box<dyn Debug + Send + Sync + 'static>> {
let (method, param) = Self::get_method(invoke_target)?;
method(application, param).await
}
pub fn regist_method<T, F, R>(method_name: &str, method: F)
where
T: Future<Output = AppResult<R>> + Send + Sync + 'static,
F: Fn(Application, Value) -> T + Copy + Send + Sync + 'static,
R: Debug + Send + Sync + 'static,
{
let f = Arc::new(box_fn(method)) as Func;
METHOD_MAP.insert(method_name.to_string(), f);
}
pub fn get_method(invoke_target: &str) -> AppResult<(Func, Value)> {
let invoke_target = invoke_target.trim();
static REGEX: Lazy<Regex> = Lazy::new(|| RegexBuilder::new("^(.+?)\\((.*)\\)$").build().expect("build invoke target regex failed"));
let cap = REGEX.captures(invoke_target);
match cap {
None => Err(app_system_error!("invalid invoke target expression: {}", invoke_target)),
Some(c) => match (c.get(1), c.get(2)) {
(Some(method), Some(param)) => {
let param_str = param.as_str().trim();
let param_value = match param_str.not_empty() {
true => JsonUtil::parse_json_string::<Value>(param_str)?,
false => Value::Null,
};
let method_name = method.as_str().trim().to_string();
match METHOD_MAP.get(&method_name) {
None => Err(app_system_error!("no registed method found for invoke target: {}", invoke_target)),
Some(method) => Ok((method.deref().clone(), param_value)),
}
}
_ => Err(app_system_error!("invalid invoke target expression for get method and param: {}", invoke_target)),
},
}
}
}
fn box_fn<T, F, R>(
f: F,
) -> impl Fn(Application, Value) -> Pin<Box<dyn Future<Output = AppResult<Box<dyn Debug + Send + Sync + 'static>>> + Send + Sync>>
where
T: Future<Output = AppResult<R>> + Send + Sync + 'static,
F: Fn(Application, Value) -> T + Copy + Send + Sync + 'static,
R: Debug + Send + Sync + 'static,
{
move |application: Application, value: Value| {
Box::pin(async move { f(application, value).await.map(|v| Box::new(v) as Box<dyn Debug + Send + Sync + 'static>) })
}
}
#[allow(unused)]
#[cfg(test)]
mod test {
use crate::tina::data::json::JsonToString;
use crate::tina::data::AppResult;
use crate::tina::server::application::{AppConfig, Application};
use crate::tina::util::invoke_util::InvokeUtil;
use serde_json::{Number, Value};
#[test]
fn test_get_method_params() -> AppResult<()> {
InvokeUtil::regist_method("hello1", |_: Application, v: Value| async move {
println!("{}", v);
Ok(v)
});
let s1 = "hello1()";
let s2 = "hello1(\"1\")";
let s3 = "hello1(1)";
let s4 = "hello1('1')";
assert_eq!(InvokeUtil::get_method(s1)?.1, Value::Null);
assert_eq!(InvokeUtil::get_method(s2)?.1, Value::String("1".to_string()));
assert_eq!(InvokeUtil::get_method(s3)?.1, Value::Number(Number::from(1)));
assert!(InvokeUtil::get_method(s4).is_err());
Ok(())
}
#[tokio::test]
async fn test_invoke_method_name() -> AppResult<()> {
async fn hell2(_: Application, v: Value) -> AppResult<String> {
Ok(v.to_string_value())
}
InvokeUtil::regist_method("hello1", |_: Application, v: Value| async move { Ok(v.to_string_value()) });
InvokeUtil::regist_method("hello2", hell2);
let s1 = "hello1(\"2\")";
let application = Application::from(AppConfig::new());
let r = InvokeUtil::invoke_target_name(application, s1).await?;
let s2 = format!("{:?}", r);
assert_eq!(s2, "\"2\"".to_string());
let s3 = "hello2(\"3\")";
let application = Application::from(AppConfig::new());
let r = InvokeUtil::invoke_target_name(application, s3).await?;
let s4 = format!("{:?}", r);
assert_eq!(s4, "\"3\"".to_string());
Ok(())
}
}