Skip to main content

astrid_plugins/wasm/
host_state.rs

1//! Shared state for Extism host functions.
2//!
3//! [`HostState`] is wrapped in [`extism::UserData`] and shared across all
4//! host function invocations for a single plugin instance.
5
6use std::collections::HashMap;
7use std::path::PathBuf;
8use std::sync::Arc;
9
10use astrid_storage::kv::ScopedKvStore;
11
12use crate::PluginId;
13use crate::security::PluginSecurityGate;
14
15/// Shared state accessible to all host functions via `UserData<HostState>`.
16pub struct HostState {
17    /// The plugin this state belongs to.
18    pub plugin_id: PluginId,
19    /// Workspace root directory (file operations are confined here).
20    pub workspace_root: PathBuf,
21    /// Plugin-scoped KV store (`plugin:{plugin_id}` namespace).
22    pub kv: ScopedKvStore,
23    /// Plugin configuration from the manifest.
24    pub config: HashMap<String, serde_json::Value>,
25    /// Optional security gate for gated operations (HTTP, file I/O).
26    pub security: Option<Arc<dyn PluginSecurityGate>>,
27    /// Tokio runtime handle for bridging async operations in sync host functions.
28    pub runtime_handle: tokio::runtime::Handle,
29}
30
31impl std::fmt::Debug for HostState {
32    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33        f.debug_struct("HostState")
34            .field("plugin_id", &self.plugin_id)
35            .field("workspace_root", &self.workspace_root)
36            .field("has_security", &self.security.is_some())
37            .finish_non_exhaustive()
38    }
39}
40
41#[cfg(test)]
42mod tests {
43    use super::*;
44
45    #[test]
46    fn host_state_debug_format() {
47        let rt = tokio::runtime::Builder::new_current_thread()
48            .build()
49            .unwrap();
50        let store = Arc::new(astrid_storage::MemoryKvStore::new());
51        let kv = ScopedKvStore::new(store, "plugin:test").unwrap();
52
53        let state = HostState {
54            plugin_id: PluginId::from_static("test"),
55            workspace_root: PathBuf::from("/tmp"),
56            kv,
57            config: HashMap::new(),
58            security: None,
59            runtime_handle: rt.handle().clone(),
60        };
61
62        let debug = format!("{state:?}");
63        assert!(debug.contains("test"));
64        assert!(debug.contains("has_security"));
65    }
66}