Skip to main content

blvm_sdk/composition/
config.rs

1//! Composition Configuration
2//!
3//! TOML-based declarative configuration format for node composition.
4
5use crate::composition::types::*;
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::path::Path;
9
10/// Node configuration from TOML file
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct NodeConfig {
13    /// Node metadata
14    #[serde(default)]
15    pub node: NodeMetadata,
16    /// Module configurations
17    #[serde(default)]
18    pub modules: HashMap<String, ModuleConfig>,
19}
20
21/// Node metadata section
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct NodeMetadata {
24    /// Node name
25    pub name: String,
26    /// Node version
27    #[serde(default)]
28    pub version: Option<String>,
29    /// Network type
30    pub network: String,
31}
32
33impl Default for NodeMetadata {
34    fn default() -> Self {
35        Self {
36            name: "custom-node".to_string(),
37            version: None,
38            network: "mainnet".to_string(),
39        }
40    }
41}
42
43/// Module configuration section
44#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct ModuleConfig {
46    /// Whether module is enabled
47    #[serde(default = "default_true")]
48    pub enabled: bool,
49    /// Module version (optional)
50    #[serde(default)]
51    pub version: Option<String>,
52    /// Module-specific configuration
53    #[serde(default)]
54    pub config: HashMap<String, toml::Value>,
55}
56
57fn default_true() -> bool {
58    true
59}
60
61impl NodeConfig {
62    /// Load configuration from TOML file
63    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
64        let contents = std::fs::read_to_string(path.as_ref()).map_err(CompositionError::IoError)?;
65
66        let config: NodeConfig = toml::from_str(&contents).map_err(|e| {
67            CompositionError::InvalidConfiguration(format!("Failed to parse TOML: {e}"))
68        })?;
69
70        Ok(config)
71    }
72
73    /// Save configuration to TOML file
74    pub fn to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
75        let toml_string = toml::to_string_pretty(self).map_err(|e| {
76            CompositionError::SerializationError(format!("Failed to serialize config: {e}"))
77        })?;
78
79        std::fs::write(path.as_ref(), toml_string).map_err(CompositionError::IoError)?;
80
81        Ok(())
82    }
83
84    /// Convert to NodeSpec
85    pub fn to_spec(&self) -> Result<NodeSpec> {
86        let network = match self.node.network.as_str() {
87            "mainnet" => NetworkType::Mainnet,
88            "testnet" => NetworkType::Testnet,
89            "regtest" => NetworkType::Regtest,
90            _ => {
91                return Err(CompositionError::InvalidConfiguration(format!(
92                    "Unknown network type: {}",
93                    self.node.network
94                )))
95            }
96        };
97
98        let modules: Result<Vec<ModuleSpec>> = self
99            .modules
100            .iter()
101            .filter(|(_, cfg)| cfg.enabled)
102            .map(|(name, cfg)| {
103                // Convert toml::Value to serde_json::Value
104                let config: HashMap<String, serde_json::Value> = cfg
105                    .config
106                    .iter()
107                    .map(|(k, v)| {
108                        let json_value = toml_to_json_value(v);
109                        (k.clone(), json_value)
110                    })
111                    .collect();
112
113                Ok(ModuleSpec {
114                    name: name.clone(),
115                    version: cfg.version.clone(),
116                    enabled: cfg.enabled,
117                    config,
118                })
119            })
120            .collect();
121
122        Ok(NodeSpec {
123            name: self.node.name.clone(),
124            version: self.node.version.clone(),
125            network,
126            modules: modules?,
127        })
128    }
129
130    /// Generate template configuration
131    pub fn template() -> Self {
132        let mut modules = HashMap::new();
133
134        // Add example modules
135        modules.insert(
136            "lightning".to_string(),
137            ModuleConfig {
138                enabled: false,
139                version: Some("0.1.0".to_string()),
140                config: HashMap::new(),
141            },
142        );
143
144        modules.insert(
145            "privacy".to_string(),
146            ModuleConfig {
147                enabled: false,
148                version: Some("0.2.0".to_string()),
149                config: HashMap::new(),
150            },
151        );
152
153        Self {
154            node: NodeMetadata {
155                name: "my-custom-node".to_string(),
156                version: Some("1.0.0".to_string()),
157                network: "mainnet".to_string(),
158            },
159            modules,
160        }
161    }
162}
163
164/// Convert toml::Value to serde_json::Value
165fn toml_to_json_value(value: &toml::Value) -> serde_json::Value {
166    match value {
167        toml::Value::String(s) => serde_json::Value::String(s.clone()),
168        toml::Value::Integer(i) => serde_json::Value::Number((*i).into()),
169        toml::Value::Float(f) => serde_json::Value::Number(
170            serde_json::Number::from_f64(*f).unwrap_or_else(|| serde_json::Number::from(0)),
171        ),
172        toml::Value::Boolean(b) => serde_json::Value::Bool(*b),
173        toml::Value::Datetime(dt) => serde_json::Value::String(dt.to_string()),
174        toml::Value::Array(arr) => {
175            serde_json::Value::Array(arr.iter().map(toml_to_json_value).collect())
176        }
177        toml::Value::Table(table) => {
178            let map: serde_json::Map<String, serde_json::Value> = table
179                .iter()
180                .map(|(k, v)| (k.clone(), toml_to_json_value(v)))
181                .collect();
182            serde_json::Value::Object(map)
183        }
184    }
185}