smcp_computer/mcp_clients/
vrl_runtime.rs1use thiserror::Error;
11
12#[cfg(feature = "vrl")]
13use {
14 serde_json::Value,
15 vrl::{
16 compiler::{self, runtime::Runtime, TargetValue, TimeZone},
17 stdlib,
18 value::{Secrets, Value as VrlValue},
19 },
20};
21
22#[cfg(feature = "vrl")]
23#[derive(Error, Debug)]
24pub enum VrlError {
25 #[error("VRL compilation error: {0}")]
26 Compilation(String),
27 #[error("VRL runtime error: {0}")]
28 Runtime(String),
29 #[error("Invalid timezone: {0}")]
30 InvalidTimezone(String),
31}
32
33#[cfg(feature = "vrl")]
34#[derive(Debug)]
35pub struct VrlResult {
36 pub processed_event: Value,
37}
38
39#[cfg(feature = "vrl")]
40pub struct VrlRuntime {
41 runtime: Runtime,
42}
43
44#[cfg(feature = "vrl")]
45impl VrlRuntime {
46 pub fn new() -> Self {
48 Self {
49 runtime: Runtime::default(),
50 }
51 }
52
53 pub fn check_syntax(script: &str) -> Result<(), VrlError> {
55 match compiler::compile(script, &stdlib::all()) {
56 Ok(_) => Ok(()),
57 Err(e) => Err(VrlError::Compilation(format!("{:?}", e))),
58 }
59 }
60
61 pub fn run(
63 &mut self,
64 script: &str,
65 event: Value,
66 timezone: &str,
67 ) -> Result<VrlResult, VrlError> {
68 let compilation = compiler::compile(script, &stdlib::all())
70 .map_err(|e| VrlError::Compilation(format!("{:?}", e)))?;
71
72 let vrl_value = self.json_to_vrl_value(event)?;
74
75 let mut target = TargetValue {
77 value: vrl_value,
78 metadata: VrlValue::Object(Default::default()),
79 secrets: Secrets::default(),
80 };
81
82 let tz = if timezone == "UTC" {
84 TimeZone::default()
85 } else {
86 TimeZone::parse(timezone).unwrap_or_default()
88 };
89
90 self.runtime
92 .resolve(&mut target, &compilation.program, &tz)
93 .map_err(|e| VrlError::Runtime(format!("{:?}", e)))?;
94
95 let processed = target.value;
97
98 let processed_event = self.vrl_value_to_json(processed)?;
100
101 Ok(VrlResult { processed_event })
102 }
103
104 fn json_to_vrl_value(&self, value: Value) -> Result<VrlValue, VrlError> {
106 Ok(VrlValue::from(value))
108 }
109
110 fn vrl_value_to_json(&self, value: VrlValue) -> Result<Value, VrlError> {
112 value
114 .try_into()
115 .map_err(|e| VrlError::Runtime(format!("Failed to convert VRL value: {:?}", e)))
116 }
117}
118
119#[cfg(feature = "vrl")]
120impl Default for VrlRuntime {
121 fn default() -> Self {
122 Self::new()
123 }
124}
125
126#[cfg(not(feature = "vrl"))]
128#[derive(Error, Debug)]
129pub enum VrlError {
130 #[error("VRL support is not enabled")]
131 NotEnabled,
132}
133
134#[cfg(not(feature = "vrl"))]
135#[derive(Debug)]
136pub struct VrlResult {
137 pub processed_event: serde_json::Value,
138}
139
140#[cfg(not(feature = "vrl"))]
141pub struct VrlRuntime;
142
143#[cfg(not(feature = "vrl"))]
144impl VrlRuntime {
145 pub fn new() -> Self {
146 Self
147 }
148
149 pub fn check_syntax(_script: &str) -> Result<(), VrlError> {
150 Err(VrlError::NotEnabled)
151 }
152
153 pub fn run(
154 &self,
155 _script: &str,
156 event: serde_json::Value,
157 _timezone: &str,
158 ) -> Result<VrlResult, VrlError> {
159 Ok(VrlResult {
161 processed_event: event,
162 })
163 }
164}
165
166#[cfg(not(feature = "vrl"))]
167impl Default for VrlRuntime {
168 fn default() -> Self {
169 Self
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176 use serde_json::json;
177
178 #[cfg(feature = "vrl")]
179 #[test]
180 fn test_vrl_syntax_check_valid_scripts() {
181 let valid_cases = vec![
183 (".field = 1", "simple assignment"),
184 (".field = \"value\"", "string assignment"),
185 (".field = true", "boolean assignment"),
186 (".field = .existing", "field reference"),
187 ];
188
189 for (script, description) in valid_cases {
190 assert!(
191 VrlRuntime::check_syntax(script).is_ok(),
192 "Script should be valid: {} - {}",
193 script,
194 description
195 );
196 }
197 }
198
199 #[cfg(feature = "vrl")]
200 #[test]
201 fn test_vrl_syntax_check_invalid_scripts() {
202 let invalid_cases = vec![
204 (".field =", "incomplete assignment"),
205 ("= 1", "missing target"),
206 (".field .", "invalid syntax"),
207 (".field = now(", "unclosed function"),
208 (".field = . +", "incomplete expression"),
209 ("if .field", "incomplete if statement"),
210 ];
211
212 for (script, description) in invalid_cases {
213 assert!(
214 VrlRuntime::check_syntax(script).is_err(),
215 "Script should be invalid: {} - {}",
216 script,
217 description
218 );
219 }
220 }
221
222 #[cfg(feature = "vrl")]
223 #[test]
224 fn test_vrl_runtime_basic() {
225 let mut runtime = VrlRuntime::new();
226
227 let script = r#"
228 .processed = true
229 .status = "ok"
230 "#;
231
232 let event = json!({
233 "original": "value"
234 });
235
236 let result = runtime.run(script, event, "UTC").unwrap();
237
238 assert_eq!(result.processed_event["original"], "value");
240 }
241
242 #[cfg(feature = "vrl")]
243 #[test]
244 fn test_vrl_runtime_complex_event() {
245 let mut runtime = VrlRuntime::new();
246
247 let script = r#"
248 .metadata.transformed = true
249 .count = 3
250 "#;
251
252 let event = json!({
253 "items": [1, 2, 3],
254 "nested": {
255 "value": 42
256 }
257 });
258
259 let result = runtime.run(script, event, "UTC").unwrap();
260
261 assert_eq!(result.processed_event["items"].as_array().unwrap().len(), 3);
263 assert_eq!(result.processed_event["nested"]["value"], 42);
264 }
265
266 #[cfg(feature = "vrl")]
267 #[test]
268 fn test_vrl_runtime_error_handling() {
269 let mut runtime = VrlRuntime::new();
270
271 let script = ".field =";
273 let event = json!({"test": "value"});
274
275 assert!(runtime.run(script, event, "UTC").is_err());
276 }
277
278 #[cfg(feature = "vrl")]
279 #[test]
280 fn test_json_to_vrl_value_conversion() {
281 let runtime = VrlRuntime::new();
282
283 let test_cases = vec![
285 (json!(null), "null"),
286 (json!(true), "boolean"),
287 (json!(42), "number"),
288 (json!("string"), "string"),
289 (json!([1, 2, 3]), "array"),
290 (json!({"key": "value"}), "object"),
291 ];
292
293 for (value, description) in test_cases {
294 let vrl_value = runtime.json_to_vrl_value(value.clone()).unwrap();
295 let json_back = runtime.vrl_value_to_json(vrl_value).unwrap();
297 assert_eq!(value, json_back, "Conversion failed for: {}", description);
298 }
299 }
300
301 #[test]
302 fn test_vrl_disabled_feature() {
303 #[cfg(not(feature = "vrl"))]
305 assert!(matches!(
306 VrlRuntime::check_syntax(".field = 1"),
307 Err(VrlError::NotEnabled)
308 ));
309
310 #[cfg(feature = "vrl")]
311 {
312 let mut runtime = VrlRuntime::new();
313 let event = json!({"test": "value"});
314 let result = runtime.run(".field = 1", event.clone(), "UTC").unwrap();
315 assert_eq!(result.processed_event["test"], "value");
317 assert_eq!(result.processed_event["field"], 1);
318 }
319 }
320
321 #[cfg(feature = "vrl")]
322 #[test]
323 fn test_vrl_error_display() {
324 let compilation_error = VrlError::Compilation("test error".to_string());
325 assert_eq!(
326 compilation_error.to_string(),
327 "VRL compilation error: test error"
328 );
329
330 let runtime_error = VrlError::Runtime("test runtime error".to_string());
331 assert_eq!(
332 runtime_error.to_string(),
333 "VRL runtime error: test runtime error"
334 );
335
336 let timezone_error = VrlError::InvalidTimezone("UTC+25".to_string());
337 assert_eq!(timezone_error.to_string(), "Invalid timezone: UTC+25");
338 }
339
340 #[cfg(feature = "vrl")]
341 #[test]
342 fn test_vrl_result_debug() {
343 let result = VrlResult {
344 processed_event: json!({"test": "value"}),
345 };
346
347 let debug_str = format!("{:?}", result);
348 assert!(debug_str.contains("VrlResult"));
349 assert!(debug_str.contains("processed_event"));
350 }
351
352 #[cfg(feature = "vrl")]
353 #[test]
354 fn test_vrl_runtime_default() {
355 assert!(VrlRuntime::check_syntax(".field = 1").is_ok());
356 }
357
358 #[cfg(feature = "vrl")]
359 #[tokio::test]
360 async fn test_vrl_async_context() {
361 let mut runtime = VrlRuntime::new();
363
364 let script = ".async_test = true";
365 let event = json!({"original": "value"});
366
367 let result = tokio::task::spawn_blocking(move || runtime.run(script, event, "UTC"))
368 .await
369 .unwrap()
370 .unwrap();
371
372 assert_eq!(result.processed_event["original"], "value");
373 }
374}