Skip to main content

aspect_core/pointcut/
matcher.rs

1//! Pointcut matching logic.
2
3use super::ast::Pointcut;
4use super::pattern::{ExecutionPattern, ModulePattern};
5
6/// Information about a function for pointcut matching.
7#[derive(Debug, Clone)]
8pub struct FunctionInfo {
9    /// Function name
10    pub name: String,
11
12    /// Module path (e.g., "crate::api::users")
13    pub module_path: String,
14
15    /// Visibility as a string ("pub", "pub(crate)", etc.)
16    pub visibility: String,
17
18    /// Return type as a string (simplified)
19    pub return_type: Option<String>,
20}
21
22impl FunctionInfo {
23    /// Create function info for testing.
24    pub fn new(
25        name: impl Into<String>,
26        module_path: impl Into<String>,
27        visibility: impl Into<String>,
28    ) -> Self {
29        Self {
30            name: name.into(),
31            module_path: module_path.into(),
32            visibility: visibility.into(),
33            return_type: None,
34        }
35    }
36
37    /// Set the return type.
38    pub fn with_return_type(mut self, return_type: impl Into<String>) -> Self {
39        self.return_type = Some(return_type.into());
40        self
41    }
42}
43
44/// Matcher trait for evaluating pointcuts against functions.
45pub trait Matcher {
46    /// Check if this pointcut matches the given function.
47    fn matches(&self, function: &FunctionInfo) -> bool;
48}
49
50impl Matcher for Pointcut {
51    fn matches(&self, function: &FunctionInfo) -> bool {
52        match self {
53            Pointcut::Execution(pattern) => pattern.matches(function),
54            Pointcut::Within(pattern) => pattern.matches(function),
55            Pointcut::And(left, right) => left.matches(function) && right.matches(function),
56            Pointcut::Or(left, right) => left.matches(function) || right.matches(function),
57            Pointcut::Not(inner) => !inner.matches(function),
58        }
59    }
60}
61
62impl Matcher for ExecutionPattern {
63    fn matches(&self, function: &FunctionInfo) -> bool {
64        // Check visibility
65        if let Some(ref vis) = self.visibility {
66            if !vis.matches(&function.visibility) {
67                return false;
68            }
69        }
70
71        // Check name
72        if !self.name.matches(&function.name) {
73            return false;
74        }
75
76        // Check return type (simplified string matching for now)
77        if let Some(ref expected_return) = self.return_type {
78            match &function.return_type {
79                Some(actual_return) if actual_return.contains(expected_return) => {}
80                _ => return false,
81            }
82        }
83
84        true
85    }
86}
87
88impl Matcher for ModulePattern {
89    fn matches(&self, function: &FunctionInfo) -> bool {
90        self.matches_path(&function.module_path)
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97    use crate::pointcut::pattern::{NamePattern, Visibility};
98
99    #[test]
100    fn test_execution_pattern_matching() {
101        let pattern = ExecutionPattern {
102            visibility: Some(Visibility::Public),
103            name: NamePattern::Exact("save_user".to_string()),
104            return_type: None,
105        };
106
107        let func1 = FunctionInfo::new("save_user", "crate::api", "pub");
108        assert!(pattern.matches(&func1));
109
110        let func2 = FunctionInfo::new("update_user", "crate::api", "pub");
111        assert!(!pattern.matches(&func2));
112
113        let func3 = FunctionInfo::new("save_user", "crate::api", "");
114        assert!(!pattern.matches(&func3)); // Not public
115    }
116
117    #[test]
118    fn test_module_pattern_matching() {
119        let pattern = ModulePattern::new("crate::api");
120
121        let func1 = FunctionInfo::new("save", "crate::api", "pub");
122        assert!(pattern.matches(&func1));
123
124        let func2 = FunctionInfo::new("save", "crate::api::users", "pub");
125        assert!(pattern.matches(&func2));
126
127        let func3 = FunctionInfo::new("save", "crate::internal", "pub");
128        assert!(!pattern.matches(&func3));
129    }
130
131    #[test]
132    fn test_pointcut_and() {
133        let exec = ExecutionPattern {
134            visibility: Some(Visibility::Public),
135            name: NamePattern::Wildcard,
136            return_type: None,
137        };
138
139        let within = ModulePattern::new("crate::api");
140
141        let pointcut = Pointcut::Execution(exec).and(Pointcut::Within(within));
142
143        let func1 = FunctionInfo::new("save", "crate::api", "pub");
144        assert!(pointcut.matches(&func1));
145
146        let func2 = FunctionInfo::new("save", "crate::api", "");
147        assert!(!pointcut.matches(&func2)); // Not public
148
149        let func3 = FunctionInfo::new("save", "crate::internal", "pub");
150        assert!(!pointcut.matches(&func3)); // Wrong module
151    }
152
153    #[test]
154    fn test_pointcut_or() {
155        let pattern1 = ExecutionPattern::named("save");
156        let pattern2 = ExecutionPattern::named("update");
157
158        let pointcut = Pointcut::Execution(pattern1).or(Pointcut::Execution(pattern2));
159
160        let func1 = FunctionInfo::new("save", "crate::api", "pub");
161        assert!(pointcut.matches(&func1));
162
163        let func2 = FunctionInfo::new("update", "crate::api", "pub");
164        assert!(pointcut.matches(&func2));
165
166        let func3 = FunctionInfo::new("delete", "crate::api", "pub");
167        assert!(!pointcut.matches(&func3));
168    }
169
170    #[test]
171    fn test_pointcut_not() {
172        let pattern = ExecutionPattern {
173            visibility: Some(Visibility::Public),
174            name: NamePattern::Wildcard,
175            return_type: None,
176        };
177
178        let pointcut = Pointcut::Execution(pattern).not();
179
180        let func1 = FunctionInfo::new("save", "crate::api", "pub");
181        assert!(!pointcut.matches(&func1)); // Public functions excluded
182
183        let func2 = FunctionInfo::new("save", "crate::internal", "");
184        assert!(pointcut.matches(&func2)); // Private functions match
185    }
186}