Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Extending the Framework

aspect-rs is designed for extensibility. This chapter shows how to create custom aspects, custom pointcuts, and extend the framework for specialized needs.

Creating Custom Aspects

Basic Custom Aspect

The simplest extension is creating a custom aspect:

#![allow(unused)]
fn main() {
use aspect_core::prelude::*;
use std::any::Any;

pub struct MyCustomAspect {
    config: MyConfig,
}

impl Aspect for MyCustomAspect {
    fn before(&self, ctx: &JoinPoint) {
        // Custom logic before function execution
        log_to_external_service(ctx.function_name, &self.config);
    }

    fn after(&self, ctx: &JoinPoint, result: &dyn Any) {
        // Custom logic after successful execution
        track_success_metric(ctx.function_name);
    }

    fn after_error(&self, ctx: &JoinPoint, error: &AspectError) {
        // Custom error handling
        alert_on_call(ctx, error);
    }
}
}

Stateful Aspects

Aspects with internal state using thread-safe structures:

#![allow(unused)]
fn main() {
use std::sync::{Arc, Mutex};
use std::collections::HashMap;

pub struct CallCounterAspect {
    counts: Arc<Mutex<HashMap<String, u64>>>,
}

impl Aspect for CallCounterAspect {
    fn before(&self, ctx: &JoinPoint) {
        let mut counts = self.counts.lock().unwrap();
        *counts.entry(ctx.function_name.to_string())
            .or_insert(0) += 1;
    }
}

impl CallCounterAspect {
    pub fn get_count(&self, function_name: &str) -> u64 {
        self.counts.lock().unwrap()
            .get(function_name)
            .copied()
            .unwrap_or(0)
    }
}
}

Generic Aspects

Type-safe generic aspects:

#![allow(unused)]
fn main() {
pub struct ValidationAspect<T: Validator> {
    validator: T,
}

pub trait Validator: Send + Sync {
    fn validate(&self, ctx: &JoinPoint) -> Result<(), String>;
}

impl<T: Validator> Aspect for ValidationAspect<T> {
    fn before(&self, ctx: &JoinPoint) {
        if let Err(e) = self.validator.validate(ctx) {
            panic!("Validation failed: {}", e);
        }
    }
}
}

Custom Pointcuts

Implementing PointcutMatcher

Create custom pattern matching logic:

#![allow(unused)]
fn main() {
use aspect_core::pointcut::PointcutMatcher;

pub struct AnnotationPointcut {
    annotation_name: String,
}

impl PointcutMatcher for AnnotationPointcut {
    fn matches(&self, ctx: &JoinPoint) -> bool {
        // Custom matching logic
        // (In real implementation, would check function annotations)
        ctx.module_path.contains(&self.annotation_name)
    }
}
}

Complex Pointcut Patterns

Combine multiple matching criteria:

#![allow(unused)]
fn main() {
pub struct ComplexPointcut {
    matchers: Vec<Box<dyn PointcutMatcher>>,
    combinator: Combinator,
}

pub enum Combinator {
    And,
    Or,
    Not,
}

impl PointcutMatcher for ComplexPointcut {
    fn matches(&self, ctx: &JoinPoint) -> bool {
        match self.combinator {
            Combinator::And => {
                self.matchers.iter().all(|m| m.matches(ctx))
            }
            Combinator::Or => {
                self.matchers.iter().any(|m| m.matches(ctx))
            }
            Combinator::Not => {
                !self.matchers[0].matches(ctx)
            }
        }
    }
}
}

Extending Code Generation

Custom Macro Attributes

Create domain-specific macro attributes:

#![allow(unused)]
fn main() {
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemFn};

#[proc_macro_attribute]
pub fn monitored(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let func = parse_macro_input!(item as ItemFn);
    let func_name = &func.sig.ident;
    let block = &func.block;

    // Generate custom wrapper
    let output = quote! {
        fn #func_name() {
            let _guard = MonitorGuard::new(stringify!(#func_name));
            #block
        }
    };

    output.into()
}
}

Custom Code Generators

Extend aspect-weaver for specialized code generation:

