Skip to main content

aspect_core/
aspect.rs

1//! Aspect trait definition.
2//!
3//! The `Aspect` trait is the core abstraction for defining cross-cutting concerns
4//! in aspect-oriented programming.
5
6use crate::error::AspectError;
7use crate::joinpoint::{JoinPoint, ProceedingJoinPoint};
8use std::any::Any;
9
10/// The core trait for defining aspects.
11///
12/// An aspect encapsulates cross-cutting concerns that can be applied to multiple
13/// joinpoints in your program. Implement this trait to define custom aspects.
14///
15/// # Thread Safety
16///
17/// Aspects must be `Send + Sync` to be used across thread boundaries. This ensures
18/// that aspects can be safely shared between threads.
19///
20/// # Advice Methods
21///
22/// - **`before`**: Executed before the target function runs
23/// - **`after`**: Executed after successful completion
24/// - **`after_error`**: Executed when an error occurs
25/// - **`around`**: Wraps the entire execution, allowing you to control when/if
26///   the target function runs
27///
28/// # Example
29///
30/// ```rust
31/// use aspect_core::prelude::*;
32/// use std::any::Any;
33///
34/// #[derive(Default)]
35/// struct LoggingAspect;
36///
37/// impl Aspect for LoggingAspect {
38///     fn before(&self, ctx: &JoinPoint) {
39///         println!("[LOG] Entering function: {}", ctx.function_name);
40///     }
41///
42///     fn after(&self, ctx: &JoinPoint, _result: &dyn Any) {
43///         println!("[LOG] Exiting function: {}", ctx.function_name);
44///     }
45///
46///     fn after_error(&self, ctx: &JoinPoint, error: &AspectError) {
47///         eprintln!("[ERROR] Function {} failed: {:?}", ctx.function_name, error);
48///     }
49/// }
50/// ```
51pub trait Aspect: Send + Sync {
52    /// Advice executed before the target function runs.
53    ///
54    /// # Parameters
55    ///
56    /// - `ctx`: Context information about the joinpoint
57    ///
58    /// # Example
59    ///
60    /// ```rust
61    /// # use aspect_core::prelude::*;
62    /// # struct MyAspect;
63    /// # impl Aspect for MyAspect {
64    /// fn before(&self, ctx: &JoinPoint) {
65    ///     println!("About to call: {}", ctx.function_name);
66    /// }
67    /// # }
68    /// ```
69    fn before(&self, _ctx: &JoinPoint) {}
70
71    /// Advice executed after the target function completes successfully.
72    ///
73    /// # Parameters
74    ///
75    /// - `ctx`: Context information about the joinpoint
76    /// - `result`: The return value of the function (as `&dyn Any`)
77    ///
78    /// # Example
79    ///
80    /// ```rust
81    /// # use aspect_core::prelude::*;
82    /// # use std::any::Any;
83    /// # struct MyAspect;
84    /// # impl Aspect for MyAspect {
85    /// fn after(&self, ctx: &JoinPoint, result: &dyn Any) {
86    ///     println!("Function {} completed", ctx.function_name);
87    ///     // You can downcast result to access the actual value
88    ///     if let Some(value) = result.downcast_ref::<i32>() {
89    ///         println!("Returned value: {}", value);
90    ///     }
91    /// }
92    /// # }
93    /// ```
94    fn after(&self, _ctx: &JoinPoint, _result: &dyn Any) {}
95
96    /// Advice executed when the target function encounters an error.
97    ///
98    /// # Parameters
99    ///
100    /// - `ctx`: Context information about the joinpoint
101    /// - `error`: The error that occurred
102    ///
103    /// # Example
104    ///
105    /// ```rust
106    /// # use aspect_core::prelude::*;
107    /// # struct MyAspect;
108    /// # impl Aspect for MyAspect {
109    /// fn after_error(&self, ctx: &JoinPoint, error: &AspectError) {
110    ///     eprintln!("Error in {}: {:?}", ctx.function_name, error);
111    ///     // Log to monitoring system, send alerts, etc.
112    /// }
113    /// # }
114    /// ```
115    fn after_error(&self, _ctx: &JoinPoint, _error: &AspectError) {}
116
117    /// Advice that wraps the entire target function execution.
118    ///
119    /// This is the most powerful advice type, allowing you to:
120    /// - Control whether the target function runs
121    /// - Modify the execution flow
122    /// - Implement retry logic, caching, etc.
123    ///
124    /// The default implementation simply proceeds with the original function.
125    ///
126    /// # Parameters
127    ///
128    /// - `pjp`: A proceeding joinpoint that can be used to execute the target function
129    ///
130    /// # Returns
131    ///
132    /// The result of the function execution (or a modified result)
133    ///
134    /// # Example
135    ///
136    /// ```rust
137    /// # use aspect_core::prelude::*;
138    /// # use std::any::Any;
139    /// # struct CachingAspect;
140    /// # impl Aspect for CachingAspect {
141    /// fn around(&self, pjp: ProceedingJoinPoint) -> Result<Box<dyn Any>, AspectError> {
142    ///     let function_name = pjp.context().function_name;
143    ///     println!("Before: {}", function_name);
144    ///
145    ///     // Execute the function
146    ///     let result = pjp.proceed();
147    ///
148    ///     println!("After: {}", function_name);
149    ///     result
150    /// }
151    /// # }
152    /// ```
153    fn around(&self, pjp: ProceedingJoinPoint) -> Result<Box<dyn Any>, AspectError> {
154        // Default implementation: call before, proceed, then after/after_error
155        let ctx = pjp.context().clone();
156
157        self.before(&ctx);
158
159        let result = pjp.proceed();
160
161        match &result {
162            Ok(value) => {
163                self.after(&ctx, value.as_ref());
164            }
165            Err(error) => {
166                self.after_error(&ctx, error);
167            }
168        }
169
170        result
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177
178    #[derive(Default)]
179    struct CountingAspect {
180        before_count: std::sync::Arc<std::sync::atomic::AtomicUsize>,
181        after_count: std::sync::Arc<std::sync::atomic::AtomicUsize>,
182    }
183
184    impl Aspect for CountingAspect {
185        fn before(&self, _ctx: &JoinPoint) {
186            self.before_count
187                .fetch_add(1, std::sync::atomic::Ordering::SeqCst);
188        }
189
190        fn after(&self, _ctx: &JoinPoint, _result: &dyn Any) {
191            self.after_count
192                .fetch_add(1, std::sync::atomic::Ordering::SeqCst);
193        }
194    }
195
196    #[test]
197    fn test_aspect_is_send_sync() {
198        fn assert_send<T: Send>() {}
199        fn assert_sync<T: Sync>() {}
200
201        assert_send::<CountingAspect>();
202        assert_sync::<CountingAspect>();
203    }
204}