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;
37pub mod source_bindings;
38pub mod source_consumer;
39pub mod source_host;
40pub mod state_store;
41pub mod stream_bridge;
42mod wasi_surface;
43pub mod wasm_plugin_context;
44
45pub use authorization_policy::{WasmAuthorizationPolicyEvaluator, build_permission_registry};
46pub use bundle::WasmBundle;
47pub use config::WasmConfig;
48pub use endpoint::WasmEndpoint;
49pub use epoch::EpochTicker;
50pub use error::{TrapReason, WasmError};
51pub use health::WasmHealthCheck;
52pub use security_policy::{WasmSecurityPolicy, build_security_policy_registry};
53pub use state_store::StateStore;
54
55use std::path::PathBuf;
56use std::sync::Arc;
57use std::sync::atomic::AtomicBool;
58
59use camel_api::CamelError;
60use camel_component_api::{Component, ComponentContext, ComponentMetadata, Endpoint};
61use metadata::WasmMetadataDescriptor;
62
63pub struct WasmComponent {
64 registry: Arc<dyn ComponentContext>,
65 base_dir: PathBuf,
66 engine_loaded: Arc<AtomicBool>,
67}
68
69impl WasmComponent {
70 pub fn new(registry: Arc<dyn ComponentContext>, base_dir: PathBuf) -> Self {
71 Self {
72 registry,
73 base_dir,
74 engine_loaded: Arc::new(AtomicBool::new(false)),
76 }
77 }
78
79 fn validate_and_resolve_path(&self, uri_path: &str) -> Result<PathBuf, CamelError> {
80 if PathBuf::from(uri_path).is_absolute() {
81 return Err(CamelError::InvalidUri(
82 "WASM path must be relative (not absolute)".to_string(),
83 ));
84 }
85
86 if PathBuf::from(uri_path)
87 .components()
88 .any(|c| matches!(c, std::path::Component::ParentDir))
89 {
90 return Err(CamelError::InvalidUri(
91 "WASM path must not contain '..'".to_string(),
92 ));
93 }
94
95 let resolved = self.base_dir.join(uri_path);
96 let canonical = resolved.canonicalize().map_err(|_| {
97 CamelError::ComponentNotFound(format!("WASM module not found: {}", resolved.display()))
98 })?;
99
100 let canonical_base = self.base_dir.canonicalize().map_err(|_| {
101 CamelError::EndpointCreationFailed(format!(
102 "failed to resolve base directory: {}",
103 self.base_dir.display()
104 ))
105 })?;
106
107 if !canonical.starts_with(&canonical_base) {
108 return Err(CamelError::InvalidUri(
109 "WASM path escapes project root".to_string(),
110 ));
111 }
112
113 Ok(canonical)
114 }
115}
116
117impl Component for WasmComponent {
118 fn scheme(&self) -> &str {
119 "wasm"
120 }
121
122 fn metadata(&self) -> ComponentMetadata {
123 WasmMetadataDescriptor::metadata()
124 }
125
126 fn create_endpoint(
127 &self,
128 uri: &str,
129 ctx: &dyn ComponentContext,
130 ) -> Result<Box<dyn Endpoint>, CamelError> {
131 let uri_without_scheme = uri.strip_prefix("wasm:").ok_or_else(|| {
132 CamelError::InvalidUri(format!("WASM URI must start with 'wasm:': {uri}"))
133 })?;
134
135 let (path_part, wasm_config) = crate::config::WasmConfig::from_uri(uri_without_scheme);
136 if path_part.is_empty() {
137 return Err(CamelError::InvalidUri(
138 "WASM URI must include a module path".to_string(),
139 ));
140 }
141
142 let module_path = self.validate_and_resolve_path(&path_part)?;
143
144 let health_check = WasmHealthCheck::new(Arc::clone(&self.engine_loaded));
145 ctx.register_current_route_health_check(Arc::new(health_check));
146
147 Ok(Box::new(WasmEndpoint::new(
148 uri.to_string(),
149 module_path,
150 self.registry.clone(),
151 wasm_config,
152 )))
153 }
154}