Skip to main content

otelite_core/
query.rs

1//! Query parser for structured filter expressions
2//!
3//! Supports simple query syntax:
4//! - `severity = ERROR`
5//! - `duration > 500ms`
6//! - `gen_ai.system = "anthropic"`
7//! - `name contains "chat"`
8
9use std::fmt;
10
11/// Comparison operator for query predicates
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum Operator {
14    /// Equality (=)
15    Equal,
16    /// Inequality (!=)
17    NotEqual,
18    /// Greater than (>)
19    GreaterThan,
20    /// Less than (<)
21    LessThan,
22    /// Greater than or equal (>=)
23    GreaterThanOrEqual,
24    /// Less than or equal (<=)
25    LessThanOrEqual,
26    /// Contains substring
27    Contains,
28}
29
30impl fmt::Display for Operator {
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        match self {
33            Operator::Equal => write!(f, "="),
34            Operator::NotEqual => write!(f, "!="),
35            Operator::GreaterThan => write!(f, ">"),
36            Operator::LessThan => write!(f, "<"),
37            Operator::GreaterThanOrEqual => write!(f, ">="),
38            Operator::LessThanOrEqual => write!(f, "<="),
39            Operator::Contains => write!(f, "contains"),
40        }
41    }
42}
43
44/// Value type for query predicates
45#[derive(Debug, Clone, PartialEq)]
46pub enum QueryValue {
47    /// String value
48    String(String),
49    /// Numeric value
50    Number(f64),
51    /// Duration in milliseconds
52    Duration(u64),
53}
54
55impl fmt::Display for QueryValue {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        match self {
58            QueryValue::String(s) => write!(f, "\"{}\"", s),
59            QueryValue::Number(n) => write!(f, "{}", n),
60            QueryValue::Duration(d) => write!(f, "{}ms", d),
61        }
62    }
63}
64
65/// A single query predicate (field operator value)
66#[derive(Debug, Clone, PartialEq)]
67pub struct QueryPredicate {
68    /// Field name (e.g., "severity", "gen_ai.system")
69    pub field: String,
70    /// Comparison operator
71    pub operator: Operator,
72    /// Value to compare against
73    pub value: QueryValue,
74}
75
76impl fmt::Display for QueryPredicate {
77    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78        write!(f, "{} {} {}", self.field, self.operator, self.value)
79    }
80}
81
82/// Error type for query parsing
83#[derive(Debug, Clone, PartialEq, Eq)]
84pub enum QueryError {
85    /// Empty query string
86    EmptyQuery,
87    /// Invalid syntax
88    InvalidSyntax(String),
89    /// Unknown operator
90    UnknownOperator(String),
91    /// Invalid value format
92    InvalidValue(String),
93}
94
95impl fmt::Display for QueryError {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        match self {
98            QueryError::EmptyQuery => write!(f, "Query string is empty"),
99            QueryError::InvalidSyntax(msg) => write!(f, "Invalid syntax: {}", msg),
100            QueryError::UnknownOperator(op) => write!(f, "Unknown operator: {}", op),
101            QueryError::InvalidValue(msg) => write!(f, "Invalid value: {}", msg),
102        }
103    }
104}
105
106impl std::error::Error for QueryError {}
107
108/// Parse a query string into a list of predicates
109///
110/// # Examples
111///
112/// ```
113/// use otelite_core::query::{parse_query, Operator, QueryValue};
114///
115/// let predicates = parse_query("severity = \"ERROR\"").unwrap();
116/// assert_eq!(predicates.len(), 1);
117/// assert_eq!(predicates[0].field, "severity");
118/// assert_eq!(predicates[0].operator, Operator::Equal);
119/// ```
120pub fn parse_query(input: &str) -> Result<Vec<QueryPredicate>, QueryError> {
121    let input = input.trim();
122    if input.is_empty() {
123        return Err(QueryError::EmptyQuery);
124    }
125
126    let mut predicates = Vec::new();
127
128    // Split by AND (case-insensitive) for multiple predicates
129    let parts: Vec<&str> = input.split(" AND ").collect();
130
131    for part in parts {
132        let predicate = parse_single_predicate(part.trim())?;
133        predicates.push(predicate);
134    }
135
136    Ok(predicates)
137}
138
139fn parse_single_predicate(input: &str) -> Result<QueryPredicate, QueryError> {
140    // Try to find operator
141    let (field, operator, value_str) = if let Some(pos) = input.find(" contains ") {
142        let field = input[..pos].trim();
143        let value = input[pos + 10..].trim();
144        (field, Operator::Contains, value)
145    } else if let Some(pos) = input.find(" >= ") {
146        let field = input[..pos].trim();
147        let value = input[pos + 4..].trim();
148        (field, Operator::GreaterThanOrEqual, value)
149    } else if let Some(pos) = input.find(" <= ") {
150        let field = input[..pos].trim();
151        let value = input[pos + 4..].trim();
152        (field, Operator::LessThanOrEqual, value)
153    } else if let Some(pos) = input.find(" != ") {
154        let field = input[..pos].trim();
155        let value = input[pos + 4..].trim();
156        (field, Operator::NotEqual, value)
157    } else if let Some(pos) = input.find(" > ") {
158        let field = input[..pos].trim();
159        let value = input[pos + 3..].trim();
160        (field, Operator::GreaterThan, value)
161    } else if let Some(pos) = input.find(" < ") {
162        let field = input[..pos].trim();
163        let value = input[pos + 3..].trim();
164        (field, Operator::LessThan, value)
165    } else if let Some(pos) = input.find(" = ") {
166        let field = input[..pos].trim();
167        let value = input[pos + 3..].trim();
168        (field, Operator::Equal, value)
169    } else {
170        return Err(QueryError::InvalidSyntax(
171            "No valid operator found. Expected: =, !=, >, <, >=, <=, contains".to_string(),
172        ));
173    };
174
175    if field.is_empty() {
176        return Err(QueryError::InvalidSyntax("Field name is empty".to_string()));
177    }
178
179    if value_str.is_empty() {
180        return Err(QueryError::InvalidValue("Value is empty".to_string()));
181    }
182
183    let value = parse_value(value_str)?;
184
185    Ok(QueryPredicate {
186        field: field.to_string(),
187        operator,
188        value,
189    })
190}
191
192fn parse_value(input: &str) -> Result<QueryValue, QueryError> {
193    let input = input.trim();
194
195    if input.is_empty() {
196        return Err(QueryError::InvalidValue("Value is empty".to_string()));
197    }
198
199    // Check for quoted string
200    if (input.starts_with('"') && input.ends_with('"'))
201        || (input.starts_with('\'') && input.ends_with('\''))
202    {
203        if input.len() < 2 {
204            return Err(QueryError::InvalidValue(
205                "Quoted string is too short".to_string(),
206            ));
207        }
208        return Ok(QueryValue::String(input[1..input.len() - 1].to_string()));
209    }
210
211    // Check for duration (e.g., "500ms", "1s")
212    if let Some(num_part) = input.strip_suffix("ms") {
213        match num_part.parse::<u64>() {
214            Ok(n) => return Ok(QueryValue::Duration(n)),
215            Err(_) => {
216                return Err(QueryError::InvalidValue(format!(
217                    "Invalid duration format: {}",
218                    input
219                )))
220            },
221        }
222    }
223
224    if let Some(num_part) = input.strip_suffix('s') {
225        match num_part.parse::<u64>() {
226            Ok(n) => return Ok(QueryValue::Duration(n * 1000)),
227            Err(_) => {
228                return Err(QueryError::InvalidValue(format!(
229                    "Invalid duration format: {}",
230                    input
231                )))
232            },
233        }
234    }
235
236    // Try to parse as number
237    if let Ok(n) = input.parse::<f64>() {
238        return Ok(QueryValue::Number(n));
239    }
240
241    // Reject unquoted strings - require explicit quoting for string values
242    Err(QueryError::InvalidValue(format!(
243        "String values must be quoted: {}",
244        input
245    )))
246}
247
248#[cfg(test)]
249mod tests {
250    use super::*;
251
252    #[test]
253    fn test_parse_simple_equality() {
254        let result = parse_query("severity = \"ERROR\"").unwrap();
255        assert_eq!(result.len(), 1);
256        assert_eq!(result[0].field, "severity");
257        assert_eq!(result[0].operator, Operator::Equal);
258        assert_eq!(result[0].value, QueryValue::String("ERROR".to_string()));
259    }
260
261    #[test]
262    fn test_parse_quoted_string() {
263        let result = parse_query("name = \"my service\"").unwrap();
264        assert_eq!(result.len(), 1);
265        assert_eq!(result[0].field, "name");
266        assert_eq!(result[0].operator, Operator::Equal);
267        assert_eq!(
268            result[0].value,
269            QueryValue::String("my service".to_string())
270        );
271    }
272
273    #[test]
274    fn test_parse_duration_ms() {
275        let result = parse_query("duration > 500ms").unwrap();
276        assert_eq!(result.len(), 1);
277        assert_eq!(result[0].field, "duration");
278        assert_eq!(result[0].operator, Operator::GreaterThan);
279        assert_eq!(result[0].value, QueryValue::Duration(500));
280    }
281
282    #[test]
283    fn test_parse_duration_seconds() {
284        let result = parse_query("duration < 2s").unwrap();
285        assert_eq!(result.len(), 1);
286        assert_eq!(result[0].field, "duration");
287        assert_eq!(result[0].operator, Operator::LessThan);
288        assert_eq!(result[0].value, QueryValue::Duration(2000));
289    }
290
291    #[test]
292    fn test_parse_numeric_value() {
293        let result = parse_query("count >= 100").unwrap();
294        assert_eq!(result.len(), 1);
295        assert_eq!(result[0].field, "count");
296        assert_eq!(result[0].operator, Operator::GreaterThanOrEqual);
297        assert_eq!(result[0].value, QueryValue::Number(100.0));
298    }
299
300    #[test]
301    fn test_parse_contains_operator() {
302        let result = parse_query("name contains \"chat\"").unwrap();
303        assert_eq!(result.len(), 1);
304        assert_eq!(result[0].field, "name");
305        assert_eq!(result[0].operator, Operator::Contains);
306        assert_eq!(result[0].value, QueryValue::String("chat".to_string()));
307    }
308
309    #[test]
310    fn test_parse_dotted_field_name() {
311        let result = parse_query("gen_ai.system = \"anthropic\"").unwrap();
312        assert_eq!(result.len(), 1);
313        assert_eq!(result[0].field, "gen_ai.system");
314        assert_eq!(result[0].operator, Operator::Equal);
315        assert_eq!(result[0].value, QueryValue::String("anthropic".to_string()));
316    }
317
318    #[test]
319    fn test_parse_not_equal() {
320        let result = parse_query("status != 200").unwrap();
321        assert_eq!(result.len(), 1);
322        assert_eq!(result[0].field, "status");
323        assert_eq!(result[0].operator, Operator::NotEqual);
324        assert_eq!(result[0].value, QueryValue::Number(200.0));
325    }
326
327    #[test]
328    fn test_parse_less_than_or_equal() {
329        let result = parse_query("latency <= 1000ms").unwrap();
330        assert_eq!(result.len(), 1);
331        assert_eq!(result[0].field, "latency");
332        assert_eq!(result[0].operator, Operator::LessThanOrEqual);
333        assert_eq!(result[0].value, QueryValue::Duration(1000));
334    }
335
336    #[test]
337    fn test_parse_multiple_predicates() {
338        let result = parse_query("severity = \"ERROR\" AND duration > 500ms").unwrap();
339        assert_eq!(result.len(), 2);
340        assert_eq!(result[0].field, "severity");
341        assert_eq!(result[0].operator, Operator::Equal);
342        assert_eq!(result[1].field, "duration");
343        assert_eq!(result[1].operator, Operator::GreaterThan);
344    }
345
346    #[test]
347    fn test_empty_query() {
348        let result = parse_query("");
349        assert!(result.is_err());
350        assert_eq!(result.unwrap_err(), QueryError::EmptyQuery);
351    }
352
353    #[test]
354    fn test_invalid_syntax_no_operator() {
355        let result = parse_query("severity ERROR");
356        assert!(result.is_err());
357        match result.unwrap_err() {
358            QueryError::InvalidSyntax(_) => {},
359            _ => panic!("Expected InvalidSyntax error"),
360        }
361    }
362
363    #[test]
364    fn test_invalid_duration_format() {
365        let result = parse_query("duration > \"abc\"");
366        // This should succeed - it's a valid string comparison
367        assert!(result.is_ok());
368
369        // Test actual invalid duration
370        let result2 = parse_query("duration > abcms");
371        assert!(result2.is_err());
372        match result2.unwrap_err() {
373            QueryError::InvalidValue(_) => {},
374            _ => panic!("Expected InvalidValue error"),
375        }
376    }
377
378    #[test]
379    fn test_empty_field_name() {
380        let result = parse_query(" = ERROR");
381        assert!(result.is_err());
382        match result.unwrap_err() {
383            QueryError::InvalidSyntax(_) => {},
384            _ => panic!("Expected InvalidSyntax error"),
385        }
386    }
387
388    #[test]
389    fn test_empty_value() {
390        // Test with explicit empty value after operator
391        let result = parse_query("severity = \"\"");
392        assert!(result.is_ok()); // Empty string is valid
393
394        // Test with whitespace-only value (no quotes)
395        let result2 = parse_query("severity =");
396        assert!(result2.is_err());
397        // This triggers InvalidSyntax because the operator pattern doesn't match
398        // when there's no space after the equals sign
399    }
400}