moduforge_rules_engine/handler/function/
serde.rs1use 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}