#![allow(unused)]
fn main() {
pub trait AspectCodeGenerator {
    fn generate_before(&self, func: &ItemFn) -> TokenStream {
        // Default implementation
        quote! {}
    }

    fn generate_after(&self, func: &ItemFn) -> TokenStream {
        quote! {}
    }

    fn generate_around(&self, func: &ItemFn) -> TokenStream {
        quote! {
            #func
        }
    }
}

pub struct OptimizingGenerator {
    inline_threshold: usize,
}

impl AspectCodeGenerator for OptimizingGenerator {
    fn generate_around(&self, func: &ItemFn) -> TokenStream {
        let should_inline = estimate_size(func) < self.inline_threshold;

        if should_inline {
            quote! {
                #[inline(always)]
                #func
            }
        } else {
            quote! {
                #[inline(never)]
                #func
            }
        }
    }
}
}

Domain-Specific Extensions

Database Aspects

Custom aspects for database operations:

#![allow(unused)]
fn main() {
pub struct TransactionAspect {
    isolation_level: IsolationLevel,
}

impl Aspect for TransactionAspect {
    fn around(&self, pjp: ProceedingJoinPoint)
        -> Result<Box<dyn Any>, AspectError>
    {
        let conn = get_connection()?;
        conn.begin_transaction(self.isolation_level)?;

        match pjp.proceed() {
            Ok(result) => {
                conn.commit()?;
                Ok(result)
            }
            Err(e) => {
                conn.rollback()?;
                Err(e)
            }
        }
    }
}
}

HTTP/API Aspects

Aspects for web services:

#![allow(unused)]
fn main() {
pub struct RateLimitAspect {
    max_requests: usize,
    window: Duration,
    limiter: Arc<Mutex<RateLimiter>>,
}

impl Aspect for RateLimitAspect {
    fn before(&self, ctx: &JoinPoint) -> Result<(), AspectError> {
        let mut limiter = self.limiter.lock().unwrap();

        if !limiter.check_rate_limit(ctx.function_name) {
            return Err(AspectError::execution(
                format!("Rate limit exceeded for {}", ctx.function_name)
            ));
        }

        Ok(())
    }
}
}

Security Aspects

Authorization and authentication:

#![allow(unused)]
fn main() {
pub struct AuthorizationAspect {
    required_roles: Vec<Role>,
}

impl Aspect for AuthorizationAspect {
    fn before(&self, ctx: &JoinPoint) -> Result<(), AspectError> {
        let current_user = get_current_user()?;

        if !current_user.has_any_role(&self.required_roles) {
            return Err(AspectError::execution(
                format!(
                    "User {} lacks required roles for {}",
                    current_user.id,
                    ctx.function_name
                )
            ));
        }

        Ok(())
    }
}
}

Plugin Architecture

Third-Party Aspect Crates

Structure for distributable aspects:

#![allow(unused)]
fn main() {
// my-custom-aspects/src/lib.rs
pub mod database;
pub mod monitoring;
pub mod security;

pub use database::TransactionAspect;
pub use monitoring::MetricsAspect;
pub use security::AuthAspect;

pub mod prelude {
    pub use super::*;
    pub use aspect_core::prelude::*;
}
}

Users can then:

[dependencies]
aspect-core = "0.1"
aspect-macros = "0.1"
my-custom-aspects = "1.0"
#![allow(unused)]
fn main() {
use my_custom_aspects::prelude::*;

#[aspect(TransactionAspect::new(IsolationLevel::ReadCommitted))]
fn update_balance(account_id: u64, amount: i64) -> Result<()> {
    // ...
}
}

Integration Points

Custom Backends

Integrate with external systems:

#![allow(unused)]
fn main() {
pub trait LogBackend: Send + Sync {
    fn log(&self, level: LogLevel, message: &str);
}

pub struct CloudWatchBackend {
    client: CloudWatchClient,
}

impl LogBackend for CloudWatchBackend {
    fn log(&self, level: LogLevel, message: &str) {
        self.client.put_log_event(level, message);
    }
}

pub struct LoggingAspect<B: LogBackend> {
    backend: Arc<B>,
}

impl<B: LogBackend> Aspect for LoggingAspect<B> {
    fn before(&self, ctx: &JoinPoint) {
        self.backend.log(
            LogLevel::Info,
            &format!("[ENTRY] {}", ctx.function_name)
        );
    }
}
}

