vector_vrl_web_playground/
lib.rs

1use std::collections::BTreeMap;
2
3use gloo_utils::format::JsValueSerdeExt;
4use serde::{Deserialize, Serialize};
5use vrl::{
6    compiler::{
7        CompileConfig, TargetValue, TimeZone, TypeState, compile_with_state,
8        runtime::{Runtime, Terminate},
9    },
10    diagnostic::{DiagnosticList, Formatter},
11    value::{Secrets, Value},
12};
13use wasm_bindgen::prelude::*;
14
15pub mod built_info {
16    include!(concat!(env!("OUT_DIR"), "/built.rs"));
17}
18
19#[derive(Serialize, Deserialize)]
20pub struct Input {
21    pub program: String,
22    pub event: Value,
23}
24
25impl Input {
26    pub fn new(program: &str, event: Value) -> Self {
27        Self {
28            program: program.to_owned(),
29            event,
30        }
31    }
32}
33
34// The module returns the result of the last expression, the resulting event,
35// and the execution time.
36#[derive(Deserialize, Serialize)]
37pub struct VrlCompileResult {
38    pub runtime_result: Value,
39    pub target_value: Value,
40    pub elapsed_time: Option<f64>,
41}
42
43#[derive(Deserialize, Serialize, Default)]
44pub struct VrlDiagnosticResult {
45    pub list: Vec<String>,
46    pub msg: String,
47    pub msg_colorized: String,
48}
49
50impl VrlDiagnosticResult {
51    fn new(program: &str, diagnostic_list: DiagnosticList) -> Self {
52        Self {
53            list: diagnostic_list
54                .clone()
55                .into_iter()
56                .map(|diag| String::from(diag.message()))
57                .collect(),
58            msg: Formatter::new(program, diagnostic_list.clone()).to_string(),
59            msg_colorized: Formatter::new(program, diagnostic_list)
60                .colored()
61                .to_string(),
62        }
63    }
64
65    fn new_runtime_error(program: &str, terminate: Terminate) -> Self {
66        Self {
67            list: Vec::with_capacity(1),
68            msg: Formatter::new(program, terminate.clone().get_expression_error()).to_string(),
69            msg_colorized: Formatter::new(program, terminate.get_expression_error())
70                .colored()
71                .to_string(),
72        }
73    }
74}
75
76fn compile(
77    mut input: Input,
78    tz_str: Option<String>,
79) -> Result<VrlCompileResult, VrlDiagnosticResult> {
80    let mut functions = vrl::stdlib::all();
81    functions.extend(vector_vrl_functions::all());
82    functions.extend(enrichment::vrl_functions());
83
84    let event = &mut input.event;
85    let state = TypeState::default();
86    let mut runtime = Runtime::default();
87    let config = CompileConfig::default();
88
89    let timezone = match tz_str.as_deref() {
90        // Empty or "Default" tz string will default to tz default
91        None | Some("") | Some("Default") => TimeZone::default(),
92        Some(other) => match other.parse() {
93            Ok(tz) => TimeZone::Named(tz),
94            Err(_) => {
95                // Returns error message if tz parsing has failed.
96                // This avoids head scratching, instead of it silently using the default timezone.
97                let error_message = format!("Invalid timezone identifier: '{other}'");
98                return Err(VrlDiagnosticResult {
99                    list: vec![error_message.clone()],
100                    msg: error_message.clone(),
101                    msg_colorized: error_message,
102                });
103            }
104        },
105    };
106
107    let mut target_value = TargetValue {
108        value: event.clone(),
109        metadata: Value::Object(BTreeMap::new()),
110        secrets: Secrets::new(),
111    };
112
113    let compilation_result = match compile_with_state(&input.program, &functions, &state, config) {
114        Ok(result) => result,
115        Err(diagnostics) => return Err(VrlDiagnosticResult::new(&input.program, diagnostics)),
116    };
117
118    let (result, elapsed_time) =
119        if let Some(performance) = web_sys::window().and_then(|w| w.performance()) {
120            let start_time = performance.now();
121            let result = runtime.resolve(&mut target_value, &compilation_result.program, &timezone);
122            let end_time = performance.now();
123            (result, Some(end_time - start_time))
124        } else {
125            // If performance API is not available, run the program without timing.
126            let result = runtime.resolve(&mut target_value, &compilation_result.program, &timezone);
127            (result, None)
128        };
129
130    match result {
131        Ok(runtime_result) => Ok(VrlCompileResult {
132            runtime_result,                   // This is the value of the last expression.
133            target_value: target_value.value, // The value of the final event
134            elapsed_time,
135        }),
136        Err(err) => Err(VrlDiagnosticResult::new_runtime_error(&input.program, err)),
137    }
138}
139
140// The user-facing function
141#[wasm_bindgen]
142pub fn run_vrl(incoming: &JsValue, tz_str: &str) -> JsValue {
143    let input: Input = incoming.into_serde().unwrap();
144
145    match compile(input, Some(tz_str.to_string())) {
146        Ok(res) => JsValue::from_serde(&res).unwrap(),
147        Err(err) => JsValue::from_serde(&err).unwrap(),
148    }
149}
150
151#[wasm_bindgen]
152pub fn vector_version() -> String {
153    built_info::VECTOR_VERSION.to_string()
154}
155
156#[wasm_bindgen]
157pub fn vector_link() -> String {
158    built_info::VECTOR_LINK.to_string()
159}
160
161#[wasm_bindgen]
162pub fn vrl_version() -> String {
163    built_info::VRL_VERSION.to_string()
164}
165
166#[wasm_bindgen]
167pub fn vrl_link() -> String {
168    built_info::VRL_LINK.to_string()
169}