zen-engine 0.55.0

Business rules engine
Documentation
use crate::nodes::function::v2::serde::JsValue;
use anyhow::Context as _;
use rquickjs::{Context, Ctx, Error as QError, FromJs, Module, Runtime};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::fmt::Debug;
use std::rc::Rc;
use zen_expression::variable::Variable;

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EvaluateResponse {
    pub output: Variable,
    pub log: Vec<Value>,
}

pub struct Script {
    runtime: Runtime,
}

impl Script {
    pub fn new(runtime: Runtime) -> Self {
        Self { runtime }
    }

    pub async fn call<P>(&mut self, source: &str, args: &P) -> anyhow::Result<EvaluateResponse>
    where
        P: Serialize,
    {
        let runtime = &self.runtime;
        let context = Context::full(&runtime).context("Failed to create context")?;

        let args_str =
            serde_json::to_string(args).context("Failed to serialize function arguments")?;

        let json_response = context.with(|ctx| -> anyhow::Result<String> {
            Module::evaluate(
                ctx.clone(),
                "main",
                "import 'internals'; globalThis.now = Date.now();",
            )
            .unwrap()
            .finish::<()>()
            .unwrap();

            let _ = ctx
                .globals()
                .set("log", Vec::<isize>::new())
                .map_err(|e| map_js_error(&ctx, e))?;

            ctx.eval::<String, _>(format!("{source};main({args_str})"))
                .map_err(|e| map_js_error(&ctx, e))
        })?;

        serde_json::from_str(json_response.as_str()).context("Failed to parse function result")
    }
}

fn map_js_error(ctx: &Ctx, e: QError) -> anyhow::Error {
    let error = JsValue::from_js(&ctx, ctx.catch())
        .map(|v| v.0)
        .unwrap_or(Variable::String(Rc::from(e.to_string().as_str())));

    anyhow::Error::msg(error.to_string())
}