Skip to main content

composable_interceptor/
lib.rs

1//! Aspect-oriented programming for wasm components
2//!
3//! Given a target WIT world, creates an interceptor component that wraps
4//! exported functions with before and after advice hooks. The interceptor,
5//! target, and advice components can then be composed using standard wasm
6//! component component model tooling, such as `wac`.
7//!
8//! # Entry points
9//!
10//! - [`create_from_wit`]: create from a WIT path (no target component required)
11//! - [`create_from_component`]: create from a target component .wasm file
12
13pub(crate) mod builder;
14pub(crate) mod encoder;
15pub(crate) mod extractor;
16pub(crate) mod generator;
17pub(crate) mod matcher;
18pub(crate) mod types;
19
20use std::path::Path;
21
22use anyhow::Result;
23
24use matcher::Pattern;
25use types::*;
26
27/// Create an interceptor component for a WIT world.
28///
29/// - `wit_path`: path to WIT file or directory
30/// - `world`: world name whose exports define the interceptor contract
31/// - `patterns`: match patterns for selective interception (empty = intercept all)
32///
33/// Returns the validated component bytes.
34pub fn create_from_wit(wit_path: &Path, world: &str, patterns: &[&str]) -> Result<Vec<u8>> {
35    let patterns = parse_patterns(patterns)?;
36    let target = extractor::extract_from_wit(wit_path, world, &patterns)?;
37    create_and_validate(target)
38}
39
40/// Create an interceptor component from an existing component binary.
41///
42/// Extracts the world from the component's embedded WIT, then creates an interceptor
43/// for its exports.
44///
45/// - `component`: a valid wasm component binary
46/// - `patterns`: match patterns for selective interception (empty = intercept all)
47///
48/// Returns the validated component bytes.
49pub fn create_from_component(component: &[u8], patterns: &[&str]) -> Result<Vec<u8>> {
50    let patterns = parse_patterns(patterns)?;
51    let target = extractor::extract_from_wasm(component, &patterns)?;
52    create_and_validate(target)
53}
54
55fn parse_patterns(patterns: &[&str]) -> Result<Vec<Pattern>> {
56    patterns
57        .iter()
58        .map(|s| Pattern::parse(s))
59        .collect::<Result<Vec<_>>>()
60}
61
62fn create_and_validate(target: TargetWorld) -> Result<Vec<u8>> {
63    if target.exports.is_empty() {
64        anyhow::bail!(
65            "No exports found in world '{}'",
66            target.resolve().worlds[target.world_id].name
67        );
68    }
69
70    for we in &target.exports {
71        match we {
72            WorldExport::Interface(ie) => {
73                let intercepted = ie
74                    .functions
75                    .iter()
76                    .filter(|fe| matches!(fe, FunctionExport::Intercepted(_)))
77                    .count();
78                let bypassed = ie.functions.len() - intercepted;
79                tracing::info!(
80                    "{}: {} intercepted, {} bypassed",
81                    ie.full_name,
82                    intercepted,
83                    bypassed
84                );
85            }
86            WorldExport::Function(fe) => {
87                let status = match fe {
88                    FunctionExport::Intercepted(_) => "intercepted",
89                    FunctionExport::Bypassed(_) => "bypassed",
90                };
91                tracing::info!("{}: {status}", fe.name());
92            }
93        }
94    }
95
96    let component_bytes = create(&target)?;
97
98    wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all())
99        .validate_all(&component_bytes)
100        .map_err(|e| anyhow::anyhow!("Validation failed: {e}"))?;
101
102    tracing::info!("Validation passed");
103
104    Ok(component_bytes)
105}
106
107// Create an interceptor component: generate modules, then build the component.
108fn create(target: &TargetWorld) -> Result<Vec<u8>> {
109    let (intercepted, core_bytes, shim_bytes, fixup_bytes) = generator::generate_modules(target)?;
110
111    if intercepted.is_empty() {
112        anyhow::bail!("No intercepted functions");
113    }
114
115    builder::build(target, &intercepted, core_bytes, shim_bytes, fixup_bytes)
116}