Skip to main content

aspect_runtime/
registry.rs

1//! Global aspect registry for managing aspect-pointcut bindings.
2//!
3//! The registry allows aspects to be registered with pointcut patterns,
4//! and then automatically applied to matching functions at runtime.
5
6use aspect_core::pointcut::{FunctionInfo, Matcher, Pointcut};
7use aspect_core::{Aspect, ProceedingJoinPoint};
8use once_cell::sync::Lazy;
9use std::sync::{Arc, RwLock};
10
11/// A registered aspect with its associated pointcut and metadata.
12#[derive(Clone)]
13pub struct RegisteredAspect {
14    /// The aspect instance
15    pub aspect: Arc<dyn Aspect>,
16
17    /// The pointcut pattern this aspect matches
18    pub pointcut: Pointcut,
19
20    /// Execution order (lower values run first/outermost)
21    pub order: i32,
22
23    /// Optional name for debugging
24    pub name: Option<String>,
25}
26
27/// Global aspect registry for managing aspect-pointcut bindings.
28///
29/// The registry is thread-safe and can be accessed from anywhere in the program.
30/// Aspects are matched against functions using their pointcut patterns.
31pub struct AspectRegistry {
32    aspects: RwLock<Vec<RegisteredAspect>>,
33}
34
35impl AspectRegistry {
36    /// Create a new empty registry.
37    fn new() -> Self {
38        Self {
39            aspects: RwLock::new(Vec::new()),
40        }
41    }
42
43    /// Register an aspect with a pointcut pattern.
44    ///
45    /// # Example
46    ///
47    /// ```rust,no_run
48    /// use aspect_runtime::registry::global_registry;
49    /// use aspect_core::pointcut::Pointcut;
50    /// use std::sync::Arc;
51    ///
52    /// // Register an aspect (my_aspect would be an actual Aspect implementation)
53    /// let pointcut = Pointcut::parse("execution(pub fn *(..))").unwrap();
54    /// // global_registry().register(Arc::new(my_aspect), pointcut, 0, Some("my_aspect".into()));
55    /// ```
56    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        // Sort by order (lower values first)
72        aspects.sort_by_key(|a| a.order);
73    }
74
75    /// Find all aspects that match the given function.
76    ///
77    /// Returns aspects in execution order (sorted by `order` field).
78    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    /// Apply all matching aspects to a function execution.
88    ///
89    /// This creates a chain of aspects, with lower-order aspects wrapping higher-order ones.
90    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            // No aspects match, just proceed
99            return pjp.proceed();
100        }
101
102        // Apply aspects in order (outermost first)
103        // Each aspect wraps the previous one
104        for registered in matching.iter().rev() {
105            let aspect = Arc::clone(&registered.aspect);
106            let inner_pjp = pjp;
107
108            // Create a new ProceedingJoinPoint that wraps the aspect application
109            pjp = ProceedingJoinPoint::new(
110                move || aspect.around(inner_pjp),
111                function_info_to_joinpoint(function),
112            );
113        }
114
115        pjp.proceed()
116    }
117
118    /// Get the number of registered aspects.
119    pub fn count(&self) -> usize {
120        self.aspects.read().unwrap().len()
121    }
122
123    /// Clear all registered aspects (useful for testing).
124    pub fn clear(&self) {
125        self.aspects.write().unwrap().clear();
126    }
127}
128
129/// Global aspect registry instance.
130///
131/// This is a singleton that can be accessed from anywhere in the program.
132pub static GLOBAL_REGISTRY: Lazy<AspectRegistry> = Lazy::new(AspectRegistry::new);
133
134/// Get a reference to the global aspect registry.
135///
136/// # Example
137///
138/// ```rust
139/// use aspect_runtime::registry::global_registry;
140///
141/// let registry = global_registry();
142/// println!("Registered aspects: {}", registry.count());
143/// ```
144pub fn global_registry() -> &'static AspectRegistry {
145    &GLOBAL_REGISTRY
146}
147
148/// Helper to convert FunctionInfo to JoinPoint
149fn 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        // Register in reverse order to test sorting
235        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        // Should match
265        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        // Should not match (wrong module)
274        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        // Should not match (not public)
283        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}