//! 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>, } 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) -> 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 { 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 { 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]; 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() } }