vrl/compiler/runtime.rs
1use std::{error::Error, fmt};
2
3use crate::path::OwnedTargetPath;
4use crate::value::Value;
5
6use super::ExpressionError;
7use super::TimeZone;
8use super::{Context, Program, Target, state};
9
10#[allow(clippy::module_name_repetitions)]
11pub type RuntimeResult = Result<Value, Terminate>;
12
13#[derive(Debug, Default)]
14pub struct Runtime {
15 state: state::RuntimeState,
16}
17
18/// The error raised if the runtime is terminated.
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub enum Terminate {
21 /// A manual `abort` call.
22 ///
23 /// This is an intentional termination that does not result in an
24 /// `Ok(Value)` result, but should neither be interpreted as an unexpected
25 /// outcome.
26 Abort(ExpressionError),
27
28 /// An unexpected program termination.
29 Error(ExpressionError),
30}
31
32impl Terminate {
33 #[must_use]
34 pub fn get_expression_error(self) -> ExpressionError {
35 match self {
36 Terminate::Error(error) | Terminate::Abort(error) => error,
37 }
38 }
39}
40
41impl fmt::Display for Terminate {
42 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43 match self {
44 Terminate::Error(error) | Terminate::Abort(error) => error.fmt(f),
45 }
46 }
47}
48
49impl Error for Terminate {
50 fn source(&self) -> Option<&(dyn Error + 'static)> {
51 None
52 }
53}
54
55impl Runtime {
56 #[must_use]
57 pub fn new(state: state::RuntimeState) -> Self {
58 Self { state }
59 }
60
61 #[must_use]
62 pub fn is_empty(&self) -> bool {
63 self.state.is_empty()
64 }
65
66 pub fn clear(&mut self) {
67 self.state.clear();
68 }
69
70 /// Resolves the provided [`Program`] to completion using the given [`Target`].
71 ///
72 /// This function ensures that the target contains a valid root object before proceeding.
73 /// If the target is invalid or missing, an error is returned. The resolution process
74 /// is performed using a [`Context`] that maintains execution state and timezone information.
75 ///
76 /// # Arguments
77 ///
78 /// * `target` - A mutable reference to an object implementing the [`Target`] trait. This
79 /// serves as the execution environment for resolving the program.
80 /// * `program` - A reference to the [`Program`] that needs to be resolved.
81 /// * `timezone` - A reference to the [`TimeZone`] used for resolving time-dependent expressions.
82 ///
83 /// # Returns
84 ///
85 /// Returns a [`RuntimeResult`], which is either:
86 /// - `Ok(value)`: The program resolved successfully, producing a value.
87 /// - `Err(Terminate::Error)`: A fatal error occurred during resolution.
88 /// - `Err(Terminate::Abort)`: The resolution was aborted due to a non-fatal expression error.
89 ///
90 /// # Errors
91 ///
92 /// The function may return an error in the following cases:
93 /// - If the target does not contain a valid root object, an error is returned.
94 /// - If the resolution process encounters an [`ExpressionError::Error`].
95 /// - If the program execution results in an [`ExpressionError::Abort`], [`ExpressionError::Fallible`], or [`ExpressionError::Missing`], the function aborts with `Terminate::Abort`.
96 pub fn resolve(
97 &mut self,
98 target: &mut dyn Target,
99 program: &Program,
100 timezone: &TimeZone,
101 ) -> RuntimeResult {
102 // Validate that the path is a value.
103 match target.target_get(&OwnedTargetPath::event_root()) {
104 Ok(Some(_)) => {}
105 Ok(None) => {
106 return Err(Terminate::Error(
107 "expected target object, got nothing".to_owned().into(),
108 ));
109 }
110 Err(err) => {
111 return Err(Terminate::Error(
112 format!("error querying target object: {err}").into(),
113 ));
114 }
115 }
116
117 let mut ctx = Context::new(target, &mut self.state, timezone);
118
119 match program.resolve(&mut ctx) {
120 Ok(value) | Err(ExpressionError::Return { value, .. }) => Ok(value),
121 Err(
122 err @ (ExpressionError::Abort { .. }
123 | ExpressionError::Fallible { .. }
124 | ExpressionError::Missing { .. }),
125 ) => Err(Terminate::Abort(err)),
126 Err(err @ ExpressionError::Error { .. }) => Err(Terminate::Error(err)),
127 }
128 }
129}