camel_component_wasm/
lib.rs1pub mod bean;
2pub mod bean_bindings;
3pub mod bindings;
4pub mod bundle;
5pub mod config;
6pub mod endpoint;
7pub mod epoch;
8pub mod error;
9pub mod host_functions;
10pub mod producer;
11pub mod runtime;
12pub mod serde_bridge;
13pub mod state_store;
14
15pub use bundle::WasmBundle;
16pub use config::WasmConfig;
17pub use endpoint::WasmEndpoint;
18pub use epoch::EpochTicker;
19pub use error::{TrapReason, WasmError};
20pub use state_store::StateStore;
21
22use std::path::PathBuf;
23use std::sync::Arc;
24
25use camel_api::CamelError;
26use camel_component_api::{Component, ComponentContext, Endpoint};
27use camel_core::Registry;
28
29pub struct WasmComponent {
30 registry: Arc<std::sync::Mutex<Registry>>,
31 base_dir: PathBuf,
32}
33
34impl WasmComponent {
35 pub fn new(registry: Arc<std::sync::Mutex<Registry>>, base_dir: PathBuf) -> Self {
36 Self { registry, base_dir }
37 }
38
39 fn validate_and_resolve_path(&self, uri_path: &str) -> Result<PathBuf, CamelError> {
40 if PathBuf::from(uri_path).is_absolute() {
41 return Err(CamelError::InvalidUri(
42 "WASM path must be relative (not absolute)".to_string(),
43 ));
44 }
45
46 if PathBuf::from(uri_path)
47 .components()
48 .any(|c| matches!(c, std::path::Component::ParentDir))
49 {
50 return Err(CamelError::InvalidUri(
51 "WASM path must not contain '..'".to_string(),
52 ));
53 }
54
55 let resolved = self.base_dir.join(uri_path);
56 let canonical = resolved.canonicalize().map_err(|_| {
57 CamelError::ComponentNotFound(format!("WASM module not found: {}", resolved.display()))
58 })?;
59
60 let canonical_base = self.base_dir.canonicalize().map_err(|_| {
61 CamelError::EndpointCreationFailed(format!(
62 "failed to resolve base directory: {}",
63 self.base_dir.display()
64 ))
65 })?;
66
67 if !canonical.starts_with(&canonical_base) {
68 return Err(CamelError::InvalidUri(
69 "WASM path escapes project root".to_string(),
70 ));
71 }
72
73 Ok(canonical)
74 }
75}
76
77impl Component for WasmComponent {
78 fn scheme(&self) -> &str {
79 "wasm"
80 }
81
82 fn create_endpoint(
83 &self,
84 uri: &str,
85 _ctx: &dyn ComponentContext,
86 ) -> Result<Box<dyn Endpoint>, CamelError> {
87 let uri_without_scheme = uri.strip_prefix("wasm:").ok_or_else(|| {
88 CamelError::InvalidUri(format!("WASM URI must start with 'wasm:': {uri}"))
89 })?;
90
91 let (path_part, wasm_config) = crate::config::WasmConfig::from_uri(uri_without_scheme);
92 if path_part.is_empty() {
93 return Err(CamelError::InvalidUri(
94 "WASM URI must include a module path".to_string(),
95 ));
96 }
97
98 let module_path = self.validate_and_resolve_path(&path_part)?;
99 Ok(Box::new(WasmEndpoint::new(
100 uri.to_string(),
101 module_path,
102 self.registry.clone(),
103 wasm_config,
104 )))
105 }
106}