1use vector_lib::{TimeZone, compile_vrl, configurable::configurable_component, emit};
2use vrl::{
3 compiler::{
4 CompilationResult, CompileConfig, Program, TypeState, VrlRuntime,
5 runtime::{Runtime, RuntimeResult, Terminate},
6 },
7 value::Value,
8};
9
10use crate::{
11 conditions::{Condition, Conditional, ConditionalConfig},
12 config::LogNamespace,
13 event::{Event, TargetEvents, VrlTarget},
14 format_vrl_diagnostics,
15 internal_events::VrlConditionExecutionError,
16};
17
18#[configurable_component]
20#[derive(Clone, Debug, Default, PartialEq, Eq)]
21pub struct VrlConfig {
22 pub(crate) source: String,
24
25 #[configurable(derived, metadata(docs::hidden))]
26 #[serde(default, skip_serializing_if = "crate::serde::is_default")]
27 pub(crate) runtime: VrlRuntime,
28}
29
30impl_generate_config_from_default!(VrlConfig);
31
32impl ConditionalConfig for VrlConfig {
33 fn build(
34 &self,
35 enrichment_tables: &vector_lib::enrichment::TableRegistry,
36 ) -> crate::Result<Condition> {
37 let functions = vrl::stdlib::all()
48 .into_iter()
49 .chain(vector_lib::enrichment::vrl_functions());
50 #[cfg(feature = "sources-dnstap")]
51 let functions = functions.chain(dnstap_parser::vrl_functions());
52
53 let functions = functions
54 .chain(vector_vrl_functions::all())
55 .collect::<Vec<_>>();
56
57 let state = TypeState::default();
58
59 let mut config = CompileConfig::default();
60 config.set_custom(enrichment_tables.clone());
61 config.set_read_only();
62
63 let CompilationResult {
64 program,
65 warnings,
66 config: _,
67 } = compile_vrl(&self.source, &functions, &state, config)
68 .map_err(|diagnostics| format_vrl_diagnostics(&self.source, diagnostics))?;
69
70 if !program.final_type_info().result.is_boolean() {
71 return Err("VRL conditions must return a boolean.".into());
72 }
73
74 if !warnings.is_empty() {
75 let warnings = format_vrl_diagnostics(&self.source, warnings);
76 warn!(message = "VRL compilation warning.", %warnings);
77 }
78
79 match self.runtime {
80 VrlRuntime::Ast => Ok(Condition::Vrl(Vrl {
81 program,
82 source: self.source.clone(),
83 })),
84 }
85 }
86}
87
88#[derive(Debug, Clone)]
89pub struct Vrl {
90 pub(super) program: Program,
91 pub(super) source: String,
92}
93
94impl Vrl {
95 fn run(&self, event: Event) -> (Event, RuntimeResult) {
96 let log_namespace = event
97 .maybe_as_log()
98 .map(|log| log.namespace())
99 .unwrap_or(LogNamespace::Legacy);
100 let mut target = VrlTarget::new(event, self.program.info(), false);
101 let timezone = TimeZone::default();
103
104 let result = Runtime::default().resolve(&mut target, &self.program, &timezone);
105 let original_event = match target.into_events(log_namespace) {
106 TargetEvents::One(event) => event,
107 _ => panic!("Event was modified in a condition. This is an internal compiler error."),
108 };
109 (original_event, result)
110 }
111}
112
113impl Conditional for Vrl {
114 fn check(&self, event: Event) -> (bool, Event) {
115 let (event, result) = self.run(event);
116
117 let result = result
118 .map(|value| match value {
119 Value::Boolean(boolean) => boolean,
120 _ => panic!("VRL condition did not return a boolean type"),
121 })
122 .unwrap_or_else(|err| {
123 emit!(VrlConditionExecutionError {
124 error: err.to_string().as_ref()
125 });
126 false
127 });
128 (result, event)
129 }
130
131 fn check_with_context(&self, event: Event) -> (Result<(), String>, Event) {
132 let (event, result) = self.run(event);
133
134 let value_result = result.map_err(|err| match err {
135 Terminate::Abort(err) => {
136 let err = format_vrl_diagnostics(
137 &self.source,
138 vrl::diagnostic::Diagnostic::from(
139 Box::new(err) as Box<dyn vrl::diagnostic::DiagnosticMessage>
140 ),
141 );
142 format!("source execution aborted: {err}")
143 }
144 Terminate::Error(err) => {
145 let err = format_vrl_diagnostics(
146 &self.source,
147 vrl::diagnostic::Diagnostic::from(
148 Box::new(err) as Box<dyn vrl::diagnostic::DiagnosticMessage>
149 ),
150 );
151 format!("source execution failed: {err}")
152 }
153 });
154
155 let value = match value_result {
156 Ok(value) => value,
157 Err(err) => {
158 return (Err(err), event);
159 }
160 };
161
162 let result = match value {
163 Value::Boolean(v) if v => Ok(()),
164 Value::Boolean(v) if !v => Err("source execution resolved to false".into()),
165 _ => Err("source execution resolved to a non-boolean value".into()),
166 };
167 (result, event)
168 }
169}
170
171#[cfg(test)]
172mod test {
173 use vector_lib::metric_tags;
174
175 use super::*;
176 use crate::{
177 event::{Metric, MetricKind, MetricValue},
178 log_event,
179 };
180
181 #[test]
182 fn generate_config() {
183 crate::test_util::test_generate_config::<VrlConfig>();
184 }
185
186 #[test]
187 fn check_vrl() {
188 let checks = vec![
189 (
190 log_event![], "true == true", Ok(()), Ok(()), ),
195 (
196 log_event!["foo" => true, "bar" => false],
197 "to_bool(.bar || .foo) ?? false",
198 Ok(()),
199 Ok(()),
200 ),
201 (
202 log_event![],
203 "true == false",
204 Ok(()),
205 Err("source execution resolved to false"),
206 ),
207 (
227 Event::Metric(
228 Metric::new(
229 "zork",
230 MetricKind::Incremental,
231 MetricValue::Counter { value: 1.0 },
232 )
233 .with_namespace(Some("zerk"))
234 .with_tags(Some(metric_tags!("host" => "zoobub"))),
235 ),
236 r#".name == "zork" && .tags.host == "zoobub" && .kind == "incremental""#,
237 Ok(()),
238 Ok(()),
239 ),
240 (
241 log_event![],
242 r#""i_return_a_string""#,
243 Err("VRL conditions must return a boolean.".into()),
244 Ok(()),
245 ),
246 ];
247
248 for (event, source, build, check) in checks {
249 let source = source.to_owned();
250 let config = VrlConfig {
251 source,
252 runtime: Default::default(),
253 };
254
255 assert_eq!(
256 config
257 .build(&Default::default())
258 .map(|_| ())
259 .map_err(|e| e.to_string()),
260 build
261 );
262
263 if let Ok(cond) = config.build(&Default::default()) {
264 assert_eq!(
265 cond.check_with_context(event.clone()).0,
266 check.map_err(|e| e.to_string())
267 );
268 }
269 }
270 }
271}