use std::collections::HashMap; use itertools::Itertools; use quickjs_rusty::serde::from_js; use quickjs_rusty::utils::create_null; use quickjs_rusty::Context; use quickjs_rusty::ContextError; use quickjs_rusty::ExecutionError; use quickjs_rusty::JsFunction; use quickjs_rusty::OwnedJsValue as JsValue; use quickjs_rusty::ValueError; use serde::Deserialize; use serde::Serialize; #[derive(thiserror::Error, Debug)] pub enum ScriptError { #[error("error context: {0:?}")] ContextError(#[from] ContextError), #[error("error running: {0:?}")] ExecutionError(#[from] ExecutionError), #[error("error from anyhow: {0:?}")] SerdeError(#[from] quickjs_rusty::serde::Error), #[error("error value: {0:?}")] ValueError(#[from] ValueError), } pub type ScriptResult = Result; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct BotFunction(String); // temporal workaround impl BotFunction { pub fn call_context(&self, runner: &Runner) -> ScriptResult { let func_name = &self.0; runner.run_script(&format!("{func_name}()")) } } pub trait DeserializeJS { fn js_into<'a, T: Deserialize<'a>>(&'a self) -> ScriptResult; } impl DeserializeJS for JsValue { fn js_into<'a, T: Deserialize<'a>>(&'a self) -> ScriptResult { let rc = from_js(self.context(), self)?; Ok(rc) } } #[derive(Default)] pub struct DeserializerJS { fn_map: HashMap, } impl DeserializerJS { pub fn new() -> Self { Self { fn_map: HashMap::new(), } } pub fn deserialize_js<'a, T: Deserialize<'a>>(value: &'a JsValue) -> ScriptResult { let mut s = Self::new(); s.inject_templates(value, "".to_string())?; let res = value.js_into()?; // val.map_functions(s.fn_map); Ok(res) } pub fn inject_templates( &mut self, value: &JsValue, path: String, ) -> ScriptResult> { if let Ok(f) = value.clone().try_into_function() { self.fn_map.insert(path.clone(), f); return Ok(Some(path)); } else if let Ok(o) = value.clone().try_into_object() { let path = if path.is_empty() { path } else { path + "." }; // trying to avoid . in the start // of stringified path let res = o .properties_iter()? .chunks(2) .into_iter() // since chunks(2) is used and properties iterator over object // always has even elements, unwrap will not fail .map( #[allow(clippy::unwrap_used)] |mut chunk| (chunk.next().unwrap(), chunk.next().unwrap()), ) .map(|(k, p)| k.and_then(|k| p.map(|p| (k, p)))) .filter_map(|m| m.ok()) .try_for_each(|(k, p)| { let k = match k.to_string() { Ok(k) => k, Err(err) => return Err(ScriptError::ValueError(err)), }; let res = match self.inject_templates(&p, path.clone() + &k)? { Some(_) => o.set_property(&k, JsValue::new(o.context(), create_null())), None => Ok(()), }; match res { Ok(res) => Ok(res), Err(err) => Err(ScriptError::ExecutionError(err)), } }); res?; }; Ok(None) } } // TODO: remove this function since it is suitable only for early development #[allow(clippy::print_stdout)] fn print(s: String) { println!("{s}"); } #[derive(Serialize, Deserialize, Debug)] pub struct BotConfig { version: f64, } #[derive(Serialize, Deserialize, Debug)] pub struct Button { name: String, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct BotMessage { // buttons: Vec