Skip to main content

camel_component_wasm/
lib.rs

1//! WebAssembly component for rust-camel — executes WASM modules as route processors via a Wasmtime runtime.
2//!
3//! Main types: `WasmComponent`, `WasmBundle`, `WasmConfig`, `WasmEndpoint`, `StateStore`.
4//! Main modules: `runtime`, `bindings`, `state_store`, `host_functions`.
5//!
6//! # Limitations
7//!
8//! - Only the [Wasmtime](https://wasmtime.dev) runtime is supported; other WASM runtimes
9//!   (Wasmer, WasmEdge, etc.) are not compatible.
10//! - WASM modules must export a specific interface defined by `camel_wasm_bindings`; arbitrary
11//!   WASM binaries cannot be dropped in without meeting this contract.
12//! - The WASM sandbox has no access to the host filesystem or network by default;
13//!   host functions are limited to what is explicitly exposed via `host_functions`.
14//! - Epoch-based fuel/interruption requires the `epoch` feature; long-running modules
15//!   without epoch support may block the async executor.
16
17pub mod authorization_policy;
18pub mod bean;
19pub mod bean_bindings;
20pub mod bindings;
21pub mod bundle;
22mod cancel_guard;
23pub mod capabilities;
24pub mod config;
25pub mod endpoint;
26pub mod epoch;
27pub mod error;
28pub mod health;
29pub mod host_functions;
30pub mod producer;
31mod return_stream;
32pub mod runtime;
33pub mod security_policy;
34pub mod security_policy_bindings;
35pub mod serde_bridge;
36pub mod source_bindings;
37pub mod source_consumer;
38pub mod source_host;
39pub mod state_store;
40pub mod stream_bridge;
41pub mod wasm_plugin_context;
42
43pub use authorization_policy::{WasmAuthorizationPolicyEvaluator, build_permission_registry};
44pub use bundle::WasmBundle;
45pub use config::WasmConfig;
46pub use endpoint::WasmEndpoint;
47pub use epoch::EpochTicker;
48pub use error::{TrapReason, WasmError};
49pub use health::WasmHealthCheck;
50pub use security_policy::{WasmSecurityPolicy, build_security_policy_registry};
51pub use state_store::StateStore;
52
53use std::path::PathBuf;
54use std::sync::Arc;
55use std::sync::atomic::AtomicBool;
56
57use camel_api::CamelError;
58use camel_component_api::{Component, ComponentContext, Endpoint};
59use camel_core::Registry;
60
61pub struct WasmComponent {
62    registry: Arc<std::sync::Mutex<Registry>>,
63    base_dir: PathBuf,
64    engine_loaded: Arc<AtomicBool>,
65}
66
67impl WasmComponent {
68    pub fn new(registry: Arc<std::sync::Mutex<Registry>>, base_dir: PathBuf) -> Self {
69        Self {
70            registry,
71            base_dir,
72            // TODO: set to true when WASM module is successfully loaded/instantiated
73            engine_loaded: Arc::new(AtomicBool::new(false)),
74        }
75    }
76
77    fn validate_and_resolve_path(&self, uri_path: &str) -> Result<PathBuf, CamelError> {
78        if PathBuf::from(uri_path).is_absolute() {
79            return Err(CamelError::InvalidUri(
80                "WASM path must be relative (not absolute)".to_string(),
81            ));
82        }
83
84        if PathBuf::from(uri_path)
85            .components()
86            .any(|c| matches!(c, std::path::Component::ParentDir))
87        {
88            return Err(CamelError::InvalidUri(
89                "WASM path must not contain '..'".to_string(),
90            ));
91        }
92
93        let resolved = self.base_dir.join(uri_path);
94        let canonical = resolved.canonicalize().map_err(|_| {
95            CamelError::ComponentNotFound(format!("WASM module not found: {}", resolved.display()))
96        })?;
97
98        let canonical_base = self.base_dir.canonicalize().map_err(|_| {
99            CamelError::EndpointCreationFailed(format!(
100                "failed to resolve base directory: {}",
101                self.base_dir.display()
102            ))
103        })?;
104
105        if !canonical.starts_with(&canonical_base) {
106            return Err(CamelError::InvalidUri(
107                "WASM path escapes project root".to_string(),
108            ));
109        }
110
111        Ok(canonical)
112    }
113}
114
115impl Component for WasmComponent {
116    fn scheme(&self) -> &str {
117        "wasm"
118    }
119
120    fn create_endpoint(
121        &self,
122        uri: &str,
123        ctx: &dyn ComponentContext,
124    ) -> Result<Box<dyn Endpoint>, CamelError> {
125        let uri_without_scheme = uri.strip_prefix("wasm:").ok_or_else(|| {
126            CamelError::InvalidUri(format!("WASM URI must start with 'wasm:': {uri}"))
127        })?;
128
129        let (path_part, wasm_config) = crate::config::WasmConfig::from_uri(uri_without_scheme);
130        if path_part.is_empty() {
131            return Err(CamelError::InvalidUri(
132                "WASM URI must include a module path".to_string(),
133            ));
134        }
135
136        let module_path = self.validate_and_resolve_path(&path_part)?;
137
138        let health_check = WasmHealthCheck::new(Arc::clone(&self.engine_loaded));
139        ctx.register_current_route_health_check(Arc::new(health_check));
140
141        Ok(Box::new(WasmEndpoint::new(
142            uri.to_string(),
143            module_path,
144            self.registry.clone(),
145            wasm_config,
146        )))
147    }
148}