cardinal_wasm_plugins/
plugin.rs

1use cardinal_errors::internal::CardinalInternalError;
2use cardinal_errors::CardinalError;
3use std::collections::HashSet;
4use std::path::{Path, PathBuf};
5use wasmer::{Engine, Module};
6
7pub struct WasmPlugin {
8    pub engine: Engine,
9    pub module: Module,
10    pub path: PathBuf,
11    pub memory_name: String,
12    pub handle_name: String,
13}
14
15impl WasmPlugin {
16    pub fn new(
17        engine: Engine,
18        module: Module,
19        memory_name: Option<String>,
20        handle_name: Option<String>,
21    ) -> Result<Self, CardinalError> {
22        let memory_name = memory_name.unwrap_or_else(|| "memory".to_string());
23        let handle_name = handle_name.unwrap_or_else(|| "handle".to_string());
24
25        let plugin = Self {
26            engine,
27            module,
28            path: PathBuf::new(),
29            memory_name: memory_name.clone(),
30            handle_name: handle_name.clone(),
31        };
32
33        plugin.validate_exports(&[memory_name, handle_name])?;
34
35        Ok(plugin)
36    }
37
38    /// Load & compile a Wasm module from a file path.
39    pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, CardinalError> {
40        let path = path.as_ref().to_path_buf();
41        let bytes = std::fs::read(&path)?;
42        let (engine, module) = Self::initiate(&bytes, None)?;
43        let mut plugin = Self::new(engine, module, None, None)?;
44        plugin.path = path;
45
46        Ok(plugin)
47    }
48
49    pub fn initiate(
50        bytes: &[u8],
51        engine: Option<Engine>,
52    ) -> Result<(Engine, Module), CardinalError> {
53        let engine = engine.unwrap_or_default();
54        let module = Module::new(&engine, bytes).map_err(|e| {
55            CardinalError::InternalError(CardinalInternalError::InvalidWasmModule(format!(
56                "Error initiating plugin {e}"
57            )))
58        })?;
59
60        Ok((engine, module))
61    }
62
63    pub fn with_memory_name(mut self, name: String) -> Self {
64        self.memory_name = name;
65        self
66    }
67
68    pub fn with_handle_name(mut self, name: String) -> Self {
69        self.handle_name = name;
70        self
71    }
72
73    pub fn validate_exports<I, S>(&self, required: I) -> Result<(), CardinalError>
74    where
75        I: IntoIterator<Item = S>,
76        S: Into<String>,
77    {
78        let required: HashSet<String> = required.into_iter().map(Into::into).collect();
79        let mut found: HashSet<String> = HashSet::new();
80
81        for export in self.module.exports() {
82            let name = export.name().to_string();
83            if required.contains(&name) {
84                found.insert(name);
85            }
86        }
87
88        let missing: Vec<_> = required.difference(&found).cloned().collect();
89        if !missing.is_empty() {
90            return Err(CardinalError::Other(format!(
91                "wasm plugin missing required exports: {missing:?}"
92            )));
93        }
94
95        Ok(())
96    }
97}