Skip to main content

sdkt_core/
config.rs

1//! Core configuration engine for the Soroban DevKit (sdkt).
2//!
3//! This module defines the global configuration structures and handles
4//! the loading of configs from files, environment variables, and CLI overrides.
5
6use serde::{Deserialize, Serialize};
7use std::fs;
8use std::path::Path;
9
10/// High-level project-wide configuration.
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
12pub struct DevKitConfig {
13    /// Soroban network settings.
14    #[serde(default)]
15    pub network: NetworkConfig,
16    /// XDR decoder configurations.
17    #[serde(default)]
18    pub decode: DecodeConfig,
19    /// Storage inspection settings.
20    #[serde(default)]
21    pub storage: StorageConfig,
22    /// Workspace contract configuration mapping.
23    #[serde(default)]
24    pub contracts: std::collections::HashMap<String, ContractConfig>,
25}
26
27/// Settings for a specific contract in a workspace.
28#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
29pub struct ContractConfig {
30    /// Path to the contract source directory (where Cargo.toml resides).
31    pub path: String,
32    /// Optional list of contract aliases that must be deployed before this one.
33    #[serde(default, skip_serializing_if = "Vec::is_empty")]
34    pub deploy_after: Vec<String>,
35}
36
37/// Soroban network connection settings.
38#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
39pub struct NetworkConfig {
40    /// Target Soroban RPC URL.
41    pub rpc_url: String,
42    /// Core passphrase matching target network.
43    pub passphrase: String,
44    /// Optional timeout for RPC requests in seconds (default: 15).
45    pub timeout_secs: Option<u64>,
46    /// Optional maximum concurrent connections in the pool (default: 100).
47    pub pool_max_idle_per_host: Option<usize>,
48}
49
50/// Settings modifying XDR decoding actions.
51#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
52pub struct DecodeConfig {
53    /// Limit max depth during nested XDR parsing.
54    pub max_depth: usize,
55    /// Fallback to hex decoding if base64 detection fails.
56    pub allow_fallback_hex: bool,
57}
58
59/// Settings for Soroban storage inspection.
60#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
61pub struct StorageConfig {
62    /// Maximum number of storage entries to fetch per page.
63    pub max_entries: usize,
64    /// Number of days before TTL expiration to start warning.
65    pub ttl_warning_days: u32,
66}
67
68impl Default for StorageConfig {
69    fn default() -> Self {
70        Self {
71            max_entries: 200,
72            ttl_warning_days: 30,
73        }
74    }
75}
76
77impl Default for DecodeConfig {
78    fn default() -> Self {
79        Self {
80            max_depth: 32,
81            allow_fallback_hex: true,
82        }
83    }
84}
85
86impl Default for NetworkConfig {
87    fn default() -> Self {
88        Self {
89            rpc_url: "https://soroban-testnet.stellar.org".to_string(),
90            passphrase: "Test SDF Network ; September 2015".to_string(),
91            timeout_secs: Some(15),
92            pool_max_idle_per_host: Some(100),
93        }
94    }
95}
96
97impl DevKitConfig {
98    /// Loads configuration from a TOML string.
99    pub fn from_toml(content: &str) -> Result<Self, toml::de::Error> {
100        toml::from_str(content)
101    }
102
103    /// Loads configuration from a specific file path.
104    ///
105    /// If the file does not exist, it falls back to the default configuration.
106    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, Box<dyn std::error::Error>> {
107        let path = path.as_ref();
108        if path.exists() {
109            let content = fs::read_to_string(path)?;
110            let config = Self::from_toml(&content)?;
111            Ok(config)
112        } else {
113            Ok(Self::default())
114        }
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    #[test]
123    fn test_default_config() {
124        let default_config = DevKitConfig::default();
125        assert_eq!(default_config.decode.max_depth, 32);
126        assert!(default_config.decode.allow_fallback_hex);
127        assert_eq!(default_config.storage.max_entries, 200);
128        assert_eq!(default_config.storage.ttl_warning_days, 30);
129    }
130
131    #[test]
132    fn test_parse_valid_toml() {
133        let toml_data = r#"
134            [network]
135            rpc_url = "http://localhost:8000"
136            passphrase = "Standalone Network"
137
138            [decode]
139            max_depth = 16
140            allow_fallback_hex = false
141
142            [storage]
143            max_entries = 100
144            ttl_warning_days = 7
145        "#;
146        let parsed = DevKitConfig::from_toml(toml_data).unwrap();
147        assert_eq!(parsed.network.rpc_url, "http://localhost:8000");
148        assert_eq!(parsed.decode.max_depth, 16);
149        assert!(!parsed.decode.allow_fallback_hex);
150        assert_eq!(parsed.storage.max_entries, 100);
151        assert_eq!(parsed.storage.ttl_warning_days, 7);
152    }
153
154    #[test]
155    fn test_old_toml_without_storage() {
156        let toml_data = r#"
157            [network]
158            rpc_url = "http://localhost:8000"
159            passphrase = "Standalone Network"
160
161            [decode]
162            max_depth = 16
163            allow_fallback_hex = false
164        "#;
165        let parsed = DevKitConfig::from_toml(toml_data).unwrap();
166        assert_eq!(parsed.storage.max_entries, 200);
167    }
168}