1use std::collections::HashMap;
8
9#[derive(Debug, Clone)]
15pub struct FieldComplexity {
16 pub field_name: String,
17 pub base_cost: usize,
19 pub multiplier: usize,
21}
22
23impl FieldComplexity {
24 pub fn new(field_name: impl Into<String>, base_cost: usize, multiplier: usize) -> Self {
25 Self {
26 field_name: field_name.into(),
27 base_cost,
28 multiplier,
29 }
30 }
31}
32
33#[derive(Debug, Clone)]
35pub struct ComplexityRule {
36 pub type_name: String,
37 pub fields: HashMap<String, FieldComplexity>,
39}
40
41impl ComplexityRule {
42 pub fn new(type_name: impl Into<String>) -> Self {
43 Self {
44 type_name: type_name.into(),
45 fields: HashMap::new(),
46 }
47 }
48
49 pub fn with_field(mut self, fc: FieldComplexity) -> Self {
50 self.fields.insert(fc.field_name.clone(), fc);
51 self
52 }
53}
54
55#[derive(Debug, Clone)]
57pub struct ComplexityConfig {
58 pub max_complexity: usize,
60 pub max_depth: usize,
62 pub default_cost: usize,
64 pub list_multiplier: usize,
66}
67
68impl ComplexityConfig {
69 pub fn new(
70 max_complexity: usize,
71 max_depth: usize,
72 default_cost: usize,
73 list_multiplier: usize,
74 ) -> Self {
75 Self {
76 max_complexity,
77 max_depth,
78 default_cost,
79 list_multiplier,
80 }
81 }
82}
83
84impl Default for ComplexityConfig {
85 fn default() -> Self {
86 Self::new(1_000, 10, 1, 10)
87 }
88}
89
90#[derive(Debug, Clone)]
96pub struct QueryNode {
97 pub field: String,
98 pub type_name: String,
99 pub children: Vec<QueryNode>,
100 pub is_list: bool,
102 pub args: HashMap<String, String>,
104}
105
106impl QueryNode {
107 pub fn leaf(field: impl Into<String>, type_name: impl Into<String>) -> Self {
109 Self {
110 field: field.into(),
111 type_name: type_name.into(),
112 children: vec![],
113 is_list: false,
114 args: HashMap::new(),
115 }
116 }
117
118 pub fn with_children(
120 field: impl Into<String>,
121 type_name: impl Into<String>,
122 children: Vec<QueryNode>,
123 ) -> Self {
124 Self {
125 field: field.into(),
126 type_name: type_name.into(),
127 children,
128 is_list: false,
129 args: HashMap::new(),
130 }
131 }
132
133 pub fn list_field(
135 field: impl Into<String>,
136 type_name: impl Into<String>,
137 children: Vec<QueryNode>,
138 ) -> Self {
139 Self {
140 field: field.into(),
141 type_name: type_name.into(),
142 children,
143 is_list: true,
144 args: HashMap::new(),
145 }
146 }
147
148 pub fn depth(&self) -> usize {
150 if self.children.is_empty() {
151 1
152 } else {
153 1 + self.children.iter().map(|c| c.depth()).max().unwrap_or(0)
154 }
155 }
156
157 pub fn field_count(&self) -> usize {
159 1 + self.children.iter().map(|c| c.field_count()).sum::<usize>()
160 }
161}
162
163#[derive(Debug, Clone)]
169pub struct ComplexityResult {
170 pub complexity: usize,
172 pub depth: usize,
174 pub exceeds_limit: bool,
176 pub depth_exceeded: bool,
178 pub breakdown: Vec<(String, usize)>,
180}
181
182pub struct QueryComplexityAnalyzer {
188 config: ComplexityConfig,
189 rules: HashMap<String, ComplexityRule>,
190}
191
192impl QueryComplexityAnalyzer {
193 pub fn new(config: ComplexityConfig) -> Self {
194 Self {
195 config,
196 rules: HashMap::new(),
197 }
198 }
199
200 pub fn add_rule(&mut self, rule: ComplexityRule) {
202 self.rules.insert(rule.type_name.clone(), rule);
203 }
204
205 pub fn analyze(&self, root: &QueryNode) -> ComplexityResult {
207 let mut breakdown = Vec::new();
208 let complexity = self.traverse(root, &mut breakdown);
209 let depth = root.depth();
210 ComplexityResult {
211 complexity,
212 depth,
213 exceeds_limit: complexity > self.config.max_complexity,
214 depth_exceeded: depth > self.config.max_depth,
215 breakdown,
216 }
217 }
218
219 pub fn node_cost(&self, node: &QueryNode, _depth: usize) -> usize {
221 let base = self
223 .rules
224 .get(&node.type_name)
225 .and_then(|r| r.fields.get(&node.field))
226 .map(|fc| fc.base_cost)
227 .unwrap_or(self.config.default_cost);
228
229 let multiplier = self
231 .rules
232 .get(&node.type_name)
233 .and_then(|r| r.fields.get(&node.field))
234 .map(|fc| fc.multiplier)
235 .unwrap_or(if node.is_list {
236 self.config.list_multiplier
237 } else {
238 1
239 });
240
241 let child_cost: usize = node.children.iter().map(|c| self.node_cost(c, 0)).sum();
242
243 base + multiplier * child_cost
244 }
245
246 fn traverse(&self, node: &QueryNode, breakdown: &mut Vec<(String, usize)>) -> usize {
247 let base = self
248 .rules
249 .get(&node.type_name)
250 .and_then(|r| r.fields.get(&node.field))
251 .map(|fc| fc.base_cost)
252 .unwrap_or(self.config.default_cost);
253
254 let multiplier = self
255 .rules
256 .get(&node.type_name)
257 .and_then(|r| r.fields.get(&node.field))
258 .map(|fc| fc.multiplier)
259 .unwrap_or(if node.is_list {
260 self.config.list_multiplier
261 } else {
262 1
263 });
264
265 let child_cost: usize = node
266 .children
267 .iter()
268 .map(|c| self.traverse(c, breakdown))
269 .sum();
270
271 let total = base + multiplier * child_cost;
272 breakdown.push((node.field.clone(), total));
273 total
274 }
275
276 pub fn is_allowed(&self, result: &ComplexityResult) -> bool {
278 !result.exceeds_limit && !result.depth_exceeded
279 }
280}
281
282#[derive(Debug, Clone)]
288pub struct SimpleCostRule {
289 pub field_name: String,
291 pub cost: usize,
293 pub multiplier_arg: Option<String>,
295}
296
297impl SimpleCostRule {
298 pub fn fixed(field_name: impl Into<String>, cost: usize) -> Self {
300 Self {
301 field_name: field_name.into(),
302 cost,
303 multiplier_arg: None,
304 }
305 }
306
307 pub fn multiplied(
309 field_name: impl Into<String>,
310 cost: usize,
311 multiplier_arg: impl Into<String>,
312 ) -> Self {
313 Self {
314 field_name: field_name.into(),
315 cost,
316 multiplier_arg: Some(multiplier_arg.into()),
317 }
318 }
319}
320
321#[derive(Debug, Clone)]
323pub struct QueryField {
324 pub name: String,
326 pub args: HashMap<String, usize>,
328 pub children: Vec<QueryField>,
330}
331
332impl QueryField {
333 pub fn new(name: impl Into<String>) -> Self {
335 Self {
336 name: name.into(),
337 args: HashMap::new(),
338 children: Vec::new(),
339 }
340 }
341
342 pub fn with_arg(mut self, key: impl Into<String>, val: usize) -> Self {
344 self.args.insert(key.into(), val);
345 self
346 }
347
348 pub fn with_child(mut self, child: QueryField) -> Self {
350 self.children.push(child);
351 self
352 }
353
354 pub fn depth(&self) -> usize {
356 if self.children.is_empty() {
357 1
358 } else {
359 1 + self.children.iter().map(|c| c.depth()).max().unwrap_or(0)
360 }
361 }
362}
363
364pub struct SimpleComplexityAnalyzer {
366 rules: HashMap<String, SimpleCostRule>,
367 default_cost: usize,
368 limit: usize,
369}
370
371impl SimpleComplexityAnalyzer {
372 pub fn new(limit: usize) -> Self {
374 Self {
375 rules: HashMap::new(),
376 default_cost: 1,
377 limit,
378 }
379 }
380
381 pub fn with_default_cost(mut self, cost: usize) -> Self {
383 self.default_cost = cost;
384 self
385 }
386
387 pub fn add_rule(&mut self, rule: SimpleCostRule) {
389 self.rules.insert(rule.field_name.clone(), rule);
390 }
391
392 pub fn cost_for_field(&self, name: &str, args: &HashMap<String, usize>) -> usize {
394 match self.rules.get(name) {
395 Some(rule) => match &rule.multiplier_arg {
396 Some(arg) => {
397 let m = args.get(arg).copied().unwrap_or(1).max(1);
398 rule.cost.saturating_mul(m)
399 }
400 None => rule.cost,
401 },
402 None => self.default_cost,
403 }
404 }
405
406 pub fn analyze_fields(&self, fields: &[QueryField]) -> (usize, Vec<(String, usize)>) {
408 let mut breakdown = Vec::new();
409 let total = self.sum_fields(fields, &mut breakdown);
410 (total, breakdown)
411 }
412
413 pub fn is_within_limit(&self, fields: &[QueryField]) -> bool {
415 let (total, _) = self.analyze_fields(fields);
416 total <= self.limit
417 }
418
419 fn sum_fields(&self, fields: &[QueryField], out: &mut Vec<(String, usize)>) -> usize {
420 let mut total = 0_usize;
421 for f in fields {
422 let cost = self.cost_for_field(&f.name, &f.args);
423 out.push((f.name.clone(), cost));
424 total = total.saturating_add(cost);
425 total = total.saturating_add(self.sum_fields(&f.children, out));
426 }
427 total
428 }
429}
430
431#[cfg(test)]
435mod tests {
436 use super::*;
437
438 fn default_analyzer() -> QueryComplexityAnalyzer {
439 QueryComplexityAnalyzer::new(ComplexityConfig::new(100, 5, 1, 10))
440 }
441
442 #[test]
445 fn test_leaf_depth_is_1() {
446 let n = QueryNode::leaf("id", "String");
447 assert_eq!(n.depth(), 1);
448 }
449
450 #[test]
451 fn test_leaf_field_count_is_1() {
452 let n = QueryNode::leaf("id", "String");
453 assert_eq!(n.field_count(), 1);
454 }
455
456 #[test]
457 fn test_with_children_depth() {
458 let n = QueryNode::with_children(
459 "user",
460 "User",
461 vec![
462 QueryNode::leaf("id", "ID"),
463 QueryNode::leaf("name", "String"),
464 ],
465 );
466 assert_eq!(n.depth(), 2);
467 }
468
469 #[test]
470 fn test_with_children_field_count() {
471 let n = QueryNode::with_children(
472 "user",
473 "User",
474 vec![
475 QueryNode::leaf("id", "ID"),
476 QueryNode::leaf("name", "String"),
477 ],
478 );
479 assert_eq!(n.field_count(), 3);
480 }
481
482 #[test]
483 fn test_list_field_is_list() {
484 let n = QueryNode::list_field("users", "User", vec![QueryNode::leaf("id", "ID")]);
485 assert!(n.is_list);
486 }
487
488 #[test]
489 fn test_list_field_depth() {
490 let n = QueryNode::list_field("users", "User", vec![QueryNode::leaf("id", "ID")]);
491 assert_eq!(n.depth(), 2);
492 }
493
494 #[test]
495 fn test_nested_depth() {
496 let inner =
497 QueryNode::with_children("posts", "Post", vec![QueryNode::leaf("title", "String")]);
498 let outer = QueryNode::with_children("user", "User", vec![inner]);
499 assert_eq!(outer.depth(), 3);
500 }
501
502 #[test]
505 fn test_single_leaf_complexity() {
506 let a = default_analyzer();
507 let r = a.analyze(&QueryNode::leaf("id", "ID"));
508 assert_eq!(r.complexity, 1); }
510
511 #[test]
512 fn test_two_leaves_cost_sum() {
513 let a = default_analyzer();
514 let n = QueryNode::with_children(
515 "root",
516 "Query",
517 vec![
518 QueryNode::leaf("a", "String"),
519 QueryNode::leaf("b", "String"),
520 ],
521 );
522 let r = a.analyze(&n);
524 assert_eq!(r.complexity, 3);
525 }
526
527 #[test]
530 fn test_list_field_multiplier_applied() {
531 let a = default_analyzer(); let n = QueryNode::list_field("users", "User", vec![QueryNode::leaf("id", "ID")]);
533 let r = a.analyze(&n);
535 assert_eq!(r.complexity, 11);
536 }
537
538 #[test]
539 fn test_non_list_no_extra_multiplier() {
540 let a = default_analyzer();
541 let n = QueryNode::with_children("user", "User", vec![QueryNode::leaf("id", "ID")]);
542 let r = a.analyze(&n);
544 assert_eq!(r.complexity, 2);
545 }
546
547 #[test]
548 fn test_list_field_two_children() {
549 let a = default_analyzer();
550 let n = QueryNode::list_field(
551 "users",
552 "User",
553 vec![
554 QueryNode::leaf("id", "ID"),
555 QueryNode::leaf("name", "String"),
556 ],
557 );
558 let r = a.analyze(&n);
560 assert_eq!(r.complexity, 21);
561 }
562
563 #[test]
566 fn test_exceeds_limit_detected() {
567 let a = QueryComplexityAnalyzer::new(ComplexityConfig::new(5, 10, 1, 10));
568 let n = QueryNode::list_field(
569 "users",
570 "User",
571 vec![
572 QueryNode::leaf("id", "ID"),
573 QueryNode::leaf("name", "String"),
574 QueryNode::leaf("email", "String"),
575 ],
576 );
577 let r = a.analyze(&n);
579 assert!(r.exceeds_limit);
580 }
581
582 #[test]
583 fn test_does_not_exceed_limit() {
584 let a = QueryComplexityAnalyzer::new(ComplexityConfig::new(100, 10, 1, 10));
585 let r = a.analyze(&QueryNode::leaf("id", "ID"));
586 assert!(!r.exceeds_limit);
587 }
588
589 #[test]
590 fn test_depth_exceeded_detected() {
591 let a = QueryComplexityAnalyzer::new(ComplexityConfig::new(10_000, 2, 1, 1));
592 let deep = QueryNode::with_children(
594 "a",
595 "A",
596 vec![QueryNode::with_children(
597 "b",
598 "B",
599 vec![QueryNode::leaf("c", "C")],
600 )],
601 );
602 let r = a.analyze(&deep);
603 assert!(r.depth_exceeded);
604 }
605
606 #[test]
607 fn test_depth_not_exceeded() {
608 let a = QueryComplexityAnalyzer::new(ComplexityConfig::new(10_000, 5, 1, 1));
609 let n = QueryNode::with_children("a", "A", vec![QueryNode::leaf("b", "B")]);
610 let r = a.analyze(&n);
611 assert!(!r.depth_exceeded);
612 }
613
614 #[test]
617 fn test_custom_rule_overrides_default_cost() {
618 let mut a = QueryComplexityAnalyzer::new(ComplexityConfig::new(10_000, 10, 1, 10));
619 a.add_rule(
620 ComplexityRule::new("Query").with_field(FieldComplexity::new("expensiveField", 50, 1)),
621 );
622 let n = QueryNode::leaf("expensiveField", "Query"); let n2 = QueryNode {
625 field: "expensiveField".to_string(),
626 type_name: "Query".to_string(),
627 children: vec![],
628 is_list: false,
629 args: HashMap::new(),
630 };
631 let r = a.analyze(&n2);
632 assert_eq!(r.complexity, 50); let _ = n; }
635
636 #[test]
637 fn test_custom_rule_multiplier_override() {
638 let mut a = QueryComplexityAnalyzer::new(ComplexityConfig::new(10_000, 10, 1, 5));
639 a.add_rule(
641 ComplexityRule::new("Query").with_field(FieldComplexity::new("heavyList", 1, 100)),
642 );
643 let n = QueryNode {
644 field: "heavyList".to_string(),
645 type_name: "Query".to_string(),
646 children: vec![QueryNode::leaf("id", "ID")],
647 is_list: true, args: HashMap::new(),
649 };
650 let r = a.analyze(&n);
652 assert_eq!(r.complexity, 101);
653 }
654
655 #[test]
658 fn test_breakdown_contains_all_fields() {
659 let a = default_analyzer();
660 let n = QueryNode::with_children(
661 "root",
662 "Query",
663 vec![
664 QueryNode::leaf("a", "String"),
665 QueryNode::leaf("b", "String"),
666 ],
667 );
668 let r = a.analyze(&n);
669 let fields: Vec<&str> = r.breakdown.iter().map(|(f, _)| f.as_str()).collect();
670 assert!(fields.contains(&"root"));
671 assert!(fields.contains(&"a"));
672 assert!(fields.contains(&"b"));
673 }
674
675 #[test]
676 fn test_breakdown_not_empty_for_single_leaf() {
677 let a = default_analyzer();
678 let r = a.analyze(&QueryNode::leaf("id", "ID"));
679 assert!(!r.breakdown.is_empty());
680 }
681
682 #[test]
685 fn test_is_allowed_within_limits() {
686 let a = default_analyzer();
687 let r = a.analyze(&QueryNode::leaf("id", "ID"));
688 assert!(a.is_allowed(&r));
689 }
690
691 #[test]
692 fn test_is_allowed_complexity_exceeded() {
693 let a = QueryComplexityAnalyzer::new(ComplexityConfig::new(0, 10, 1, 1));
694 let r = a.analyze(&QueryNode::leaf("id", "ID")); assert!(!a.is_allowed(&r));
696 }
697
698 #[test]
699 fn test_is_allowed_depth_exceeded() {
700 let a = QueryComplexityAnalyzer::new(ComplexityConfig::new(10_000, 1, 1, 1));
701 let n = QueryNode::with_children("a", "A", vec![QueryNode::leaf("b", "B")]); let r = a.analyze(&n);
703 assert!(!a.is_allowed(&r));
704 }
705
706 #[test]
709 fn test_deep_nesting_cost_calculation() {
710 let a = QueryComplexityAnalyzer::new(ComplexityConfig::new(100_000, 20, 1, 1));
711 let n = QueryNode::with_children(
713 "a",
714 "A",
715 vec![QueryNode::with_children(
716 "b",
717 "B",
718 vec![QueryNode::with_children(
719 "c",
720 "C",
721 vec![QueryNode::with_children(
722 "d",
723 "D",
724 vec![QueryNode::leaf("e", "E")],
725 )],
726 )],
727 )],
728 );
729 let r = a.analyze(&n);
730 assert_eq!(r.complexity, 5);
733 assert_eq!(r.depth, 5);
734 }
735
736 #[test]
737 fn test_zero_child_leaf_node() {
738 let a = default_analyzer();
739 let n = QueryNode::leaf("scalar", "String");
740 let r = a.analyze(&n);
741 assert_eq!(r.complexity, 1);
742 assert_eq!(r.depth, 1);
743 assert_eq!(n.field_count(), 1);
744 }
745
746 #[test]
747 fn test_node_cost_matches_analyze() {
748 let a = default_analyzer();
749 let n = QueryNode::list_field("items", "Item", vec![QueryNode::leaf("id", "ID")]);
750 let r = a.analyze(&n);
751 let nc = a.node_cost(&n, 0);
752 assert_eq!(r.complexity, nc);
753 }
754
755 #[test]
756 fn test_multiple_rules_independent() {
757 let mut a = QueryComplexityAnalyzer::new(ComplexityConfig::new(10_000, 10, 1, 10));
758 a.add_rule(ComplexityRule::new("TypeA").with_field(FieldComplexity::new("fieldA", 5, 1)));
759 a.add_rule(ComplexityRule::new("TypeB").with_field(FieldComplexity::new("fieldB", 20, 1)));
760 let na = QueryNode {
761 field: "fieldA".to_string(),
762 type_name: "TypeA".to_string(),
763 children: vec![],
764 is_list: false,
765 args: HashMap::new(),
766 };
767 let nb = QueryNode {
768 field: "fieldB".to_string(),
769 type_name: "TypeB".to_string(),
770 children: vec![],
771 is_list: false,
772 args: HashMap::new(),
773 };
774 let ra = a.analyze(&na);
775 let rb = a.analyze(&nb);
776 assert_eq!(ra.complexity, 5);
777 assert_eq!(rb.complexity, 20);
778 }
779
780 #[test]
783 fn test_query_field_new_defaults() {
784 let f = QueryField::new("users");
785 assert_eq!(f.name, "users");
786 assert!(f.args.is_empty());
787 assert!(f.children.is_empty());
788 }
789
790 #[test]
791 fn test_query_field_with_arg() {
792 let f = QueryField::new("list").with_arg("first", 10);
793 assert_eq!(f.args.get("first"), Some(&10));
794 }
795
796 #[test]
797 fn test_query_field_with_child() {
798 let f = QueryField::new("user").with_child(QueryField::new("id"));
799 assert_eq!(f.children.len(), 1);
800 }
801
802 #[test]
803 fn test_query_field_depth_leaf() {
804 let f = QueryField::new("id");
805 assert_eq!(f.depth(), 1);
806 }
807
808 #[test]
809 fn test_query_field_depth_two_levels() {
810 let f = QueryField::new("user").with_child(QueryField::new("id"));
811 assert_eq!(f.depth(), 2);
812 }
813
814 #[test]
815 fn test_query_field_depth_three_levels() {
816 let f =
817 QueryField::new("a").with_child(QueryField::new("b").with_child(QueryField::new("c")));
818 assert_eq!(f.depth(), 3);
819 }
820
821 #[test]
822 fn test_simple_analyzer_empty_fields_zero_cost() {
823 let a = SimpleComplexityAnalyzer::new(100);
824 assert!(a.is_within_limit(&[]));
825 let (total, _) = a.analyze_fields(&[]);
826 assert_eq!(total, 0);
827 }
828
829 #[test]
830 fn test_simple_analyzer_default_cost_one() {
831 let a = SimpleComplexityAnalyzer::new(100);
832 let fields = vec![QueryField::new("a"), QueryField::new("b")];
833 let (total, _) = a.analyze_fields(&fields);
834 assert_eq!(total, 2);
835 }
836
837 #[test]
838 fn test_simple_analyzer_custom_default_cost() {
839 let a = SimpleComplexityAnalyzer::new(100).with_default_cost(5);
840 let fields = vec![QueryField::new("a")];
841 let (total, _) = a.analyze_fields(&fields);
842 assert_eq!(total, 5);
843 }
844
845 #[test]
846 fn test_simple_analyzer_fixed_rule() {
847 let mut a = SimpleComplexityAnalyzer::new(100);
848 a.add_rule(SimpleCostRule::fixed("expensive", 50));
849 let fields = vec![QueryField::new("expensive")];
850 let (total, _) = a.analyze_fields(&fields);
851 assert_eq!(total, 50);
852 }
853
854 #[test]
855 fn test_simple_analyzer_multiplied_rule() {
856 let mut a = SimpleComplexityAnalyzer::new(10_000);
857 a.add_rule(SimpleCostRule::multiplied("list", 10, "first"));
858 let fields = vec![QueryField::new("list").with_arg("first", 5)];
859 let (total, _) = a.analyze_fields(&fields);
860 assert_eq!(total, 50); }
862
863 #[test]
864 fn test_simple_analyzer_multiplied_missing_arg_defaults_one() {
865 let mut a = SimpleComplexityAnalyzer::new(10_000);
866 a.add_rule(SimpleCostRule::multiplied("list", 10, "first"));
867 let fields = vec![QueryField::new("list")]; let (total, _) = a.analyze_fields(&fields);
869 assert_eq!(total, 10); }
871
872 #[test]
873 fn test_simple_analyzer_within_limit_true() {
874 let a = SimpleComplexityAnalyzer::new(100);
875 let fields = vec![QueryField::new("x")];
876 assert!(a.is_within_limit(&fields));
877 }
878
879 #[test]
880 fn test_simple_analyzer_exceeds_limit() {
881 let a = SimpleComplexityAnalyzer::new(0);
882 let fields = vec![QueryField::new("x")];
883 assert!(!a.is_within_limit(&fields));
884 }
885
886 #[test]
887 fn test_simple_analyzer_breakdown_has_entries() {
888 let a = SimpleComplexityAnalyzer::new(100);
889 let fields = vec![QueryField::new("a"), QueryField::new("b")];
890 let (_, breakdown) = a.analyze_fields(&fields);
891 assert_eq!(breakdown.len(), 2);
892 }
893
894 #[test]
895 fn test_simple_analyzer_children_included_in_total() {
896 let a = SimpleComplexityAnalyzer::new(100);
897 let f = QueryField::new("user")
898 .with_child(QueryField::new("name"))
899 .with_child(QueryField::new("email"));
900 let (total, _) = a.analyze_fields(&[f]);
901 assert_eq!(total, 3); }
903
904 #[test]
905 fn test_simple_cost_rule_fixed() {
906 let rule = SimpleCostRule::fixed("field", 7);
907 assert_eq!(rule.cost, 7);
908 assert!(rule.multiplier_arg.is_none());
909 }
910
911 #[test]
912 fn test_simple_cost_rule_multiplied() {
913 let rule = SimpleCostRule::multiplied("field", 7, "count");
914 assert_eq!(rule.multiplier_arg, Some("count".to_string()));
915 }
916
917 #[test]
918 fn test_cost_for_field_unknown_uses_default() {
919 let a = SimpleComplexityAnalyzer::new(100).with_default_cost(3);
920 let cost = a.cost_for_field("unknown", &HashMap::new());
921 assert_eq!(cost, 3);
922 }
923
924 #[test]
925 fn test_cost_for_field_known_fixed() {
926 let mut a = SimpleComplexityAnalyzer::new(100);
927 a.add_rule(SimpleCostRule::fixed("known", 42));
928 let cost = a.cost_for_field("known", &HashMap::new());
929 assert_eq!(cost, 42);
930 }
931
932 #[test]
933 fn test_query_field_multiple_children() {
934 let f = QueryField::new("root")
935 .with_child(QueryField::new("a"))
936 .with_child(QueryField::new("b"))
937 .with_child(QueryField::new("c"));
938 assert_eq!(f.children.len(), 3);
939 }
940}