camel_component_wasm/
lib.rs1pub mod authorization_policy;
18pub mod bean;
19pub mod bean_bindings;
20pub mod bindings;
21pub mod bundle;
22pub mod config;
23pub mod endpoint;
24pub mod epoch;
25pub mod error;
26pub mod health;
27pub mod host_functions;
28pub mod producer;
29pub mod runtime;
30pub mod security_policy;
31pub mod security_policy_bindings;
32pub mod serde_bridge;
33pub mod state_store;
34pub mod wasm_plugin_context;
35
36pub use authorization_policy::{WasmAuthorizationPolicyEvaluator, build_permission_registry};
37pub use bundle::WasmBundle;
38pub use config::WasmConfig;
39pub use endpoint::WasmEndpoint;
40pub use epoch::EpochTicker;
41pub use error::{TrapReason, WasmError};
42pub use health::WasmHealthCheck;
43pub use security_policy::{WasmSecurityPolicy, build_security_policy_registry};
44pub use state_store::StateStore;
45
46use std::path::PathBuf;
47use std::sync::Arc;
48use std::sync::atomic::AtomicBool;
49
50use camel_api::CamelError;
51use camel_component_api::{Component, ComponentContext, Endpoint};
52use camel_core::Registry;
53
54pub struct WasmComponent {
55 registry: Arc<std::sync::Mutex<Registry>>,
56 base_dir: PathBuf,
57 engine_loaded: Arc<AtomicBool>,
58}
59
60impl WasmComponent {
61 pub fn new(registry: Arc<std::sync::Mutex<Registry>>, base_dir: PathBuf) -> Self {
62 Self {
63 registry,
64 base_dir,
65 engine_loaded: Arc::new(AtomicBool::new(false)),
67 }
68 }
69
70 fn validate_and_resolve_path(&self, uri_path: &str) -> Result<PathBuf, CamelError> {
71 if PathBuf::from(uri_path).is_absolute() {
72 return Err(CamelError::InvalidUri(
73 "WASM path must be relative (not absolute)".to_string(),
74 ));
75 }
76
77 if PathBuf::from(uri_path)
78 .components()
79 .any(|c| matches!(c, std::path::Component::ParentDir))
80 {
81 return Err(CamelError::InvalidUri(
82 "WASM path must not contain '..'".to_string(),
83 ));
84 }
85
86 let resolved = self.base_dir.join(uri_path);
87 let canonical = resolved.canonicalize().map_err(|_| {
88 CamelError::ComponentNotFound(format!("WASM module not found: {}", resolved.display()))
89 })?;
90
91 let canonical_base = self.base_dir.canonicalize().map_err(|_| {
92 CamelError::EndpointCreationFailed(format!(
93 "failed to resolve base directory: {}",
94 self.base_dir.display()
95 ))
96 })?;
97
98 if !canonical.starts_with(&canonical_base) {
99 return Err(CamelError::InvalidUri(
100 "WASM path escapes project root".to_string(),
101 ));
102 }
103
104 Ok(canonical)
105 }
106}
107
108impl Component for WasmComponent {
109 fn scheme(&self) -> &str {
110 "wasm"
111 }
112
113 fn create_endpoint(
114 &self,
115 uri: &str,
116 ctx: &dyn ComponentContext,
117 ) -> Result<Box<dyn Endpoint>, CamelError> {
118 let uri_without_scheme = uri.strip_prefix("wasm:").ok_or_else(|| {
119 CamelError::InvalidUri(format!("WASM URI must start with 'wasm:': {uri}"))
120 })?;
121
122 let (path_part, wasm_config) = crate::config::WasmConfig::from_uri(uri_without_scheme);
123 if path_part.is_empty() {
124 return Err(CamelError::InvalidUri(
125 "WASM URI must include a module path".to_string(),
126 ));
127 }
128
129 let module_path = self.validate_and_resolve_path(&path_part)?;
130
131 let health_check = WasmHealthCheck::new(Arc::clone(&self.engine_loaded));
132 ctx.register_current_route_health_check(Arc::new(health_check));
133
134 Ok(Box::new(WasmEndpoint::new(
135 uri.to_string(),
136 module_path,
137 self.registry.clone(),
138 wasm_config,
139 )))
140 }
141}