canic_core/ops/runtime/
wasm.rs

1use crate::{
2    Error, ThisError, cdk::types::WasmModule, ids::CanisterRole, log, log::Topic,
3    ops::runtime::RuntimeOpsError,
4};
5use std::{cell::RefCell, collections::HashMap};
6
7thread_local! {
8    ///
9    /// Runtime WASM registry
10    ///
11    /// Application-owned, in-memory registry mapping canister roles
12    /// to their embedded WASM modules. This is runtime state, not domain
13    /// state and not infrastructure plumbing.
14    ///
15    static WASM_REGISTRY: RefCell<HashMap<CanisterRole, WasmModule>> =
16        RefCell::new(HashMap::new());
17}
18
19///
20/// WasmOpsError
21///
22
23#[derive(Debug, ThisError)]
24pub enum WasmOpsError {
25    #[error("wasm '{0}' not found")]
26    WasmNotFound(CanisterRole),
27}
28
29impl From<WasmOpsError> for Error {
30    fn from(err: WasmOpsError) -> Self {
31        RuntimeOpsError::WasmOpsError(err).into()
32    }
33}
34
35///
36/// WasmOps
37/// Runtime API for accessing embedded WASM modules.
38///
39
40pub struct WasmOps;
41
42impl WasmOps {
43    /// Fetch a WASM module for the given canister role, if registered.
44    #[must_use]
45    pub fn get(role: &CanisterRole) -> Option<WasmModule> {
46        WASM_REGISTRY.with_borrow(|reg| reg.get(role).cloned())
47    }
48
49    /// Fetch a WASM module or return an error if missing.
50    pub fn try_get(role: &CanisterRole) -> Result<WasmModule, Error> {
51        Self::get(role).ok_or_else(|| WasmOpsError::WasmNotFound(role.clone()).into())
52    }
53
54    /// Import a static slice of (role, wasm bytes) at startup.
55    ///
56    /// Intended to be called during canister initialization.
57    #[allow(clippy::cast_precision_loss)]
58    pub fn import_static(wasms: &'static [(CanisterRole, &[u8])]) {
59        for (role, bytes) in wasms {
60            let wasm = WasmModule::new(bytes);
61            let size = wasm.len();
62
63            WASM_REGISTRY.with_borrow_mut(|reg| {
64                reg.insert(role.clone(), wasm);
65            });
66
67            log!(
68                Topic::Wasm,
69                Info,
70                "📄 wasm.import: {} ({:.2} KB)",
71                role,
72                size as f64 / 1000.0
73            );
74        }
75    }
76
77    /// Import a static slice of (role, wasm bytes) without logging.
78    pub fn import_static_quiet(wasms: &'static [(CanisterRole, &[u8])]) {
79        for (role, bytes) in wasms {
80            let wasm = WasmModule::new(bytes);
81            WASM_REGISTRY.with_borrow_mut(|reg| {
82                reg.insert(role.clone(), wasm);
83            });
84        }
85    }
86
87    /// Clear the registry (tests only).
88    #[cfg(test)]
89    #[expect(dead_code)]
90    pub(crate) fn clear_for_test() {
91        WASM_REGISTRY.with_borrow_mut(HashMap::clear);
92    }
93}