Skip to main content

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}