use std::collections::BTreeMap;
use std::sync::LazyLock;
use vrl::{
compiler::{compile as vrl_compile, Program as VrlProgram, TargetValue as VrlTargetValue},
core::Value as VrlValue,
path::OwnedSegment,
prelude::{
state::RuntimeState as VrlState, Context as VrlContext, Function, TimeZone as VrlTimeZone,
},
value::Secrets as VrlSecrets,
};
use crate::expressions::{
error::{ExpressionCompileError, ExpressionExecutionError},
functions::env::Env,
};
static VRL_FUNCTIONS: LazyLock<Vec<Box<dyn Function>>> = LazyLock::new(|| {
let mut funcs = vrl::stdlib::all();
funcs.push(Box::new(Env));
funcs
});
static VRL_TIMEZONE: LazyLock<VrlTimeZone> = LazyLock::new(VrlTimeZone::default);
pub trait FromVrlValue: Sized {
type Error: std::error::Error + Send + Sync + 'static;
fn from_vrl_value(value: VrlValue) -> Result<Self, Self::Error>;
}
pub trait ToVrlValue {
fn to_vrl_value(&self) -> VrlValue;
}
pub trait CompileExpression {
fn compile_expression(
&self,
functions: Option<&[Box<dyn Function>]>,
) -> Result<VrlProgram, ExpressionCompileError>;
}
impl CompileExpression for str {
fn compile_expression(
&self,
functions: Option<&[Box<dyn Function>]>,
) -> Result<VrlProgram, ExpressionCompileError> {
let functions = functions.unwrap_or(&VRL_FUNCTIONS);
let compilation_result = vrl_compile(self, functions).map_err(|diagnostics| {
ExpressionCompileError::new(
self.to_string(),
vrl::diagnostic::Formatter::new(self, diagnostics).to_string(),
)
})?;
Ok(compilation_result.program)
}
}
pub trait ExecutableProgram {
fn execute(&self, value: VrlValue) -> Result<VrlValue, ExpressionExecutionError>;
}
impl ExecutableProgram for VrlProgram {
#[inline]
fn execute(&self, value: VrlValue) -> Result<VrlValue, ExpressionExecutionError> {
let mut target = VrlTargetValue {
value,
metadata: VrlValue::Object(BTreeMap::new()),
secrets: VrlSecrets::default(),
};
let mut state = VrlState::default();
let mut ctx = VrlContext::new(&mut target, &mut state, &VRL_TIMEZONE);
Ok(self.resolve(&mut ctx)?)
}
}
#[derive(Debug, Default, Clone)]
struct HintNode {
is_terminal: bool,
children: Vec<(String, HintNode)>,
}
impl HintNode {
fn insert(&mut self, path: &[OwnedSegment]) {
if path.is_empty() {
self.is_terminal = true;
return;
}
let OwnedSegment::Field(ref f) = path[0] else {
return; };
let key = f.as_str();
let child_idx = if let Some(idx) = self.children.iter().position(|(k, _)| k == key) {
idx
} else {
self.children.push((key.to_string(), HintNode::default()));
self.children.len() - 1
};
self.children[child_idx].1.insert(&path[1..]);
}
fn get_child(&self, key: &str) -> Option<&HintNode> {
self.children.iter().find(|(k, _)| k == key).map(|(_, v)| v)
}
}
#[derive(Debug, Default, Clone)]
pub struct ProgramHints {
root: HintNode,
}
impl ProgramHints {
pub fn from_program(program: &VrlProgram) -> Self {
let mut root = HintNode::default();
for q in &program.info().target_queries {
root.insert(&q.path.segments);
}
Self { root }
}
pub fn context_builder<'a>(
&'a self,
build_fn: impl FnOnce(&mut VrlObjectBuilder<'a, '_>),
) -> VrlValue {
VrlContextBuilder::new(self).build_root(build_fn)
}
}
pub struct VrlContextBuilder<'a> {
hints: &'a ProgramHints,
}
impl<'a> VrlContextBuilder<'a> {
fn new(hints: &'a ProgramHints) -> Self {
Self { hints }
}
fn build_root(&self, build_fn: impl FnOnce(&mut VrlObjectBuilder<'a, '_>)) -> VrlValue {
let mut map = BTreeMap::new();
let mut obj_builder = VrlObjectBuilder {
node: Some(&self.hints.root),
force_build: self.hints.root.is_terminal,
map: &mut map,
};
build_fn(&mut obj_builder);
VrlValue::Object(map)
}
}
enum ChildState<'a> {
Skip,
Force,
Explore(&'a HintNode),
}
pub struct VrlObjectBuilder<'a, 'b> {
node: Option<&'a HintNode>,
force_build: bool,
map: &'b mut BTreeMap<vrl::value::KeyString, VrlValue>,
}
impl<'a, 'b> VrlObjectBuilder<'a, 'b> {
pub fn insert_lazy<F>(&mut self, key: &'static str, value_fn: F) -> &mut Self
where
F: FnOnce() -> VrlValue,
{
if !matches!(self.evaluate_child(key), ChildState::Skip) {
self.map.insert(key.into(), value_fn());
}
self
}
pub fn insert_object(
&mut self,
key: &'static str,
build_fn: impl FnOnce(&mut VrlObjectBuilder<'a, '_>),
) -> &mut Self {
let child_state = self.evaluate_child(key);
if matches!(child_state, ChildState::Skip) {
return self;
}
let mut inner_map = BTreeMap::new();
let force_build = matches!(child_state, ChildState::Force);
let child_node = match child_state {
ChildState::Explore(n) => Some(n),
_ => None,
};
let mut sub_builder = VrlObjectBuilder {
node: child_node,
force_build,
map: &mut inner_map,
};
build_fn(&mut sub_builder);
if !inner_map.is_empty() || force_build {
self.map.insert(key.into(), VrlValue::Object(inner_map));
}
self
}
#[inline]
fn evaluate_child(&self, key: &str) -> ChildState<'a> {
if self.force_build {
return ChildState::Force;
}
let Some(node) = self.node else {
return ChildState::Skip;
};
let Some(child) = node.get_child(key) else {
return ChildState::Skip;
};
if child.is_terminal {
return ChildState::Force;
}
ChildState::Explore(child)
}
}