Skip to main content

javascript_mel/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![doc = include_str!("../README.md")]
3
4#[cfg(any(
5    all(feature = "real", feature = "mock"),
6    not(any(feature = "real", feature = "mock"))
7))]
8compile_error!("One of the two features 'real' or 'mock' must be enabled");
9
10mod engine;
11
12use async_std::sync::Mutex;
13use engine::Engine;
14use json_mel::*;
15use melodium_core::*;
16use melodium_macro::{check, mel_model, mel_package, mel_treatment};
17use std::{
18    collections::HashMap,
19    sync::{Arc, Weak},
20};
21
22#[derive(Debug)]
23/// Provides a JavaScript/ECMAScript execution engine.
24///
25/// The engine is initialised with `code`, which may define functions, set up variables, or
26/// perform any one-time setup useful for later processing. Use the `process` treatment to run
27/// JavaScript against data at track time.
28///
29/// - `stack_size_limit`: maximum stack size available to JavaScript code, in bytes.
30/// - `recursion_limit`: maximum call-stack depth.
31/// - `loop_iteration_limit`: maximum iterations any single loop may perform.
32/// - `strict`: enables strict mode; can also be enabled per-script with `"use strict"`.
33/// - `code`: JavaScript source loaded once at engine initialisation.
34#[mel_model(
35    param stack_size_limit u64 1024
36    param recursion_limit u64 400
37    param loop_iteration_limit u64 4294967295
38    param strict bool false
39    param {content(javascript)} code string ""
40    initialize initialize
41    shutdown shutdown
42)]
43pub struct JavaScriptEngine {
44    model: Weak<JavaScriptEngineModel>,
45    engine: Mutex<Option<Engine>>,
46}
47
48impl JavaScriptEngine {
49    fn new(model: Weak<JavaScriptEngineModel>) -> Self {
50        Self {
51            model,
52            engine: Mutex::new(None),
53        }
54    }
55
56    fn initialize(&self) {
57        let model = self.model.upgrade().unwrap();
58
59        let engine = Engine::new(
60            model.get_stack_size_limit(),
61            model.get_recursion_limit(),
62            model.get_loop_iteration_limit(),
63            model.get_strict(),
64            model.get_code(),
65        );
66
67        async_std::task::block_on(async move {
68            *model.inner().engine.lock().await = Some(engine);
69        });
70    }
71
72    fn shutdown(&self) {
73        let model = self.model.upgrade().unwrap();
74        async_std::task::block_on(async move {
75            if let Some(engine) = model.inner().engine.lock().await.as_ref() {
76                engine.stop();
77            }
78        });
79    }
80
81    fn invoke_source(&self, _source: &str, _params: HashMap<String, Value>) {}
82
83    pub async fn process(
84        &self,
85        value: serde_json::Value,
86        code: String,
87    ) -> Result<Result<serde_json::Value, String>, ()> {
88        if let Some(engine) = self.engine.lock().await.as_ref() {
89            let res = engine.process(value, code).await;
90            res
91        } else {
92            Err(())
93        }
94    }
95
96    pub(crate) fn engine(&self) -> &Mutex<Option<Engine>> {
97        &self.engine
98    }
99}
100
101/// Executes JavaScript code on values.
102///
103/// For every incoming `value`, `code` is executed as-is within `engine`.
104/// Inside the `code` part the incoming value is reffered as globally-accessible `value` variable.
105/// `code` can return any JS object convertible into JSON data.
106///
107/// If `code` not actually processable JavaScript code, or its return value not convertible into JSON, a none `result` value is send.
108#[mel_treatment(
109    model engine JavaScriptEngine
110    input value Stream<Json>
111    output result Stream<Option<Json>>
112)]
113pub async fn process(#[mel(content(javascript))] code: string) {
114    let engine = JavaScriptEngineModel::into(engine);
115
116    'main: while let Ok(values) = value
117        .recv_many()
118        .await
119        .map(|values| Into::<Vec<Value>>::into(values))
120    {
121        for value in values {
122            if let Value::Data(value) = value {
123                match value.downcast_arc::<Json>() {
124                    Ok(value) => {
125                        let processed;
126                        if let Some(engine) = engine.inner().engine().lock().await.as_ref() {
127                            processed = engine.process(value.0.clone(), code.clone()).await;
128                        } else {
129                            break;
130                        }
131
132                        match processed {
133                            Ok(Ok(value)) => {
134                                check!(
135                                    result
136                                        .send_one_as(Some(Arc::new(Json(value)) as Arc<dyn Data>))
137                                        .await
138                                );
139                            }
140                            Ok(Err(_err)) => {
141                                check!(result.send_one_as(Option::<Arc<dyn Data>>::None).await);
142                            }
143                            Err(_) => {
144                                break 'main;
145                            }
146                        }
147                    }
148                    Err(_) => break 'main,
149                }
150            }
151        }
152    }
153}
154
155mel_package!();