moduforge_rules_engine/handler/function/
serde.rs

1use crate::handler::function::error::ResultExt;
2use ahash::{HashMap, HashMapExt};
3use rquickjs::{Ctx, FromJs, IntoAtom, IntoJs, Type, Value as QValue};
4use rust_decimal::prelude::ToPrimitive;
5use rust_decimal::Decimal;
6use serde_json::json;
7use std::rc::Rc;
8use moduforge_rules_expression::variable::Variable;
9
10#[derive(Debug)]
11pub struct JsValue(pub Variable);
12
13impl<'js> FromJs<'js> for JsValue {
14    fn from_js(
15        ctx: &Ctx<'js>,
16        v: QValue<'js>,
17    ) -> rquickjs::Result<Self> {
18        let computed_value = match v.type_of() {
19            Type::Uninitialized | Type::Undefined | Type::Null => {
20                Variable::Null
21            },
22            Type::Bool => Variable::Bool(
23                v.as_bool().or_throw_msg(ctx, "failed to convert to bool")?,
24            ),
25            Type::Int => Variable::Number(Decimal::from(
26                v.as_int().or_throw_msg(ctx, "failed to convert to int")?,
27            )),
28            Type::BigInt => Variable::Number(Decimal::from(
29                v.into_big_int()
30                    .map(|b| b.clone().to_i64().ok())
31                    .flatten()
32                    .or_throw_msg(ctx, "failed to convert to number")?,
33            )),
34            Type::Float => Variable::Number(
35                Decimal::try_from(
36                    v.as_float()
37                        .or_throw_msg(ctx, "failed to convert to number")?,
38                )
39                .or_throw_msg(ctx, "failed to convert to number")?,
40            ),
41            Type::String => Variable::String(
42                v.into_string()
43                    .map(|s| s.to_string().ok())
44                    .flatten()
45                    .map(|s| Rc::from(s.as_str()))
46                    .or_throw_msg(ctx, "failed to convert to string")?,
47            ),
48            Type::Array => {
49                let arr = v
50                    .into_array()
51                    .or_throw_msg(ctx, "failed to convert to array")?;
52
53                let mut js_arr = Vec::with_capacity(arr.len());
54                for x in arr.into_iter() {
55                    js_arr.push(
56                        JsValue::from_js(ctx, x.or_throw(ctx)?)
57                            .or_throw(ctx)?
58                            .0,
59                    )
60                }
61
62                Variable::from_array(js_arr)
63            },
64            Type::Object => {
65                let object = v
66                    .into_object()
67                    .or_throw_msg(ctx, "failed to convert to object")?;
68
69                let mut js_object = HashMap::with_capacity(object.len());
70                for p in object.props::<String, QValue>() {
71                    let (k, v) = p.or_throw(ctx)?;
72                    js_object.insert(
73                        Rc::from(k.as_str()),
74                        JsValue::from_js(ctx, v).or_throw(ctx)?.0,
75                    );
76                }
77
78                Variable::from_object(js_object)
79            },
80            Type::Exception => {
81                let exception = v
82                    .into_exception()
83                    .or_throw_msg(ctx, "failed to convert to exception")?;
84
85                let message = exception.message().unwrap_or_default();
86                let description = exception.to_string();
87
88                json!({ "message": message, "description": description }).into()
89            },
90            Type::Function => json!("[Function]").into(),
91            Type::Module => json!("[Module]").into(),
92            Type::Constructor => json!("[Constructor]").into(),
93            Type::Symbol => json!("[Symbol]").into(),
94            Type::Unknown => json!("[Unknown]").into(),
95            Type::Promise => {
96                let promise = v.into_promise().or_throw(ctx)?;
97                let val: JsValue = promise.finish()?;
98                val.0
99            },
100        };
101
102        Ok(JsValue(computed_value))
103    }
104}
105
106impl<'js> IntoJs<'js> for JsValue {
107    fn into_js(
108        self,
109        ctx: &Ctx<'js>,
110    ) -> rquickjs::Result<QValue<'js>> {
111        let res = match self.0 {
112            Variable::Null => QValue::new_null(ctx.clone()),
113            Variable::Bool(b) => QValue::new_bool(ctx.clone(), b),
114            Variable::Number(n) => QValue::new_number(
115                ctx.clone(),
116                n.to_f64()
117                    .or_throw_msg(ctx, "failed to convert float to number")?,
118            ),
119            Variable::String(str) => str.into_js(ctx)?,
120            Variable::Array(a) => {
121                let qarr = rquickjs::Array::new(ctx.clone())?;
122
123                let arr = a.borrow();
124                for (idx, item) in arr.iter().enumerate() {
125                    qarr.set(idx, JsValue(item.clone()))?;
126                }
127
128                qarr.into_value()
129            },
130            Variable::Object(o) => {
131                let qmap = rquickjs::Object::new(ctx.clone())?;
132
133                let obj = o.borrow();
134                for (key, value) in obj.iter() {
135                    qmap.set(key.into_atom(ctx)?, JsValue(value.clone()))?;
136                }
137
138                qmap.into_value()
139            },
140            Variable::Dynamic(d) => d.to_string().into_js(ctx)?,
141        };
142
143        Ok(res)
144    }
145}