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}