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