use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum AstNode {
MatchAll,
Comparison(ComparisonNode),
LogicalOp(LogicalOpNode),
UnaryOp(UnaryOpNode),
CollectionOp(CollectionOpNode),
GeoExpr(GeoExprNode),
NslookupExpr(NslookupExprNode),
StatsExpr(StatsNode),
QueryWithStats(QueryWithStatsNode),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ComparisonNode {
pub field: String,
pub operator: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub value: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub field_mutators: Option<Vec<Mutator>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub value_mutators: Option<Vec<Mutator>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub type_hint: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct LogicalOpNode {
pub operator: String,
pub left: Box<AstNode>,
pub right: Box<AstNode>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct UnaryOpNode {
pub operator: String,
pub operand: Box<AstNode>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CollectionOpNode {
pub operator: String,
pub field: String,
pub comparison_operator: String,
pub value: Value,
#[serde(skip_serializing_if = "Option::is_none")]
pub field_mutators: Option<Vec<Mutator>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub type_hint: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct GeoExprNode {
pub field: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub field_mutators: Option<Vec<Mutator>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub type_hint: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub conditions: Option<Box<AstNode>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub geo_params: Option<HashMap<String, Value>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct NslookupExprNode {
pub field: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub field_mutators: Option<Vec<Mutator>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub type_hint: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub conditions: Option<Box<AstNode>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub nslookup_params: Option<HashMap<String, Value>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum VizParamValue {
String(String),
Integer(i64),
Float(f64),
Boolean(bool),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct StatsNode {
pub aggregations: Vec<Aggregation>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub group_by: Vec<GroupBy>,
#[serde(skip_serializing_if = "Option::is_none")]
pub viz_hint: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub viz_params: Option<HashMap<String, VizParamValue>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct QueryWithStatsNode {
pub filter: Box<AstNode>,
pub stats: StatsNode,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Aggregation {
pub function: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub field: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub alias: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub modifier: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub percentile_values: Option<Vec<f64>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rank_values: Option<Vec<f64>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub field_mutators: Option<Vec<Mutator>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct GroupBy {
pub field: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub bucket_size: Option<usize>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Mutator {
pub name: String,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub args: Vec<Value>,
#[serde(skip_serializing_if = "std::collections::HashMap::is_empty", default)]
pub named_args: std::collections::HashMap<String, Value>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Value {
String(String),
Integer(i64),
Float(f64),
Boolean(bool),
List(Vec<Value>),
Null,
}
impl Value {
pub fn is_null(&self) -> bool {
matches!(self, Value::Null)
}
pub fn as_string(&self) -> Option<&str> {
match self {
Value::String(s) => Some(s),
_ => None,
}
}
pub fn as_integer(&self) -> Option<i64> {
match self {
Value::Integer(i) => Some(*i),
Value::Float(f) => Some(*f as i64),
_ => None,
}
}
pub fn as_float(&self) -> Option<f64> {
match self {
Value::Float(f) => Some(*f),
Value::Integer(i) => Some(*i as f64),
_ => None,
}
}
pub fn as_bool(&self) -> Option<bool> {
match self {
Value::Boolean(b) => Some(*b),
_ => None,
}
}
pub fn as_list(&self) -> Option<&Vec<Value>> {
match self {
Value::List(l) => Some(l),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_value_conversions() {
let str_val = Value::String("test".to_string());
assert_eq!(str_val.as_string(), Some("test"));
assert!(str_val.as_integer().is_none());
let int_val = Value::Integer(42);
assert_eq!(int_val.as_integer(), Some(42));
assert_eq!(int_val.as_float(), Some(42.0));
let null_val = Value::Null;
assert!(null_val.is_null());
}
#[test]
fn test_serialization() {
let node = ComparisonNode {
field: "test".to_string(),
operator: "eq".to_string(),
value: Some(Value::String("value".to_string())),
field_mutators: None,
value_mutators: None,
type_hint: None,
};
let json = serde_json::to_string(&node).unwrap();
assert!(json.contains("\"field\":\"test\""));
}
}