tina-core 0.0.2

Tina platform
Documentation
//! 调用工具

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(())
    }
}