Skip to main content

tellaro_query_language/parser/
ast.rs

1//! Abstract Syntax Tree (AST) structures for TQL.
2//!
3//! These structures represent the parsed query tree and match the Python implementation's AST.
4
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8/// Top-level AST node types
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
10#[serde(tag = "type", rename_all = "snake_case")]
11pub enum AstNode {
12    /// Match all records (empty query)
13    MatchAll,
14
15    /// Comparison operation (field op value)
16    Comparison(ComparisonNode),
17
18    /// Logical operation (AND/OR)
19    LogicalOp(LogicalOpNode),
20
21    /// Unary operation (NOT)
22    UnaryOp(UnaryOpNode),
23
24    /// Collection operation (ANY/ALL/NONE)
25    CollectionOp(CollectionOpNode),
26
27    /// GeoIP expression
28    GeoExpr(GeoExprNode),
29
30    /// DNS lookup expression
31    NslookupExpr(NslookupExprNode),
32
33    /// Stats expression only
34    StatsExpr(StatsNode),
35
36    /// Query with stats
37    QueryWithStats(QueryWithStatsNode),
38}
39
40/// Comparison node: field operator value
41#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
42pub struct ComparisonNode {
43    /// Field name (may include dots for nested access)
44    pub field: String,
45
46    /// Comparison operator (eq, ne, gt, contains, etc.)
47    pub operator: String,
48
49    /// Expected value (None for exists/not_exists operators)
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub value: Option<Value>,
52
53    /// Field mutators (transformations applied to field)
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub field_mutators: Option<Vec<Mutator>>,
56
57    /// Value mutators (transformations applied to value)
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub value_mutators: Option<Vec<Mutator>>,
60
61    /// Type hint for the field
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub type_hint: Option<String>,
64}
65
66/// Logical operation node: left AND/OR right
67#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
68pub struct LogicalOpNode {
69    /// Logical operator (and, or)
70    pub operator: String,
71
72    /// Left operand
73    pub left: Box<AstNode>,
74
75    /// Right operand
76    pub right: Box<AstNode>,
77}
78
79/// Unary operation node: NOT operand
80#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
81pub struct UnaryOpNode {
82    /// Unary operator (not)
83    pub operator: String,
84
85    /// Operand to negate
86    pub operand: Box<AstNode>,
87}
88
89/// Collection operation node: ANY/ALL/NONE field op value
90#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
91pub struct CollectionOpNode {
92    /// Collection operator (any, all, none, not_any, not_all, not_none)
93    pub operator: String,
94
95    /// Field name (should be an array/list field)
96    pub field: String,
97
98    /// Comparison operator to apply to elements
99    pub comparison_operator: String,
100
101    /// Value to compare against
102    pub value: Value,
103
104    /// Field mutators
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub field_mutators: Option<Vec<Mutator>>,
107
108    /// Type hint
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub type_hint: Option<String>,
111}
112
113/// GeoIP expression node
114#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
115pub struct GeoExprNode {
116    /// Field containing IP address
117    pub field: String,
118
119    /// Field mutators
120    #[serde(skip_serializing_if = "Option::is_none")]
121    pub field_mutators: Option<Vec<Mutator>>,
122
123    /// Type hint
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub type_hint: Option<String>,
126
127    /// Conditions to apply to GeoIP results
128    #[serde(skip_serializing_if = "Option::is_none")]
129    pub conditions: Option<Box<AstNode>>,
130
131    /// GeoIP parameters
132    #[serde(skip_serializing_if = "Option::is_none")]
133    pub geo_params: Option<HashMap<String, Value>>,
134}
135
136/// DNS lookup expression node
137#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
138pub struct NslookupExprNode {
139    /// Field containing hostname
140    pub field: String,
141
142    /// Field mutators
143    #[serde(skip_serializing_if = "Option::is_none")]
144    pub field_mutators: Option<Vec<Mutator>>,
145
146    /// Type hint
147    #[serde(skip_serializing_if = "Option::is_none")]
148    pub type_hint: Option<String>,
149
150    /// Conditions to apply to nslookup results
151    #[serde(skip_serializing_if = "Option::is_none")]
152    pub conditions: Option<Box<AstNode>>,
153
154    /// Nslookup parameters
155    #[serde(skip_serializing_if = "Option::is_none")]
156    pub nslookup_params: Option<HashMap<String, Value>>,
157}
158
159/// Visualization parameter value
160#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
161#[serde(untagged)]
162pub enum VizParamValue {
163    /// String value
164    String(String),
165    /// Numeric integer value
166    Integer(i64),
167    /// Numeric float value
168    Float(f64),
169    /// Boolean value
170    Boolean(bool),
171}
172
173/// Stats expression node
174#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
175pub struct StatsNode {
176    /// Aggregation functions to apply
177    pub aggregations: Vec<Aggregation>,
178
179    /// Fields to group by
180    #[serde(skip_serializing_if = "Vec::is_empty", default)]
181    pub group_by: Vec<GroupBy>,
182
183    /// Visualization hint
184    #[serde(skip_serializing_if = "Option::is_none")]
185    pub viz_hint: Option<String>,
186
187    /// Visualization parameters (e.g., title="Events", stacked=true)
188    #[serde(skip_serializing_if = "Option::is_none")]
189    pub viz_params: Option<HashMap<String, VizParamValue>>,
190}
191
192/// Query with stats node: filter | stats
193#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
194pub struct QueryWithStatsNode {
195    /// Filter expression
196    pub filter: Box<AstNode>,
197
198    /// Stats expression
199    pub stats: StatsNode,
200}
201
202/// Aggregation function specification
203#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
204pub struct Aggregation {
205    /// Aggregation function name (count, sum, avg, etc.)
206    pub function: String,
207
208    /// Field to aggregate (None for count(*))
209    #[serde(skip_serializing_if = "Option::is_none")]
210    pub field: Option<String>,
211
212    /// Alias for the result
213    #[serde(skip_serializing_if = "Option::is_none")]
214    pub alias: Option<String>,
215
216    /// Modifier (top, bottom)
217    #[serde(skip_serializing_if = "Option::is_none")]
218    pub modifier: Option<String>,
219
220    /// Limit for modifier
221    #[serde(skip_serializing_if = "Option::is_none")]
222    pub limit: Option<usize>,
223
224    /// Percentile values (for percentile functions)
225    #[serde(skip_serializing_if = "Option::is_none")]
226    pub percentile_values: Option<Vec<f64>>,
227
228    /// Rank values (for percentile_rank functions)
229    #[serde(skip_serializing_if = "Option::is_none")]
230    pub rank_values: Option<Vec<f64>>,
231
232    /// Field mutators for the aggregation field
233    #[serde(skip_serializing_if = "Option::is_none")]
234    pub field_mutators: Option<Vec<Mutator>>,
235}
236
237/// Group by specification
238#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
239pub struct GroupBy {
240    /// Field to group by
241    pub field: String,
242
243    /// Bucket size (for top N grouping)
244    #[serde(skip_serializing_if = "Option::is_none")]
245    pub bucket_size: Option<usize>,
246}
247
248/// Field/value mutator (transformation function)
249#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
250pub struct Mutator {
251    /// Mutator function name (lowercase, base64_encode, etc.)
252    pub name: String,
253
254    /// Positional arguments to the mutator
255    #[serde(skip_serializing_if = "Vec::is_empty", default)]
256    pub args: Vec<Value>,
257
258    /// Named arguments to the mutator (e.g., find='world', delimiter=',')
259    #[serde(skip_serializing_if = "std::collections::HashMap::is_empty", default)]
260    pub named_args: std::collections::HashMap<String, Value>,
261}
262
263/// Value types
264#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
265#[serde(untagged)]
266pub enum Value {
267    /// String value
268    String(String),
269
270    /// Integer value
271    Integer(i64),
272
273    /// Float value
274    Float(f64),
275
276    /// Boolean value
277    Boolean(bool),
278
279    /// List/array value
280    List(Vec<Value>),
281
282    /// Null value
283    Null,
284}
285
286impl Value {
287    /// Check if value is null
288    pub fn is_null(&self) -> bool {
289        matches!(self, Value::Null)
290    }
291
292    /// Convert to string if possible
293    pub fn as_string(&self) -> Option<&str> {
294        match self {
295            Value::String(s) => Some(s),
296            _ => None,
297        }
298    }
299
300    /// Convert to integer if possible
301    pub fn as_integer(&self) -> Option<i64> {
302        match self {
303            Value::Integer(i) => Some(*i),
304            Value::Float(f) => Some(*f as i64),
305            _ => None,
306        }
307    }
308
309    /// Convert to float if possible
310    pub fn as_float(&self) -> Option<f64> {
311        match self {
312            Value::Float(f) => Some(*f),
313            Value::Integer(i) => Some(*i as f64),
314            _ => None,
315        }
316    }
317
318    /// Convert to boolean if possible
319    pub fn as_bool(&self) -> Option<bool> {
320        match self {
321            Value::Boolean(b) => Some(*b),
322            _ => None,
323        }
324    }
325
326    /// Convert to list if possible
327    pub fn as_list(&self) -> Option<&Vec<Value>> {
328        match self {
329            Value::List(l) => Some(l),
330            _ => None,
331        }
332    }
333}
334
335#[cfg(test)]
336mod tests {
337    use super::*;
338
339    #[test]
340    fn test_value_conversions() {
341        let str_val = Value::String("test".to_string());
342        assert_eq!(str_val.as_string(), Some("test"));
343        assert!(str_val.as_integer().is_none());
344
345        let int_val = Value::Integer(42);
346        assert_eq!(int_val.as_integer(), Some(42));
347        assert_eq!(int_val.as_float(), Some(42.0));
348
349        let null_val = Value::Null;
350        assert!(null_val.is_null());
351    }
352
353    #[test]
354    fn test_serialization() {
355        let node = ComparisonNode {
356            field: "test".to_string(),
357            operator: "eq".to_string(),
358            value: Some(Value::String("value".to_string())),
359            field_mutators: None,
360            value_mutators: None,
361            type_hint: None,
362        };
363
364        let json = serde_json::to_string(&node).unwrap();
365        assert!(json.contains("\"field\":\"test\""));
366    }
367}