aspect_core/joinpoint.rs
1//! JoinPoint and related types.
2//!
3//! A joinpoint represents a specific point in program execution where an aspect
4//! can be applied, such as a function call.
5
6use crate::error::AspectError;
7use std::any::Any;
8use std::fmt;
9
10/// Information about a specific point in program execution.
11///
12/// A `JoinPoint` provides context about where an aspect is being applied,
13/// including the function name, module path, and source location.
14///
15/// # Example
16///
17/// ```rust
18/// use aspect_core::prelude::*;
19///
20/// let jp = JoinPoint {
21/// function_name: "process_data",
22/// module_path: "my_app::data",
23/// location: Location {
24/// file: "src/data.rs",
25/// line: 42,
26/// },
27/// };
28///
29/// println!("Executing: {} at {}:{}",
30/// jp.function_name,
31/// jp.location.file,
32/// jp.location.line);
33/// ```
34#[derive(Debug, Clone)]
35pub struct JoinPoint {
36 /// The name of the function being called
37 pub function_name: &'static str,
38
39 /// The module path containing the function
40 pub module_path: &'static str,
41
42 /// Source code location information
43 pub location: Location,
44}
45
46impl JoinPoint {
47 /// Creates a new JoinPoint.
48 ///
49 /// # Example
50 ///
51 /// ```rust
52 /// use aspect_core::prelude::*;
53 ///
54 /// let jp = JoinPoint::new(
55 /// "my_function",
56 /// "my::module",
57 /// Location { file: "src/lib.rs", line: 100 },
58 /// );
59 /// ```
60 pub fn new(
61 function_name: &'static str,
62 module_path: &'static str,
63 location: Location,
64 ) -> Self {
65 Self {
66 function_name,
67 module_path,
68 location,
69 }
70 }
71
72 /// Returns the fully qualified name of the function.
73 ///
74 /// # Example
75 ///
76 /// ```rust
77 /// # use aspect_core::prelude::*;
78 /// # let jp = JoinPoint::new("func", "my::mod", Location { file: "a.rs", line: 1 });
79 /// assert_eq!(jp.qualified_name(), "my::mod::func");
80 /// ```
81 pub fn qualified_name(&self) -> String {
82 format!("{}::{}", self.module_path, self.function_name)
83 }
84}
85
86impl fmt::Display for JoinPoint {
87 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88 write!(
89 f,
90 "{}::{}@{}:{}",
91 self.module_path, self.function_name, self.location.file, self.location.line
92 )
93 }
94}
95
96/// Source code location information.
97///
98/// Indicates where in the source code a joinpoint occurs.
99#[derive(Debug, Clone, Copy)]
100pub struct Location {
101 /// The source file path
102 pub file: &'static str,
103
104 /// The line number in the source file
105 pub line: u32,
106}
107
108impl fmt::Display for Location {
109 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110 write!(f, "{}:{}", self.file, self.line)
111 }
112}
113
114/// A proceeding joinpoint that can be used in "around" advice.
115///
116/// This type wraps the original function execution and allows aspects to
117/// control when (or if) the function runs.
118///
119/// # Example
120///
121/// ```rust
122/// use aspect_core::prelude::*;
123/// use std::any::Any;
124///
125/// struct TimingAspect;
126///
127/// impl Aspect for TimingAspect {
128/// fn around(&self, pjp: ProceedingJoinPoint) -> Result<Box<dyn Any>, AspectError> {
129/// let start = std::time::Instant::now();
130/// let result = pjp.proceed()?;
131/// let elapsed = start.elapsed();
132/// println!("Execution took: {:?}", elapsed);
133/// Ok(result)
134/// }
135/// }
136/// ```
137pub struct ProceedingJoinPoint<'a> {
138 /// The original function to execute
139 inner: Box<dyn FnOnce() -> Result<Box<dyn Any>, AspectError> + 'a>,
140
141 /// Context information about this joinpoint
142 context: JoinPoint,
143}
144
145impl<'a> ProceedingJoinPoint<'a> {
146 /// Creates a new ProceedingJoinPoint.
147 ///
148 /// # Parameters
149 ///
150 /// - `f`: The original function to execute
151 /// - `context`: Information about the joinpoint
152 pub fn new<F>(f: F, context: JoinPoint) -> Self
153 where
154 F: FnOnce() -> Result<Box<dyn Any>, AspectError> + 'a,
155 {
156 Self {
157 inner: Box::new(f),
158 context,
159 }
160 }
161
162 /// Proceeds with the original function execution.
163 ///
164 /// This consumes the ProceedingJoinPoint and executes the wrapped function.
165 ///
166 /// # Returns
167 ///
168 /// The result of the function execution.
169 ///
170 /// # Example
171 ///
172 /// ```rust
173 /// # use aspect_core::prelude::*;
174 /// # use std::any::Any;
175 /// # struct MyAspect;
176 /// # impl Aspect for MyAspect {
177 /// fn around(&self, pjp: ProceedingJoinPoint) -> Result<Box<dyn Any>, AspectError> {
178 /// println!("Before proceed");
179 /// let result = pjp.proceed()?;
180 /// println!("After proceed");
181 /// Ok(result)
182 /// }
183 /// # }
184 /// ```
185 pub fn proceed(self) -> Result<Box<dyn Any>, AspectError> {
186 (self.inner)()
187 }
188
189 /// Returns a reference to the joinpoint context.
190 ///
191 /// # Example
192 ///
193 /// ```rust
194 /// # use aspect_core::prelude::*;
195 /// # use std::any::Any;
196 /// # struct MyAspect;
197 /// # impl Aspect for MyAspect {
198 /// fn around(&self, pjp: ProceedingJoinPoint) -> Result<Box<dyn Any>, AspectError> {
199 /// let ctx = pjp.context();
200 /// println!("Calling: {}", ctx.function_name);
201 /// pjp.proceed()
202 /// }
203 /// # }
204 /// ```
205 pub fn context(&self) -> &JoinPoint {
206 &self.context
207 }
208}
209
210impl<'a> fmt::Debug for ProceedingJoinPoint<'a> {
211 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212 f.debug_struct("ProceedingJoinPoint")
213 .field("context", &self.context)
214 .finish()
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221
222 #[test]
223 fn test_joinpoint_qualified_name() {
224 let jp = JoinPoint {
225 function_name: "my_func",
226 module_path: "crate::module",
227 location: Location {
228 file: "src/lib.rs",
229 line: 10,
230 },
231 };
232
233 assert_eq!(jp.qualified_name(), "crate::module::my_func");
234 }
235
236 #[test]
237 fn test_joinpoint_display() {
238 let jp = JoinPoint {
239 function_name: "test",
240 module_path: "mod",
241 location: Location {
242 file: "test.rs",
243 line: 42,
244 },
245 };
246
247 let display = format!("{}", jp);
248 assert!(display.contains("test"));
249 assert!(display.contains("mod"));
250 assert!(display.contains("test.rs"));
251 assert!(display.contains("42"));
252 }
253
254 #[test]
255 fn test_proceeding_joinpoint() {
256 let jp = JoinPoint {
257 function_name: "test",
258 module_path: "test",
259 location: Location {
260 file: "test.rs",
261 line: 1,
262 },
263 };
264
265 let pjp = ProceedingJoinPoint::new(
266 || Ok(Box::new(42) as Box<dyn Any>),
267 jp,
268 );
269
270 assert_eq!(pjp.context().function_name, "test");
271
272 let result = pjp.proceed().unwrap();
273 let value = result.downcast_ref::<i32>().unwrap();
274 assert_eq!(*value, 42);
275 }
276}