activecube_rs/compiler/
validator.rs1use crate::compiler::ir::{FilterNode, QueryIR};
2
3const MAX_QUERY_DEPTH: usize = 6;
4const ABSOLUTE_MAX_LIMIT: u32 = 10000;
5
6pub fn validate(ir: QueryIR) -> Result<QueryIR, async_graphql::Error> {
7 if ir.limit > ABSOLUTE_MAX_LIMIT {
8 return Err(async_graphql::Error::new(format!(
9 "Limit {} exceeds maximum allowed ({})", ir.limit, ABSOLUTE_MAX_LIMIT
10 )));
11 }
12
13 let depth = filter_depth(&ir.filters);
14 if depth > MAX_QUERY_DEPTH {
15 return Err(async_graphql::Error::new(format!(
16 "Filter nesting depth {} exceeds maximum ({})", depth, MAX_QUERY_DEPTH
17 )));
18 }
19
20 let having_depth = filter_depth(&ir.having);
21 if having_depth > MAX_QUERY_DEPTH {
22 return Err(async_graphql::Error::new(format!(
23 "HAVING filter depth {} exceeds maximum ({})", having_depth, MAX_QUERY_DEPTH
24 )));
25 }
26
27 if ir.selects.is_empty() {
28 return Err(async_graphql::Error::new("Query must select at least one field"));
29 }
30
31 Ok(ir)
32}
33
34fn filter_depth(node: &FilterNode) -> usize {
35 match node {
36 FilterNode::Empty | FilterNode::Condition { .. } => 1,
37 FilterNode::And(children) | FilterNode::Or(children) => {
38 1 + children.iter().map(filter_depth).max().unwrap_or(0)
39 }
40 FilterNode::ArrayIncludes { element_conditions, .. } => {
41 1 + element_conditions.iter()
42 .flat_map(|conds| conds.iter().map(filter_depth))
43 .max()
44 .unwrap_or(0)
45 }
46 }
47}
48
49#[cfg(test)]
50mod tests {
51 use super::*;
52 use crate::compiler::ir::*;
53
54 fn minimal_ir() -> QueryIR {
55 QueryIR {
56 cube: "Test".into(), schema: "test".into(), table: "test_table".into(),
57 selects: vec![SelectExpr::Column { column: "id".into(), alias: None }],
58 filters: FilterNode::Empty, having: FilterNode::Empty,
59 group_by: vec![], order_by: vec![], limit: 25, offset: 0,
60 limit_by: None,
61 use_final: false,
62 joins: vec![],
63 custom_query_builder: None,
64 from_subquery: None,
65 }
66 }
67
68 #[test]
69 fn test_valid_ir_passes() { assert!(validate(minimal_ir()).is_ok()); }
70
71 #[test]
72 fn test_excessive_limit_rejected() {
73 let mut ir = minimal_ir(); ir.limit = 99999;
74 assert!(validate(ir).is_err());
75 }
76
77 #[test]
78 fn test_empty_selects_rejected() {
79 let mut ir = minimal_ir(); ir.selects.clear();
80 assert!(validate(ir).is_err());
81 }
82
83 #[test]
84 fn test_deep_filter_rejected() {
85 fn deep_filter(depth: usize) -> FilterNode {
86 if depth == 0 {
87 FilterNode::Condition { column: "x".into(), op: CompareOp::Eq, value: SqlValue::Int(1) }
88 } else {
89 FilterNode::And(vec![deep_filter(depth - 1)])
90 }
91 }
92 let mut ir = minimal_ir(); ir.filters = deep_filter(10);
93 assert!(validate(ir).is_err());
94 }
95}