sz_orm_core/
cycle_detection.rs1use std::collections::HashSet;
11
12use crate::DbError;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
18pub enum CyclePolicy {
19 Error,
21 #[default]
23 Truncate,
24 AllowWithDepthLimit(usize),
26}
27
28pub struct CycleDetector {
33 policy: CyclePolicy,
34 visited: HashSet<String>,
35 current_depth: usize,
36 path: Vec<String>,
37}
38
39impl CycleDetector {
40 pub fn new(policy: CyclePolicy) -> Self {
42 Self {
43 policy,
44 visited: HashSet::new(),
45 current_depth: 0,
46 path: Vec::new(),
47 }
48 }
49
50 pub fn check(&mut self, entity_type: &str, relation_name: &str) -> Result<bool, DbError> {
55 let key = format!("{}::{}", entity_type, relation_name);
56
57 if self.visited.contains(&key) {
58 return match self.policy {
59 CyclePolicy::Error => {
60 self.path.push(key.clone());
61 Err(DbError::InvalidInput(format!(
62 "检测到循环引用: {}",
63 self.path.join(" → ")
64 )))
65 }
66 CyclePolicy::Truncate => Ok(false),
67 CyclePolicy::AllowWithDepthLimit(max_depth) => {
68 if self.current_depth >= max_depth {
69 Ok(false)
70 } else {
71 Ok(true)
72 }
73 }
74 };
75 }
76
77 if let CyclePolicy::AllowWithDepthLimit(max_depth) = self.policy {
78 if self.current_depth >= max_depth {
79 return Ok(false);
80 }
81 }
82
83 Ok(true)
84 }
85
86 pub fn enter(&mut self, entity_type: &str, relation_name: &str) {
88 let key = format!("{}::{}", entity_type, relation_name);
89 self.visited.insert(key.clone());
90 self.path.push(key);
91 self.current_depth += 1;
92 }
93
94 pub fn leave(&mut self) {
96 self.path.pop();
97 self.current_depth = self.current_depth.saturating_sub(1);
98 }
99
100 pub fn depth(&self) -> usize {
102 self.current_depth
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 #[test]
111 fn test_cycle_policy_default() {
112 assert_eq!(CyclePolicy::default(), CyclePolicy::Truncate);
113 }
114
115 #[test]
116 fn test_cycle_detector_no_cycle() {
117 let mut detector = CycleDetector::new(CyclePolicy::Error);
118 assert!(detector.check("User", "orders").unwrap());
119 detector.enter("User", "orders");
120 assert!(detector.check("Order", "items").unwrap());
121 detector.enter("Order", "items");
122 detector.leave();
123 detector.leave();
124 }
125
126 #[test]
127 fn test_cycle_detector_error_policy() {
128 let mut detector = CycleDetector::new(CyclePolicy::Error);
129 detector.enter("User", "orders");
130 assert!(detector.check("Order", "user").unwrap());
131 detector.enter("Order", "user");
132 let result = detector.check("User", "orders");
133 assert!(result.is_err());
134 }
135
136 #[test]
137 fn test_cycle_detector_truncate_policy() {
138 let mut detector = CycleDetector::new(CyclePolicy::Truncate);
139 detector.enter("User", "orders");
140 detector.enter("Order", "user");
141 let result = detector.check("User", "orders").unwrap();
142 assert!(!result);
143 }
144
145 #[test]
146 fn test_cycle_detector_depth_limit() {
147 let mut detector = CycleDetector::new(CyclePolicy::AllowWithDepthLimit(3));
148 detector.enter("User", "orders");
149 assert_eq!(detector.depth(), 1);
150 detector.enter("Order", "items");
151 assert_eq!(detector.depth(), 2);
152 detector.enter("OrderItem", "product");
153 assert_eq!(detector.depth(), 3);
154 let result = detector.check("Product", "category").unwrap();
155 assert!(!result);
156 }
157
158 #[test]
159 fn test_cycle_detector_different_relation_no_false_positive() {
160 let mut detector = CycleDetector::new(CyclePolicy::Error);
161 detector.enter("User", "orders");
162 let result = detector.check("User", "manager");
163 assert!(result.unwrap());
164 }
165}