rush_wasm_engine 0.1.0

The rules engine is based on the rete algorithm
Documentation
use crate::{WasmLoaderFile, WasmRuntime, WASM_LOADER_FILE};
use anyhow::anyhow;
use std::collections::HashMap;
use wd_tools::PFErr;

#[async_trait::async_trait]
pub trait WasmLoader: Send + Sync {
    fn load(&self, _rule_name: String, file: String) -> anyhow::Result<Vec<u8>>;
    async fn async_load(&self, rule_name: String, file: String) -> anyhow::Result<Vec<u8>> {
        self.load(rule_name, file)
    }
}

#[derive(Default)]
pub struct WasmRuntimeFactory {
    loader: HashMap<&'static str, Box<dyn WasmLoader>>,
}

impl WasmRuntimeFactory {
    pub fn new() -> Self {
        let loader: HashMap<&'static str, Box<dyn WasmLoader>> = HashMap::new();
        let mut lrf = Self { loader };
        lrf.add_loader(WASM_LOADER_FILE, WasmLoaderFile);
        lrf
    }
    pub fn add_loader<Load: WasmLoader + 'static>(&mut self, tag: &'static str, loader: Load) {
        self.loader.insert(tag, Box::new(loader));
    }
    pub fn remove_loader<S: AsRef<str>>(&mut self, tag: S) {
        self.loader.remove(tag.as_ref());
    }
    fn check_engine(buf: &str) -> anyhow::Result<(String, String)> {
        let buf = buf.trim_start_matches(|c| " \n\r\t".contains(c));
        let (head, body) = if let Some(s) = buf.split_once('\n') {
            s
        } else {
            return anyhow!("first input must is : rule [name] [description] wasm [other]").err();
        };
        let list = head.split(' ').collect::<Vec<_>>();
        if list.len() < 4 {
            return anyhow!("rule header format: rule [name] [description] wasm [other]").err();
        }
        if list[0].to_lowercase() != "rule" {
            return anyhow!("rule header must have start 'rule'").err();
        }
        if list[3].to_lowercase() != "wasm" {
            return anyhow!("WasmRuntime no support rule[{}]", list[3]).err();
        }
        let body = body.trim_start_matches(|c| " \n\r\t".contains(c));
        Ok((list[2].to_string(), body.into()))
    }
    pub fn build<S: AsRef<str>>(&self, rule: S) -> anyhow::Result<WasmRuntime> {
        let (rule, buf) = Self::check_engine(rule.as_ref())?;
        for (k, v) in self.loader.iter() {
            if buf.starts_with(*k) {
                let bytes = v.load(rule, buf)?;
                return WasmRuntime::new(bytes);
            }
        }
        anyhow!("not found eligible loader").err()
    }
    pub async fn async_build<S: AsRef<str>>(&self, rule: S) -> anyhow::Result<WasmRuntime> {
        let (rule, buf) = Self::check_engine(rule.as_ref())?;
        for (k, v) in self.loader.iter() {
            if buf.starts_with(*k) {
                let bytes = v.async_load(rule, buf).await?;
                return WasmRuntime::new(bytes);
            }
        }
        anyhow!("not found eligible loader").err()
    }
}

#[cfg(test)]
mod test {
    use crate::WasmRuntimeFactory;
    use serde_json::Value;
    use std::collections::HashMap;

    const WASM_RULE: &'static str = "
    rule WASM_RULE _ wasm
    wasm_file: ../target/wasm32-unknown-unknown/release/wasm_example_one.wasm
    ";

    #[test]
    fn test_wasm_build() {
        let rt = WasmRuntimeFactory::new().build(WASM_RULE).unwrap();

        let result: HashMap<String, String> = rt.call(Value::String("hello".into())).unwrap();
        assert_eq!(result.get("input").unwrap().as_str(), "hello");
    }
}