Skip to main content

blvm_sdk/module/
bootstrap.rs

1//! Module bootstrap from environment.
2//!
3//! When the node spawns a module, it passes `MODULE_ID`, `SOCKET_PATH`, and `DATA_DIR`
4//! via environment variables. Use `ModuleBootstrap::from_env()` to read them—no clap needed.
5
6use std::collections::HashMap;
7use std::path::{Path, PathBuf};
8
9/// Config types that can be loaded and converted to context map.
10/// Use `impl_module_config!(ConfigType)` to implement by delegating to inherent `load` and `to_context_map`.
11#[cfg(feature = "node")]
12pub trait ModuleConfig: Default {
13    fn load_config(path: impl AsRef<Path>) -> Result<Self, anyhow::Error>
14    where
15        Self: Sized;
16    /// Return config as key-value map for ModuleContext. Delegates to inherent `to_context_map` via macro.
17    fn config_map(&self) -> HashMap<String, String>;
18}
19
20/// Implement ModuleConfig by delegating to inherent `load` and `to_context_map`.
21#[macro_export]
22macro_rules! impl_module_config {
23    ($config:ty) => {
24        impl $crate::module::ModuleConfig for $config {
25            fn load_config(
26                path: impl std::convert::AsRef<std::path::Path>,
27            ) -> Result<Self, anyhow::Error> {
28                <$config>::load(path)
29            }
30            fn config_map(&self) -> std::collections::HashMap<String, String> {
31                self.to_context_map()
32            }
33        }
34    };
35}
36
37/// Bootstrap parameters for a module, read from environment.
38///
39/// The node sets these when spawning. For standalone testing, set them manually:
40/// `MODULE_ID`, `SOCKET_PATH`, `DATA_DIR`.
41#[derive(Clone, Debug)]
42pub struct ModuleBootstrap {
43    pub module_id: String,
44    pub socket_path: PathBuf,
45    pub data_dir: PathBuf,
46}
47
48impl ModuleBootstrap {
49    /// Read bootstrap params from environment.
50    ///
51    /// Expects: `MODULE_ID`, `SOCKET_PATH`, `DATA_DIR`.
52    pub fn from_env() -> Result<Self, std::env::VarError> {
53        Ok(Self {
54            module_id: std::env::var("MODULE_ID")?,
55            socket_path: PathBuf::from(std::env::var("SOCKET_PATH")?),
56            data_dir: PathBuf::from(std::env::var("DATA_DIR")?),
57        })
58    }
59
60    /// Bootstrap from env, or use defaults when env vars are unset (standalone/testing).
61    pub fn from_env_or_defaults(
62        module_id: impl Into<String>,
63        socket_path: impl Into<PathBuf>,
64        data_dir: impl Into<PathBuf>,
65    ) -> Self {
66        Self::from_env().unwrap_or_else(|_| Self {
67            module_id: module_id.into(),
68            socket_path: socket_path.into(),
69            data_dir: data_dir.into(),
70        })
71    }
72
73    /// Bootstrap from env, or defaults derived from module name (standalone/testing).
74    ///
75    /// Uses `data/modules/{name}.sock` and `data/modules/{name}` when env vars are unset.
76    pub fn for_module(module_name: &str) -> Self {
77        Self::from_env().unwrap_or_else(|_| Self {
78            module_id: module_name.to_string(),
79            socket_path: PathBuf::from(format!("data/modules/{module_name}.sock")),
80            data_dir: PathBuf::from(format!("data/modules/{module_name}")),
81        })
82    }
83
84    /// Init logging, set DATA_DIR, log startup, and return bootstrap. One-liner for module mains.
85    #[cfg(feature = "node")]
86    pub fn init_module(module_name: &str) -> Self {
87        blvm_node::utils::init_module_logging(module_name.replace('-', "_").as_str(), None);
88        let bootstrap = Self::for_module(module_name);
89        std::env::set_var("DATA_DIR", bootstrap.data_dir.to_string_lossy().as_ref());
90        tracing::info!(
91            "{} starting... (module_id: {}, socket: {:?})",
92            module_name,
93            bootstrap.module_id,
94            bootstrap.socket_path
95        );
96        bootstrap
97    }
98
99    /// Build ModuleContext from bootstrap + data_dir + config (reduces setup boilerplate).
100    #[cfg(feature = "node")]
101    pub fn context(
102        &self,
103        data_dir: &Path,
104        config: HashMap<String, String>,
105    ) -> blvm_node::module::traits::ModuleContext {
106        blvm_node::module::traits::ModuleContext {
107            module_id: self.module_id.clone(),
108            socket_path: self.socket_path.to_string_lossy().to_string(),
109            data_dir: data_dir.to_string_lossy().to_string(),
110            config,
111        }
112    }
113
114    /// Load config from `{data_dir}/config.toml` and build ModuleContext. Convention for setup closures.
115    #[cfg(feature = "node")]
116    pub fn context_with_config<C: ModuleConfig>(
117        &self,
118        data_dir: &Path,
119    ) -> (blvm_node::module::traits::ModuleContext, C) {
120        let config = C::load_config(data_dir.join("config.toml")).unwrap_or_default();
121        let ctx = self.context(data_dir, config.config_map());
122        (ctx, config)
123    }
124}