Skip to main content

hive_console_sdk/expressions/
lib.rs

1use std::collections::BTreeMap;
2use std::sync::LazyLock;
3
4use vrl::{
5    compiler::{compile as vrl_compile, Program as VrlProgram, TargetValue as VrlTargetValue},
6    core::Value as VrlValue,
7    path::OwnedSegment,
8    prelude::{
9        state::RuntimeState as VrlState, Context as VrlContext, Function, TimeZone as VrlTimeZone,
10    },
11    value::Secrets as VrlSecrets,
12};
13
14use crate::expressions::{
15    error::{ExpressionCompileError, ExpressionExecutionError},
16    functions::env::Env,
17};
18
19static VRL_FUNCTIONS: LazyLock<Vec<Box<dyn Function>>> = LazyLock::new(|| {
20    let mut funcs = vrl::stdlib::all();
21    // Our custom functions:
22    funcs.push(Box::new(Env));
23    funcs
24});
25static VRL_TIMEZONE: LazyLock<VrlTimeZone> = LazyLock::new(VrlTimeZone::default);
26
27/// This trait provides a unified way to convert VRL values to specific Rust types.
28pub trait FromVrlValue: Sized {
29    /// Associated error type for this conversion
30    type Error: std::error::Error + Send + Sync + 'static;
31
32    /// Convert a VRL value to this type
33    /// - `value` - The VRL value to convert
34    fn from_vrl_value(value: VrlValue) -> Result<Self, Self::Error>;
35}
36
37/// This trait provides a convenient method to convert sonic_rs Values to VRL Values.
38pub trait ToVrlValue {
39    /// Convert a sonic_rs Value to a VRL Value
40    fn to_vrl_value(&self) -> VrlValue;
41}
42
43/// This trait provides a convenient method to compile expressions directly on string types.
44pub trait CompileExpression {
45    /// Compile a VRL expression string into an executable program
46    /// - `functions` - Optional custom functions; if None, uses standard VRL functions
47    fn compile_expression(
48        &self,
49        functions: Option<&[Box<dyn Function>]>,
50    ) -> Result<VrlProgram, ExpressionCompileError>;
51}
52
53impl CompileExpression for str {
54    fn compile_expression(
55        &self,
56        functions: Option<&[Box<dyn Function>]>,
57    ) -> Result<VrlProgram, ExpressionCompileError> {
58        let functions = functions.unwrap_or(&VRL_FUNCTIONS);
59
60        let compilation_result = vrl_compile(self, functions).map_err(|diagnostics| {
61            ExpressionCompileError::new(
62                self.to_string(),
63                // Format diagnostics into a human-readable string like this:
64                // error[E203]: syntax error
65                //   ┌─ :1:23
66                //   │
67                // 1 │ if (.request.headerss["x-timeout"] == "short") {
68                //   │                       ^^^^^^^^^^^
69                //   │                       │
70                //   │                       unexpected syntax token: "StringLiteral"
71                //   │                       expected one of: "integer literal"
72                //   │
73                //   = see language documentation at https://vrl.dev
74                //   = try your code in the VRL REPL, learn more at https://vrl.dev/examples
75                vrl::diagnostic::Formatter::new(self, diagnostics).to_string(),
76            )
77        })?;
78
79        Ok(compilation_result.program)
80    }
81}
82
83/// Provides a convenient `.execute()` method on VRL `Program` types
84/// that handles all the boilerplate of setting up execution context,
85/// target values, and error handling.
86pub trait ExecutableProgram {
87    fn execute(&self, value: VrlValue) -> Result<VrlValue, ExpressionExecutionError>;
88}
89
90impl ExecutableProgram for VrlProgram {
91    #[inline]
92    fn execute(&self, value: VrlValue) -> Result<VrlValue, ExpressionExecutionError> {
93        let mut target = VrlTargetValue {
94            value,
95            metadata: VrlValue::Object(BTreeMap::new()),
96            secrets: VrlSecrets::default(),
97        };
98
99        let mut state = VrlState::default();
100        let mut ctx = VrlContext::new(&mut target, &mut state, &VRL_TIMEZONE);
101
102        Ok(self.resolve(&mut ctx)?)
103    }
104}
105
106#[derive(Debug, Default, Clone)]
107struct HintNode {
108    is_terminal: bool,
109    children: Vec<(String, HintNode)>,
110}
111
112impl HintNode {
113    fn insert(&mut self, path: &[OwnedSegment]) {
114        if path.is_empty() {
115            self.is_terminal = true;
116            return;
117        }
118
119        let OwnedSegment::Field(ref f) = path[0] else {
120            return; // Ignore index segments
121        };
122        let key = f.as_str();
123
124        let child_idx = if let Some(idx) = self.children.iter().position(|(k, _)| k == key) {
125            idx
126        } else {
127            self.children.push((key.to_string(), HintNode::default()));
128            self.children.len() - 1
129        };
130
131        self.children[child_idx].1.insert(&path[1..]);
132    }
133
134    fn get_child(&self, key: &str) -> Option<&HintNode> {
135        self.children.iter().find(|(k, _)| k == key).map(|(_, v)| v)
136    }
137}
138
139/// This struct analyzes a VRL program to determine which variables are accessed
140/// during execution.
141/// The purpose of this struct is to selectively build context for expressions.
142#[derive(Debug, Default, Clone)]
143pub struct ProgramHints {
144    root: HintNode,
145}
146
147impl ProgramHints {
148    pub fn from_program(program: &VrlProgram) -> Self {
149        let mut root = HintNode::default();
150        for q in &program.info().target_queries {
151            root.insert(&q.path.segments);
152        }
153        Self { root }
154    }
155
156    pub fn context_builder<'a>(
157        &'a self,
158        build_fn: impl FnOnce(&mut VrlObjectBuilder<'a, '_>),
159    ) -> VrlValue {
160        VrlContextBuilder::new(self).build_root(build_fn)
161    }
162}
163
164pub struct VrlContextBuilder<'a> {
165    hints: &'a ProgramHints,
166}
167
168impl<'a> VrlContextBuilder<'a> {
169    fn new(hints: &'a ProgramHints) -> Self {
170        Self { hints }
171    }
172
173    /// Entry point to build the root object
174    fn build_root(&self, build_fn: impl FnOnce(&mut VrlObjectBuilder<'a, '_>)) -> VrlValue {
175        let mut map = BTreeMap::new();
176        let mut obj_builder = VrlObjectBuilder {
177            node: Some(&self.hints.root),
178            force_build: self.hints.root.is_terminal,
179            map: &mut map,
180        };
181        build_fn(&mut obj_builder);
182        VrlValue::Object(map)
183    }
184}
185
186enum ChildState<'a> {
187    Skip,
188    Force,
189    Explore(&'a HintNode),
190}
191
192/// A builder tied to a specific depth in the object tree.
193pub struct VrlObjectBuilder<'a, 'b> {
194    node: Option<&'a HintNode>,
195    force_build: bool,
196    map: &'b mut BTreeMap<vrl::value::KeyString, VrlValue>,
197}
198
199impl<'a, 'b> VrlObjectBuilder<'a, 'b> {
200    /// Inserts a lazy value if the path is requested.
201    pub fn insert_lazy<F>(&mut self, key: &'static str, value_fn: F) -> &mut Self
202    where
203        F: FnOnce() -> VrlValue,
204    {
205        if !matches!(self.evaluate_child(key), ChildState::Skip) {
206            self.map.insert(key.into(), value_fn());
207        }
208        self
209    }
210
211    /// Nests a new object. The inner builder will be skipped entirely if the parent key
212    /// is not requested.
213    pub fn insert_object(
214        &mut self,
215        key: &'static str,
216        build_fn: impl FnOnce(&mut VrlObjectBuilder<'a, '_>),
217    ) -> &mut Self {
218        let child_state = self.evaluate_child(key);
219
220        if matches!(child_state, ChildState::Skip) {
221            return self;
222        }
223
224        let mut inner_map = BTreeMap::new();
225
226        let force_build = matches!(child_state, ChildState::Force);
227        let child_node = match child_state {
228            ChildState::Explore(n) => Some(n),
229            _ => None,
230        };
231
232        let mut sub_builder = VrlObjectBuilder {
233            node: child_node,
234            force_build,
235            map: &mut inner_map,
236        };
237        build_fn(&mut sub_builder);
238
239        // Only insert the object if children were added or it was explicitly requested
240        if !inner_map.is_empty() || force_build {
241            self.map.insert(key.into(), VrlValue::Object(inner_map));
242        }
243        self
244    }
245
246    /// Evaluates a key to determine how its children should be built.
247    #[inline]
248    fn evaluate_child(&self, key: &str) -> ChildState<'a> {
249        if self.force_build {
250            return ChildState::Force;
251        }
252
253        let Some(node) = self.node else {
254            return ChildState::Skip;
255        };
256
257        let Some(child) = node.get_child(key) else {
258            return ChildState::Skip;
259        };
260
261        if child.is_terminal {
262            return ChildState::Force;
263        }
264
265        ChildState::Explore(child)
266    }
267}