camel_component_wasm/
lib.rs1pub 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(crate) mod metadata;
31pub mod producer;
32mod return_stream;
33pub mod runtime;
34pub mod security_policy;
35pub mod security_policy_bindings;
36pub mod serde_bridge;
37mod source_auth_edge;
38pub mod source_bindings;
39pub mod source_consumer;
40pub mod source_host;
41pub mod staged_listener;
42pub mod state_store;
43pub mod stream_bridge;
44mod wasi_surface;
45pub mod wasm_plugin_context;
46
47pub use authorization_policy::{WasmAuthorizationPolicyEvaluator, build_permission_registry};
48pub use bundle::WasmBundle;
49pub use config::WasmConfig;
50pub use endpoint::WasmEndpoint;
51pub use epoch::EpochTicker;
52pub use error::{TrapReason, WasmError};
53pub use health::WasmHealthCheck;
54pub use security_policy::{WasmSecurityPolicy, build_security_policy_registry};
55pub use state_store::StateStore;
56
57use std::collections::HashMap;
58use std::path::PathBuf;
59use std::sync::Arc;
60use std::sync::OnceLock;
61use std::sync::RwLock;
62use std::sync::atomic::AtomicBool;
63
64use camel_api::CamelError;
65use camel_component_api::{Component, ComponentContext, ComponentMetadata, Endpoint};
66use metadata::WasmMetadataDescriptor;
67
68pub struct WasmComponent {
69 registry: Arc<dyn ComponentContext>,
70 base_dir: PathBuf,
71 engine_loaded: Arc<AtomicBool>,
72}
73
74impl WasmComponent {
75 pub fn new(registry: Arc<dyn ComponentContext>, base_dir: PathBuf) -> Self {
76 Self {
77 registry,
78 base_dir,
79 engine_loaded: Arc::new(AtomicBool::new(false)),
81 }
82 }
83
84 fn validate_and_resolve_path(&self, uri_path: &str) -> Result<PathBuf, CamelError> {
85 if PathBuf::from(uri_path).is_absolute() {
86 return Err(CamelError::InvalidUri(
87 "WASM path must be relative (not absolute)".to_string(),
88 ));
89 }
90
91 if PathBuf::from(uri_path)
92 .components()
93 .any(|c| matches!(c, std::path::Component::ParentDir))
94 {
95 return Err(CamelError::InvalidUri(
96 "WASM path must not contain '..'".to_string(),
97 ));
98 }
99
100 let resolved = self.base_dir.join(uri_path);
101 let canonical = resolved.canonicalize().map_err(|_| {
102 CamelError::ComponentNotFound(format!("WASM module not found: {}", resolved.display()))
103 })?;
104
105 let canonical_base = self.base_dir.canonicalize().map_err(|_| {
106 CamelError::EndpointCreationFailed(format!(
107 "failed to resolve base directory: {}",
108 self.base_dir.display()
109 ))
110 })?;
111
112 if !canonical.starts_with(&canonical_base) {
113 return Err(CamelError::InvalidUri(
114 "WASM path escapes project root".to_string(),
115 ));
116 }
117
118 Ok(canonical)
119 }
120}
121
122impl Component for WasmComponent {
123 fn scheme(&self) -> &str {
124 "wasm"
125 }
126
127 fn metadata(&self) -> ComponentMetadata {
128 WasmMetadataDescriptor::metadata()
129 }
130
131 fn create_endpoint(
132 &self,
133 uri: &str,
134 ctx: &dyn ComponentContext,
135 ) -> Result<Box<dyn Endpoint>, CamelError> {
136 let uri_without_scheme = uri.strip_prefix("wasm:").ok_or_else(|| {
137 CamelError::InvalidUri(format!("WASM URI must start with 'wasm:': {uri}"))
138 })?;
139
140 let (path_part, wasm_config) = crate::config::WasmConfig::from_uri(uri_without_scheme);
141 if path_part.is_empty() {
142 return Err(CamelError::InvalidUri(
143 "WASM URI must include a module path".to_string(),
144 ));
145 }
146
147 let module_path = self.validate_and_resolve_path(&path_part)?;
148
149 let health_check = WasmHealthCheck::new(Arc::clone(&self.engine_loaded));
150 ctx.register_current_route_health_check(Arc::new(health_check));
151
152 Ok(Box::new(WasmEndpoint::new(
153 uri.to_string(),
154 module_path,
155 self.registry.clone(),
156 wasm_config,
157 )))
158 }
159}
160
161pub struct WasmSourceBindAcks {
170 acks: OnceLock<RwLock<HashMap<String, bool>>>,
171}
172
173impl WasmSourceBindAcks {
174 pub fn global() -> &'static Self {
176 static INSTANCE: OnceLock<WasmSourceBindAcks> = OnceLock::new();
177 INSTANCE.get_or_init(|| WasmSourceBindAcks {
178 acks: OnceLock::new(),
179 })
180 }
181
182 pub fn set(&self, acks: HashMap<String, bool>) {
186 let lock = self.acks.get_or_init(|| RwLock::new(HashMap::new()));
187 *lock
188 .write()
189 .unwrap_or_else(|poisoned| poisoned.into_inner()) = acks;
190 }
191
192 pub fn acknowledged(&self, bind: &str) -> bool {
196 let lock = match self.acks.get() {
197 Some(lock) => lock,
198 None => return false,
199 };
200 lock.read()
201 .unwrap_or_else(|poisoned| poisoned.into_inner())
202 .get(bind)
203 .copied()
204 .unwrap_or(false)
205 }
206}