1use aspect_core::pointcut::{FunctionInfo, Matcher, Pointcut};
7use aspect_core::{Aspect, ProceedingJoinPoint};
8use once_cell::sync::Lazy;
9use std::sync::{Arc, RwLock};
10
11#[derive(Clone)]
13pub struct RegisteredAspect {
14 pub aspect: Arc<dyn Aspect>,
16
17 pub pointcut: Pointcut,
19
20 pub order: i32,
22
23 pub name: Option<String>,
25}
26
27pub struct AspectRegistry {
32 aspects: RwLock<Vec<RegisteredAspect>>,
33}
34
35impl AspectRegistry {
36 fn new() -> Self {
38 Self {
39 aspects: RwLock::new(Vec::new()),
40 }
41 }
42
43 pub fn register(
57 &self,
58 aspect: Arc<dyn Aspect>,
59 pointcut: Pointcut,
60 order: i32,
61 name: Option<String>,
62 ) {
63 let mut aspects = self.aspects.write().unwrap();
64 aspects.push(RegisteredAspect {
65 aspect,
66 pointcut,
67 order,
68 name,
69 });
70
71 aspects.sort_by_key(|a| a.order);
73 }
74
75 pub fn find_matching(&self, function: &FunctionInfo) -> Vec<RegisteredAspect> {
79 let aspects = self.aspects.read().unwrap();
80 aspects
81 .iter()
82 .filter(|registered| registered.pointcut.matches(function))
83 .cloned()
84 .collect()
85 }
86
87 pub fn apply_aspects(
91 &self,
92 function: &FunctionInfo,
93 mut pjp: ProceedingJoinPoint,
94 ) -> Result<Box<dyn std::any::Any>, aspect_core::AspectError> {
95 let matching = self.find_matching(function);
96
97 if matching.is_empty() {
98 return pjp.proceed();
100 }
101
102 for registered in matching.iter().rev() {
105 let aspect = Arc::clone(®istered.aspect);
106 let inner_pjp = pjp;
107
108 pjp = ProceedingJoinPoint::new(
110 move || aspect.around(inner_pjp),
111 function_info_to_joinpoint(function),
112 );
113 }
114
115 pjp.proceed()
116 }
117
118 pub fn count(&self) -> usize {
120 self.aspects.read().unwrap().len()
121 }
122
123 pub fn clear(&self) {
125 self.aspects.write().unwrap().clear();
126 }
127}
128
129pub static GLOBAL_REGISTRY: Lazy<AspectRegistry> = Lazy::new(AspectRegistry::new);
133
134pub fn global_registry() -> &'static AspectRegistry {
145 &GLOBAL_REGISTRY
146}
147
148fn function_info_to_joinpoint(info: &FunctionInfo) -> aspect_core::JoinPoint {
150 aspect_core::JoinPoint {
151 function_name: Box::leak(info.name.clone().into_boxed_str()),
152 module_path: Box::leak(info.module_path.clone().into_boxed_str()),
153 location: aspect_core::Location {
154 file: "unknown",
155 line: 0,
156 },
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163 use aspect_core::{Aspect, JoinPoint};
164 use std::any::Any;
165 use std::sync::{Arc, Mutex};
166
167 #[derive(Clone)]
168 struct TestAspect {
169 name: String,
170 called: Arc<Mutex<Vec<String>>>,
171 }
172
173 impl Aspect for TestAspect {
174 fn before(&self, ctx: &JoinPoint) {
175 self.called
176 .lock()
177 .unwrap()
178 .push(format!("{}:before:{}", self.name, ctx.function_name));
179 }
180
181 fn after(&self, ctx: &JoinPoint, _result: &dyn Any) {
182 self.called
183 .lock()
184 .unwrap()
185 .push(format!("{}:after:{}", self.name, ctx.function_name));
186 }
187 }
188
189 #[test]
190 fn test_registry_register_and_find() {
191 let registry = AspectRegistry::new();
192
193 let calls = Arc::new(Mutex::new(Vec::new()));
194 let aspect = Arc::new(TestAspect {
195 name: "test".to_string(),
196 called: calls.clone(),
197 });
198
199 let pointcut = Pointcut::parse("execution(pub fn *(..))").unwrap();
200 registry.register(aspect, pointcut, 0, Some("test".into()));
201
202 assert_eq!(registry.count(), 1);
203
204 let function = FunctionInfo {
205 name: "test_func".to_string(),
206 module_path: "test::module".to_string(),
207 visibility: "pub".to_string(),
208 return_type: None,
209 };
210
211 let matching = registry.find_matching(&function);
212 assert_eq!(matching.len(), 1);
213 assert_eq!(matching[0].name.as_deref(), Some("test"));
214 }
215
216 #[test]
217 fn test_aspect_ordering() {
218 let registry = AspectRegistry::new();
219
220 let calls1 = Arc::new(Mutex::new(Vec::new()));
221 let aspect1 = Arc::new(TestAspect {
222 name: "first".to_string(),
223 called: calls1.clone(),
224 });
225
226 let calls2 = Arc::new(Mutex::new(Vec::new()));
227 let aspect2 = Arc::new(TestAspect {
228 name: "second".to_string(),
229 called: calls2.clone(),
230 });
231
232 let pointcut = Pointcut::parse("execution(pub fn *(..))").unwrap();
233
234 registry.register(aspect2, pointcut.clone(), 20, Some("second".into()));
236 registry.register(aspect1, pointcut, 10, Some("first".into()));
237
238 let function = FunctionInfo {
239 name: "test_func".to_string(),
240 module_path: "test::module".to_string(),
241 visibility: "pub".to_string(),
242 return_type: None,
243 };
244
245 let matching = registry.find_matching(&function);
246 assert_eq!(matching.len(), 2);
247 assert_eq!(matching[0].name.as_deref(), Some("first"));
248 assert_eq!(matching[1].name.as_deref(), Some("second"));
249 }
250
251 #[test]
252 fn test_pointcut_matching() {
253 let registry = AspectRegistry::new();
254
255 let calls = Arc::new(Mutex::new(Vec::new()));
256 let aspect = Arc::new(TestAspect {
257 name: "api".to_string(),
258 called: calls.clone(),
259 });
260
261 let pointcut = Pointcut::parse("execution(pub fn *(..)) && within(crate::api)").unwrap();
262 registry.register(aspect, pointcut, 0, Some("api".into()));
263
264 let func1 = FunctionInfo {
266 name: "save_user".to_string(),
267 module_path: "crate::api".to_string(),
268 visibility: "pub".to_string(),
269 return_type: None,
270 };
271 assert_eq!(registry.find_matching(&func1).len(), 1);
272
273 let func2 = FunctionInfo {
275 name: "save_user".to_string(),
276 module_path: "crate::internal".to_string(),
277 visibility: "pub".to_string(),
278 return_type: None,
279 };
280 assert_eq!(registry.find_matching(&func2).len(), 0);
281
282 let func3 = FunctionInfo {
284 name: "save_user".to_string(),
285 module_path: "crate::api".to_string(),
286 visibility: "".to_string(),
287 return_type: None,
288 };
289 assert_eq!(registry.find_matching(&func3).len(), 0);
290 }
291}