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 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 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}