1use std::{
20 fmt,
21 path::{Path, PathBuf},
22 str::FromStr,
23};
24
25use log::{debug, error};
26use serde::Deserialize;
27use structopt::StructOpt;
28use url::Url;
29
30use darkfi::{rpc::settings::RpcSettingsOpt, util::file::load_file, Error, Result};
31
32#[derive(Clone, Debug, Deserialize, StructOpt)]
34pub struct ExplorerConfig {
35 #[allow(dead_code)] pub network: String,
38 pub network_config: NetworkConfigs,
40 pub path: Option<String>,
42}
43
44impl ExplorerConfig {
45 pub fn new(config_path: String) -> Result<Self> {
48 let config_content = load_file(Path::new(&config_path)).map_err(|err| {
50 Error::ConfigError(format!(
51 "Failed to read the configuration file {config_path}: {err:?}"
52 ))
53 })?;
54
55 let mut config = toml::from_str::<Self>(&config_content).map_err(|e| {
57 error!(target: "explorerd::config", "Failed parsing TOML config: {e}");
58 Error::ConfigError(format!("Failed to parse the configuration file {config_path}"))
59 })?;
60
61 config.path = Some(config_path);
63
64 debug!(target: "explorerd::config", "Successfully loaded configuration: {config:?}");
65
66 Ok(config)
67 }
68
69 #[allow(dead_code)] pub fn active_network_config(&self) -> Option<ExplorerNetworkConfig> {
72 self.get_network_config(self.network.as_str())
73 }
74
75 pub fn get_network_config(&self, network: &str) -> Option<ExplorerNetworkConfig> {
77 match network {
78 "localnet" => self.network_config.localnet.clone(),
79 "testnet" => self.network_config.testnet.clone(),
80 "mainnet" => self.network_config.mainnet.clone(),
81 _ => None,
82 }
83 }
84}
85
86impl Default for ExplorerConfig {
88 fn default() -> Self {
89 Self {
90 network: String::from("testnet"),
91 network_config: NetworkConfigs::default(),
92 path: None,
93 }
94 }
95}
96
97impl TryFrom<&PathBuf> for ExplorerConfig {
99 type Error = Error;
100 fn try_from(path: &PathBuf) -> Result<Self> {
101 let path_str = path.to_str().ok_or_else(|| {
102 Error::ConfigError("Unable to convert PathBuf to a valid UTF-8 path string".to_string())
103 })?;
104
105 ExplorerConfig::new(path_str.to_string())
107 }
108}
109
110impl FromStr for ExplorerConfig {
112 type Err = String;
113 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
114 let config: ExplorerConfig =
115 toml::from_str(s).map_err(|e| format!("Failed to parse ExplorerdConfig: {e}"))?;
116 Ok(config)
117 }
118}
119
120#[derive(Debug, Clone, Deserialize, StructOpt)]
122pub struct NetworkConfigs {
123 pub localnet: Option<ExplorerNetworkConfig>,
125 pub testnet: Option<ExplorerNetworkConfig>,
127 pub mainnet: Option<ExplorerNetworkConfig>,
129}
130
131impl Default for NetworkConfigs {
133 fn default() -> Self {
134 NetworkConfigs {
135 localnet: None,
136 testnet: Some(ExplorerNetworkConfig::default()),
137 mainnet: None,
138 }
139 }
140}
141
142impl FromStr for NetworkConfigs {
144 type Err = String;
145 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
146 let config: NetworkConfigs =
147 toml::from_str(s).map_err(|e| format!("Failed to parse NetworkConfigs: {e}"))?;
148 Ok(config)
149 }
150}
151
152#[derive(Clone, Deserialize, StructOpt)]
154#[structopt()]
155#[serde(default)]
156pub struct ExplorerNetworkConfig {
157 #[structopt(flatten)]
158 pub rpc: RpcSettingsOpt,
160
161 #[structopt(long, default_value = "~/.local/share/darkfi/explorerd/testnet")]
162 pub database: String,
164
165 #[structopt(short, long, default_value = "tcp://127.0.0.1:8340")]
166 pub endpoint: Url,
168}
169
170impl TryFrom<(&PathBuf, &String)> for ExplorerNetworkConfig {
173 type Error = Error;
174 fn try_from(path_and_network: (&PathBuf, &String)) -> Result<Self> {
175 let config: ExplorerConfig = path_and_network.0.try_into()?;
177 match config.get_network_config(path_and_network.1) {
179 Some(config) => Ok(config),
180 None => Err(Error::ConfigError(format!(
181 "Failed to retrieve network configuration for network: {}",
182 path_and_network.1
183 ))),
184 }
185 }
186}
187
188impl Default for ExplorerNetworkConfig {
191 fn default() -> Self {
192 Self::from_iter(&[""])
193 }
194}
195
196impl fmt::Debug for ExplorerNetworkConfig {
198 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199 let mut debug_struct = f.debug_struct("ExplorerdConfig");
200 debug_struct
201 .field("rpc_listen", &self.rpc.rpc_listen.to_string().trim_end_matches('/'))
202 .field("db_path", &self.database)
203 .field("endpoint", &self.endpoint.to_string().trim_end_matches('/'));
204 debug_struct.finish()
205 }
206}
207
208impl FromStr for ExplorerNetworkConfig {
210 type Err = String;
211 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
212 let config: ExplorerNetworkConfig = toml::from_str(s)
213 .map_err(|e| format!("Failed to parse ExplorerdNetworkConfig: {e}"))?;
214 Ok(config)
215 }
216}
217
218#[cfg(test)]
219mod tests {
222 use std::path::Path;
223
224 use super::*;
225
226 use crate::test_utils::init_logger;
227
228 #[test]
231 fn test_explorerd_config_from_file() {
232 const CONFIG_PATH: &str = "explorerd_config.toml";
234 const ACTIVE_NETWORK: &str = "testnet";
235
236 const NETWORK_CONFIGS: &[(&str, &str, &str, &str)] = &[
237 (
238 "localnet",
239 "~/.local/share/darkfi/explorerd/localnet",
240 "tcp://127.0.0.1:8240/",
241 "tcp://127.0.0.1:14567/",
242 ),
243 (
244 "testnet",
245 "~/.local/share/darkfi/explorerd/testnet",
246 "tcp://127.0.0.1:8340/",
247 "tcp://127.0.0.1:14667/",
248 ),
249 (
250 "mainnet",
251 "~/.local/share/darkfi/explorerd/mainnet",
252 "tcp://127.0.0.1:8440/",
253 "tcp://127.0.0.1:14767/",
254 ),
255 ];
256
257 init_logger(simplelog::LevelFilter::Info, vec!["sled", "runtime", "net"]);
258
259 assert!(Path::new(CONFIG_PATH).exists());
261
262 let config = ExplorerConfig::new(CONFIG_PATH.to_string())
264 .expect("Failed to load configuration from file");
265
266 assert_eq!(config.network, ACTIVE_NETWORK);
268
269 assert_eq!(config.path.as_deref(), Some(CONFIG_PATH));
271
272 let active_config = config.active_network_config();
274 assert!(active_config.is_some(), "Active network configuration should not be None.");
275 let active_config = active_config.unwrap();
276 assert_eq!(active_config.database, NETWORK_CONFIGS[1].1); assert_eq!(active_config.endpoint.to_string(), NETWORK_CONFIGS[1].2);
278 assert_eq!(&active_config.rpc.rpc_listen.to_string(), NETWORK_CONFIGS[1].3);
279
280 for &(network, expected_db, expected_endpoint, expected_rpc) in NETWORK_CONFIGS {
282 let network_config = config.get_network_config(network);
283
284 if let Some(config) = network_config {
285 assert_eq!(config.database, expected_db);
286 assert_eq!(config.endpoint.to_string(), expected_endpoint);
287 assert_eq!(config.rpc.rpc_listen.to_string(), expected_rpc);
288 } else {
289 assert!(network_config.is_none(), "{network} configuration is missing");
290 }
291 }
292
293 let config_path_buf = &PathBuf::from(CONFIG_PATH);
295 let mainnet_string = &String::from("mainnet");
296 let mainnet_config: ExplorerNetworkConfig = (config_path_buf, mainnet_string)
297 .try_into()
298 .expect("Failed to load explorer network config");
299 assert_eq!(mainnet_config.database, NETWORK_CONFIGS[2].1); assert_eq!(mainnet_config.endpoint.to_string(), NETWORK_CONFIGS[2].2);
301 assert_eq!(&mainnet_config.rpc.rpc_listen.to_string(), NETWORK_CONFIGS[2].3);
302 }
303}