Metrics Integration

Connect to monitoring systems:

#![allow(unused)]
fn main() {
pub trait MetricsReporter: Send + Sync {
    fn report_call(&self, function: &str, duration: Duration);
    fn report_error(&self, function: &str, error: &AspectError);
}

pub struct PrometheusReporter {
    registry: Registry,
}

impl MetricsReporter for PrometheusReporter {
    fn report_call(&self, function: &str, duration: Duration) {
        FUNCTION_DURATION
            .with_label_values(&[function])
            .observe(duration.as_secs_f64());
    }

    fn report_error(&self, function: &str, error: &AspectError) {
        ERROR_COUNTER
            .with_label_values(&[function])
            .inc();
    }
}
}

Best Practices

1. Keep Aspects Focused

Each aspect should have a single responsibility:

#![allow(unused)]
fn main() {
// GOOD: Focused aspect
pub struct TimingAspect { ... }

// AVOID: Kitchen sink aspect
pub struct EverythingAspect {
    logger: Logger,
    timer: Timer,
    cache: Cache,
    metrics: Metrics,
}
}

2. Make Aspects Configurable

Use builder pattern for complex configuration:

#![allow(unused)]
fn main() {
pub struct RetryAspect {
    max_attempts: usize,
    backoff: BackoffStrategy,
    retry_on: Vec<ErrorKind>,
}

impl RetryAspect {
    pub fn builder() -> RetryAspectBuilder {
        RetryAspectBuilder::default()
    }
}

pub struct RetryAspectBuilder {
    max_attempts: usize,
    backoff: BackoffStrategy,
    retry_on: Vec<ErrorKind>,
}

impl RetryAspectBuilder {
    pub fn max_attempts(mut self, n: usize) -> Self {
        self.max_attempts = n;
        self
    }

    pub fn with_backoff(mut self, strategy: BackoffStrategy) -> Self {
        self.backoff = strategy;
        self
    }

    pub fn build(self) -> RetryAspect {
        RetryAspect {
            max_attempts: self.max_attempts,
            backoff: self.backoff,
            retry_on: self.retry_on,
        }
    }
}

// Usage
#[aspect(RetryAspect::builder()
    .max_attempts(3)
    .with_backoff(BackoffStrategy::Exponential)
    .build())]
fn fetch_data() -> Result<Data> { ... }
}

3. Document Performance Impact

Include performance characteristics in documentation:

#![allow(unused)]
fn main() {
/// Transaction aspect for database operations.
///
/// # Performance
///
/// - Overhead: ~50-100µs per transaction
/// - Memory: ~200 bytes per connection
/// - Allocations: 2 per begin/commit cycle
///
/// Use only on functions that perform database operations.
pub struct TransactionAspect { ... }
}

4. Provide Examples

Include usage examples in documentation:

#![allow(unused)]
fn main() {
/// # Examples
///
/// ```rust
/// use my_aspects::TransactionAspect;
///
/// #[aspect(TransactionAspect::new(IsolationLevel::ReadCommitted))]
/// fn transfer_funds(from: u64, to: u64, amount: i64) -> Result<()> {
///     // Database operations
/// }
/// ```
pub struct TransactionAspect { ... }
}

Testing Extensions

Unit Testing Aspects

#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_custom_aspect() {
        let aspect = MyCustomAspect::new();
        let ctx = JoinPoint {
            function_name: "test_function",
            module_path: "test::module",
            location: Location {
                file: "test.rs",
                line: 42,
            },
        };

        aspect.before(&ctx);
        // Assert expected behavior
    }
}
}

Integration Testing

#![allow(unused)]
fn main() {
#[test]
fn test_aspect_integration() {
    #[aspect(CounterAspect::new())]
    fn test_func() -> i32 {
        42
    }

    let result = test_func();
    assert_eq!(result, 42);

    let count = COUNTER_ASPECT.get_count("test_func");
    assert_eq!(count, 1);
}
}

See Also