chimes_utils/utils/
script_engine.rs

1use std::cell::RefCell;
2
3use std::{fs::write, sync::Mutex};
4
5use base64::prelude::*;
6use futures_util::future::LocalBoxFuture;
7use rhai::Scope;
8use serde_json::{Map, Value};
9use tera::{Context, Tera};
10
11use crate::{get_local_timestamp, ChimesError};
12
13fn base64_decode(base: &str, urlsafe: bool) -> Result<Vec<u8>, ChimesError> {
14    let newt = base.replace('\n', "");
15    let newt_str = newt.as_str();
16    // let eng = base64::prelude::
17    if urlsafe {
18        match BASE64_URL_SAFE_NO_PAD.decode(newt_str) {
19            Ok(vs) => Ok(vs),
20            Err(err) => {
21                log::info!("Decode URL_SAFE_NO_PAD the base64 with error {}", err);
22                match BASE64_URL_SAFE.decode(newt_str) {
23                    Ok(vs) => Ok(vs),
24                    Err(err) => {
25                        log::info!("Decode URL_SAFE the base64 with error {}", err);
26                        Err(ChimesError::custom(100010, err.to_string()))
27                    }
28                }
29            }
30        }
31    } else {
32        match BASE64_STANDARD_NO_PAD.decode(newt_str) {
33            Ok(vs) => Ok(vs),
34            Err(err) => {
35                log::info!("Decode STANDARD_NO_PAD the base64 with error {}", err);
36                match BASE64_STANDARD.decode(newt_str) {
37                    Ok(vs) => Ok(vs),
38                    Err(err) => {
39                        log::info!("Decode STANDARD the base64 with error {}", err);
40                        Err(ChimesError::custom(100010, err.to_string()))
41                    }
42                }
43            }
44        }
45    }
46}
47
48fn base64_encode(content: Vec<u8>) -> String {
49    BASE64_STANDARD.encode(content)
50}
51
52pub fn write_file(path: &String, content: &Vec<u8>) {
53    match write(path, content) {
54        Ok(_) => {}
55        Err(err) => {
56            log::info!("Write file with an error {}", err);
57        }
58    }
59}
60
61pub fn translate_dict(_name: String, _value: String) -> String {
62    String::new()
63}
64
65pub trait DictTranslate {
66    fn translate(&self, name: &str, val: &str) -> String;
67    fn reload_all_dicts(&'static self) -> LocalBoxFuture<'static, Result<(), ChimesError>>;
68}
69
70pub struct Dummy {}
71
72impl DictTranslate for Dummy {
73    fn translate(&self, _name: &str, _val: &str) -> String {
74        String::new()
75    }
76
77    fn reload_all_dicts(&'static self) -> LocalBoxFuture<'static, Result<(), ChimesError>> {
78        Box::pin(async move { Ok(()) })
79    }
80}
81
82pub struct Translation {
83    h: Box<dyn DictTranslate>,
84}
85
86impl Translation {
87    pub fn new() -> Self {
88        Self {
89            h: Box::new(Dummy {}),
90        }
91    }
92
93    pub fn replace(&mut self, dt: impl DictTranslate + 'static) {
94        self.h = Box::new(dt);
95    }
96}
97
98impl Default for Translation {
99    fn default() -> Self {
100        Self::new()
101    }
102}
103
104unsafe impl Send for Translation {}
105unsafe impl Sync for Translation {}
106
107lazy_static! {
108    pub static ref FUNCTION_TRANSLATE_DICT: Mutex<RefCell<Translation>> =
109        Mutex::new(RefCell::new(Translation::new()));
110}
111
112pub fn set_translate_dict_fn(func: impl DictTranslate + 'static) {
113    FUNCTION_TRANSLATE_DICT
114        .lock()
115        .unwrap()
116        .borrow_mut()
117        .replace(func);
118}
119
120pub fn get_translate_dict_fn() -> &'static Translation {
121    unsafe { &*FUNCTION_TRANSLATE_DICT.lock().unwrap().as_ptr() }
122}
123
124pub async fn reload_translate_dicts() {
125    // FUNCTION_TRANSLATE_DICT.lock().unwrap().borrow_mut().h.reload_all_dicts();
126    match get_translate_dict_fn().h.reload_all_dicts().await {
127        Ok(_) => {
128            log::info!("Reload dicts successfully.");
129        }
130        Err(err) => {
131            log::info!("Failed: {} ", err);
132        }
133    }
134}
135
136pub fn script_eval(
137    script: &str,
138    ctx: &Map<String, Value>,
139) -> Result<std::string::String, ChimesError> {
140    let mut engine = rhai::Engine::new();
141    let mut scope = Scope::new();
142
143    engine.register_fn("timestamp", get_local_timestamp);
144
145    engine.register_fn("snowflake_id", move |prefix: &str| {
146        let new_id = rbatis::snowflake::new_snowflake_id();
147        format!("{}_{}", prefix, new_id)
148    });
149
150    engine.register_fn("base64_encode", move |content: Vec<u8>| {
151        base64_encode(content)
152    });
153
154    engine.register_fn(
155        "base64_decode_std",
156        move |content: String| match base64_decode(content.as_str(), false) {
157            Ok(ts) => ts,
158            Err(_) => vec![],
159        },
160    );
161
162    engine.register_fn(
163        "base64_decode_url",
164        move |content: String| match base64_decode(content.as_str(), true) {
165            Ok(ts) => ts,
166            Err(_) => vec![],
167        },
168    );
169
170    engine.register_fn("write_file", move |name: &str, content: Vec<u8>| {
171        write_file(&name.to_string(), &content);
172    });
173
174    engine.register_fn("translate_dict", move |name: &str, val: &str| {
175        get_translate_dict_fn().h.translate(name, val)
176    });
177
178    let json = Value::Object(ctx.clone()).to_string();
179
180    match engine.parse_json(json.as_str(), true) {
181        Ok(dynval) => {
182            scope.push("ctx", dynval);
183        }
184        Err(_) => {
185            log::info!("could not convert to rhai::Dynamic. ");
186        }
187    };
188
189    log::debug!("Script: {}", script);
190
191    let tt = match engine.eval_with_scope::<String>(&mut scope, script) {
192        Ok(t) => t,
193        Err(err) => {
194            log::info!("error on execute the script: {}", err.to_string());
195            return Err(ChimesError::custom(210, err.to_string()));
196        }
197    };
198    Ok(tt)
199}
200
201pub fn template_eval(script: &str, ctx: Value) -> Result<String, ChimesError> {
202    let mut tera = match Tera::new("templates/**/*.tera") {
203        Ok(t) => t,
204        Err(err) => {
205            log::info!(
206                "Could not found tera context {}, then use default Tera.",
207                err
208            );
209            // return Err(ChimesError::custom(310, err.to_string()));
210            Tera::default()
211        }
212    };
213
214    let context = match Context::from_serialize(&ctx) {
215        Ok(c) => c,
216        Err(_) => Context::new(),
217    };
218
219    match tera.render_str(script, &context) {
220        Ok(text) => Ok(text),
221        Err(err) => Err(ChimesError::custom(410, err.to_string())),
222    }
223}