Archived
123 lines
3.8 KiB
Rust
123 lines
3.8 KiB
Rust
//! Aspect chain — ordered execution of aspects around a method call.
|
|
//!
|
|
//! Aspects execute in order: each one wraps the next, forming a chain.
|
|
//! The innermost item is the actual method invocation.
|
|
//!
|
|
//! ```text
|
|
//! @authenticate → @authorize → @cache → @log → [method body]
|
|
//! before before check log
|
|
//! hit? ──→ return cached
|
|
//! miss? → [method body] → store → after-log
|
|
//! ```
|
|
//!
|
|
//! ## Security-by-default
|
|
//!
|
|
//! `AspectChain::with_default_auth()` prepends `AuthenticateAspect` to every
|
|
//! chain. Call this when building chains for non-`@public` functions.
|
|
|
|
use crate::{aspects::AuthenticateAspect, AopResult, Aspect, InvocationContext, InvocationResult, ProceedFn};
|
|
use std::sync::Arc;
|
|
|
|
/// An ordered chain of aspects applied to a single method.
|
|
pub struct AspectChain {
|
|
aspects: Vec<Arc<dyn Aspect>>,
|
|
}
|
|
|
|
impl AspectChain {
|
|
pub fn new() -> Self {
|
|
Self { aspects: Vec::new() }
|
|
}
|
|
|
|
/// Add an aspect to the end of the chain.
|
|
pub fn add(mut self, aspect: Arc<dyn Aspect>) -> Self {
|
|
self.aspects.push(aspect);
|
|
self
|
|
}
|
|
|
|
/// Number of aspects in this chain.
|
|
pub fn len(&self) -> usize {
|
|
self.aspects.len()
|
|
}
|
|
|
|
pub fn is_empty(&self) -> bool {
|
|
self.aspects.is_empty()
|
|
}
|
|
|
|
/// Execute the chain around the given proceed function.
|
|
///
|
|
/// Aspects run in order (left to right in the decorator list).
|
|
/// Each aspect's `around` method receives the next aspect's `around`
|
|
/// as the `proceed` function, forming a true onion model.
|
|
pub fn execute(
|
|
&self,
|
|
ctx: InvocationContext,
|
|
proceed: ProceedFn,
|
|
) -> AopResult<InvocationResult> {
|
|
if self.aspects.is_empty() {
|
|
return proceed(ctx);
|
|
}
|
|
self.run_aspect(0, ctx, proceed)
|
|
}
|
|
|
|
fn run_aspect(
|
|
&self,
|
|
index: usize,
|
|
ctx: InvocationContext,
|
|
final_proceed: ProceedFn,
|
|
) -> AopResult<InvocationResult> {
|
|
if index >= self.aspects.len() {
|
|
return final_proceed(ctx);
|
|
}
|
|
|
|
let aspect = self.aspects[index].clone();
|
|
let remaining_aspects = self.aspects[index + 1..].to_vec();
|
|
let final_proceed = Arc::new(final_proceed);
|
|
|
|
let next: ProceedFn = Box::new(move |ctx: InvocationContext| {
|
|
if remaining_aspects.is_empty() {
|
|
return final_proceed(ctx);
|
|
}
|
|
|
|
// Build remaining chain recursively
|
|
let sub_chain = AspectChain {
|
|
aspects: remaining_aspects.clone(),
|
|
};
|
|
sub_chain.execute(ctx, {
|
|
let fp = final_proceed.clone();
|
|
Box::new(move |ctx| fp(ctx))
|
|
})
|
|
});
|
|
|
|
aspect.around(ctx, &next)
|
|
}
|
|
|
|
/// Return the names of all aspects in this chain (in order).
|
|
pub fn aspect_names(&self) -> Vec<&str> {
|
|
self.aspects.iter().map(|a| a.name()).collect()
|
|
}
|
|
|
|
/// Prepend `AuthenticateAspect` to this chain.
|
|
///
|
|
/// This is the mechanism for security-by-default: the framework calls
|
|
/// `with_default_auth()` on every chain that does NOT have `@public`.
|
|
///
|
|
/// Equivalent to `.add(Arc::new(AuthenticateAspect))` at position 0, but
|
|
/// semantically explicit about what it means.
|
|
pub fn with_default_auth(self) -> Self {
|
|
let mut aspects = vec![Arc::new(AuthenticateAspect) as Arc<dyn Aspect>];
|
|
aspects.extend(self.aspects);
|
|
Self { aspects }
|
|
}
|
|
|
|
/// Returns `true` if the chain contains an `AuthenticateAspect`.
|
|
pub fn has_auth(&self) -> bool {
|
|
self.aspects.iter().any(|a| a.name() == "authenticate")
|
|
}
|
|
}
|
|
|
|
impl Default for AspectChain {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|