ottl/mod.rs
1/// OTTL Parser Library
2///
3/// This library provides a Rust implementation of the OpenTelemetry Transformation Language (OTTL)
4/// parser with callback binding at parse time.
5///
6/// # Example
7///
8/// <!-- vale off -->
9/// ```ignore
10/// use ottl::{Parser, OttlParser, CallbackMap, EnumMap, PathResolverMap, EvalContextFamily};
11///
12/// struct MyFamily;
13/// impl EvalContextFamily for MyFamily {
14/// type Context<'a> = MyContext<'a>;
15/// }
16///
17/// let editors = ottl::editors::standard();
18/// let converters = CallbackMap::new();
19/// let enums = EnumMap::new();
20/// let path_resolvers = PathResolverMap::<MyFamily>::new();
21///
22/// let parser = Parser::<MyFamily>::new(&editors, &converters, &enums, &path_resolvers, "set(my.attr, 1) where 1 > 0");
23/// let mut ctx = MyContext { /* ... */ };
24/// let result = parser.execute(&mut ctx);
25/// ```
26/// <!-- vale on -->
27use std::collections::HashMap;
28use std::error::Error;
29use std::fmt;
30use std::sync::Arc;
31
32pub mod editors;
33pub mod helpers;
34pub(crate) mod lexer;
35mod parser;
36
37#[cfg(test)]
38mod tests;
39
40// Re-export from submodules
41pub use parser::Field;
42pub use parser::IndexExpr;
43pub use parser::Parser;
44
45// =====================================================================================================================
46// Error and Result Types
47// =====================================================================================================================
48
49/// Standard error type for the library
50pub type BoxError = Box<dyn Error + Send + Sync>;
51
52/// Standard result type for the library
53pub type Result<T> = std::result::Result<T, BoxError>;
54
55// =====================================================================================================================
56// Context Types
57// =====================================================================================================================
58
59/// A "family" of evaluation context types, parameterized by lifetime.
60///
61/// This trait uses Generic Associated Types (GATs) to separate the context family
62/// (known at parse time, carries no lifetime) from the concrete context type
63/// (instantiated with a lifetime at execution time).
64///
65/// # Why a family?
66///
67/// The parser is long-lived and created once, but contexts are short-lived and often
68/// borrow data. We can't write `Parser<MyContext<'a>>` because `'a` doesn't exist
69/// when the parser is created. Instead, the parser stores the *family* `F`, and
70/// `F::Context<'a>` is only materialized when `execute` is called.
71///
72/// # Example
73///
74/// <!-- vale off -->
75/// ```ignore
76/// struct SpanFamily;
77///
78/// impl EvalContextFamily for SpanFamily {
79/// type Context<'a> = SpanContext<'a>;
80/// }
81///
82/// struct SpanContext<'a> {
83/// span: &'a mut Span,
84/// resource: &'a Resource,
85/// }
86/// ```
87/// <!-- vale on -->
88pub trait EvalContextFamily: 'static {
89 /// The concrete context type for a given borrow lifetime.
90 type Context<'a>;
91}
92
93// =====================================================================================================================
94// Value Types
95// =====================================================================================================================
96
97/// Represents all possible values in OTTL expressions and function arguments.
98/// Uses `Arc<str>` for strings and `Arc<[u8]>` for bytes to enable cheap cloning.
99#[derive(Clone, Default, Debug, PartialEq /* , Eq, PartialOrd - not applicable it seems */)]
100pub enum Value {
101 /// Nil/null value
102 #[default]
103 Nil,
104 /// Boolean value (true/false)
105 Bool(bool),
106 /// 64-bit signed integer
107 Int(i64),
108 /// 64-bit floating point
109 Float(f64),
110 /// String value (Arc for cheap clone)
111 String(Arc<str>),
112 /// Bytes literal (for example, 0xC0FFEE) - Arc for cheap clone
113 Bytes(Arc<[u8]>),
114 /// List of values
115 List(Vec<Value>),
116 /// Map of string keys to values
117 Map(HashMap<String, Value>),
118}
119
120///Static methods of Value
121impl Value {
122 ///Static method: create a string value from any string-like type
123 #[inline]
124 pub fn string(s: impl Into<Arc<str>>) -> Self {
125 Value::String(s.into())
126 }
127
128 ///Static method: create a bytes value from any bytes-like type
129 #[inline]
130 pub fn bytes(b: impl Into<Arc<[u8]>>) -> Self {
131 Value::Bytes(b.into())
132 }
133}
134
135// =====================================================================================================================
136// Argument Types
137// =====================================================================================================================
138
139/// Argument passed to callback functions.
140/// Can be either a positional argument or a named argument.
141#[derive(Debug, Clone)]
142pub enum Argument {
143 /// Positional argument with just a value
144 Positional(Value),
145 /// Named argument with name and value
146 Named { name: String, value: Value },
147}
148
149// =====================================================================================================================
150// Path Accessor Types
151// =====================================================================================================================
152
153/// Trait for accessing (reading and writing) path values in the context.
154///
155/// The evaluator calls [`get`](PathAccessor::get) and [`set`](PathAccessor::set) with a slice
156/// of [`Field`]s representing the structured path. Each field carries its own name and index
157/// keys; path interpretation is the integrator's responsibility.
158///
159/// The `fields` slice is pre-allocated at parse time and passed by reference at execution time,
160/// so the hot path involves **zero allocation and zero copy**.
161///
162/// The type parameter `F` is the [`EvalContextFamily`] that determines the concrete context type.
163/// The lifetime on `F::Context<'a>` is introduced per method call, so `PathAccessor<F>` itself
164/// carries no lifetime and can be stored in `Arc<dyn PathAccessor<F>>`.
165pub trait PathAccessor<F: EvalContextFamily>: fmt::Debug + Send + Sync {
166 /// Get the value at this path.
167 ///
168 /// `fields` contains the structured path segments with per-field index keys.
169 /// Path interpretation, including indexing (for example, `["key"]`, `[0]`), is **not** implemented by
170 /// OTTL; the integrator must implement this method. For applying index keys to a value, use
171 /// [`crate::helpers::apply_indexes`].
172 fn get<'a>(&self, ctx: &F::Context<'a>, fields: &[Field]) -> Result<Value>;
173
174 /// Set the value at this path.
175 ///
176 /// `fields` contains the structured path segments with per-field index keys.
177 /// The integrator decides how to interpret the field chain and which keys to honour.
178 fn set<'a>(&self, ctx: &mut F::Context<'a>, fields: &[Field], value: &Value) -> Result<()>;
179}
180
181/// Type alias for the path resolver function.
182/// Returns a PathAccessor for the given context family.
183pub type PathResolver<F> = Arc<dyn Fn() -> Result<Arc<dyn PathAccessor<F>>> + Send + Sync>;
184
185/// Map from path string to its resolver. Parser looks up each path in the expression
186/// in this map; if a path is missing, parsing fails with an error.
187pub type PathResolverMap<F> = HashMap<String, PathResolver<F>>;
188
189// =====================================================================================================================
190// Callback Types
191// =====================================================================================================================
192
193/// Trait for lazy argument evaluation - ZERO ALLOCATION at runtime.
194/// Arguments are evaluated only when requested by the callback.
195pub trait Args {
196 /// Number of arguments
197 fn len(&self) -> usize;
198
199 /// Check if empty
200 fn is_empty(&self) -> bool {
201 self.len() == 0
202 }
203
204 /// Get argument value by index (lazy evaluation - NO ALLOCATION)
205 fn get(&mut self, index: usize) -> Result<Value>;
206
207 /// Get argument name by index (for named arguments)
208 fn name(&self, index: usize) -> Option<&str>;
209
210 /// Get named argument value (searches by name, lazy evaluation)
211 fn get_named(&mut self, name: &str) -> Option<Result<Value>> {
212 for i in 0..self.len() {
213 if self.name(i) == Some(name) {
214 return Some(self.get(i));
215 }
216 }
217 None
218 }
219
220 /// Set value at argument path by index.
221 /// The argument at `index` must be a path expression.
222 /// This calls PathAccessor::set on the resolved path.
223 fn set(&mut self, index: usize, value: &Value) -> Result<()>;
224}
225
226/// Callback function type for editors and converters.
227/// Uses lazy Args trait for ZERO-ALLOCATION argument evaluation.
228pub type CallbackFn = Arc<dyn Fn(&mut dyn Args) -> Result<Value> + Send + Sync>;
229
230/// Map of function names to their callback implementations.
231pub type CallbackMap = HashMap<String, CallbackFn>;
232
233/// Map of enum names to their integer values.
234pub type EnumMap = HashMap<String, i64>;
235
236// =====================================================================================================================
237// Parser API Trait
238// =====================================================================================================================
239
240/// Public API trait for the OTTL Parser.
241///
242/// This trait defines the interface for executing parsed OTTL statements.
243/// The type parameter `F` is the [`EvalContextFamily`] that determines the
244/// concrete evaluation context type.
245///
246/// # Example
247///
248/// <!-- vale off -->
249/// ```ignore
250/// use ottl::{OttlParser, Parser, EvalContextFamily};
251///
252/// let parser = Parser::<MyFamily>::new(...);
253///
254/// // Check for parsing errors
255/// parser.is_error()?;
256///
257/// // Execute the statement
258/// let mut ctx = MyContext { /* ... */ };
259/// let result = parser.execute(&mut ctx)?;
260/// ```
261/// <!-- vale on -->
262pub trait OttlParser<F: EvalContextFamily> {
263 /// Checks if the parser encountered any errors during creation.
264 ///
265 /// Call this method after creating a parser to verify that the OTTL expression
266 /// was parsed successfully.
267 ///
268 /// # Returns
269 /// * `Ok(())` - if no errors occurred during parsing
270 /// * `Err(BoxError)` - if parsing failed with error details
271 fn is_error(&self) -> Result<()>;
272
273 /// Executes this OTTL statement with the given context.
274 ///
275 /// This method evaluates the parsed OTTL expression, invoking any bound
276 /// editor or converter callbacks as needed and resolving path references.
277 ///
278 /// # Arguments
279 /// * `ctx` - The mutable evaluation context that provides access to telemetry data
280 /// and can be modified by editor functions.
281 ///
282 /// # Returns
283 /// * `Ok(Value)` - The result of evaluating the expression. If expression has no
284 /// return value, returns `Value::Nil`.
285 /// * `Err(BoxError)` - An error if evaluation fails (for example, type mismatch,
286 /// missing path, callback error).
287 fn execute<'a>(&self, ctx: &mut F::Context<'a>) -> Result<Value>;
288}