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 {}: {:?}",
52 config_path, err
53 ))
54 })?;
55
56 let mut config = toml::from_str::<Self>(&config_content).map_err(|e| {
58 error!(target: "explorerd::config", "Failed parsing TOML config: {}", e);
59 Error::ConfigError(format!("Failed to parse the configuration file {}", config_path))
60 })?;
61
62 config.path = Some(config_path);
64
65 debug!(target: "explorerd::config", "Successfully loaded configuration: {:?}", config);
66
67 Ok(config)
68 }
69
70 #[allow(dead_code)] pub fn active_network_config(&self) -> Option<ExplorerNetworkConfig> {
73 self.get_network_config(self.network.as_str())
74 }
75
76 pub fn get_network_config(&self, network: &str) -> Option<ExplorerNetworkConfig> {
78 match network {
79 "localnet" => self.network_config.localnet.clone(),
80 "testnet" => self.network_config.testnet.clone(),
81 "mainnet" => self.network_config.mainnet.clone(),
82 _ => None,
83 }
84 }
85}
86
87impl Default for ExplorerConfig {
89 fn default() -> Self {
90 Self {
91 network: String::from("testnet"),
92 network_config: NetworkConfigs::default(),
93 path: None,
94 }
95 }
96}
97
98impl TryFrom<&PathBuf> for ExplorerConfig {
100 type Error = Error;
101 fn try_from(path: &PathBuf) -> Result<Self> {
102 let path_str = path.to_str().ok_or_else(|| {
103 Error::ConfigError("Unable to convert PathBuf to a valid UTF-8 path string".to_string())
104 })?;
105
106 ExplorerConfig::new(path_str.to_string())
108 }
109}
110
111impl FromStr for ExplorerConfig {
113 type Err = String;
114 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
115 let config: ExplorerConfig =
116 toml::from_str(s).map_err(|e| format!("Failed to parse ExplorerdConfig: {}", e))?;
117 Ok(config)
118 }
119}
120
121#[derive(Debug, Clone, Deserialize, StructOpt)]
123pub struct NetworkConfigs {
124 pub localnet: Option<ExplorerNetworkConfig>,
126 pub testnet: Option<ExplorerNetworkConfig>,
128 pub mainnet: Option<ExplorerNetworkConfig>,
130}
131
132impl Default for NetworkConfigs {
134 fn default() -> Self {
135 NetworkConfigs {
136 localnet: None,
137 testnet: Some(ExplorerNetworkConfig::default()),
138 mainnet: None,
139 }
140 }
141}
142
143impl FromStr for NetworkConfigs {
145 type Err = String;
146 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
147 let config: NetworkConfigs =
148 toml::from_str(s).map_err(|e| format!("Failed to parse NetworkConfigs: {}", e))?;
149 Ok(config)
150 }
151}
152
153#[derive(Clone, Deserialize, StructOpt)]
155#[structopt()]
156#[serde(default)]
157pub struct ExplorerNetworkConfig {
158 #[structopt(flatten)]
159 pub rpc: RpcSettingsOpt,
161
162 #[structopt(long, default_value = "~/.local/share/darkfi/explorerd/testnet")]
163 pub database: String,
165
166 #[structopt(short, long, default_value = "tcp://127.0.0.1:8340")]
167 pub endpoint: Url,
169}
170
171impl TryFrom<(&PathBuf, &String)> for ExplorerNetworkConfig {
174 type Error = Error;
175 fn try_from(path_and_network: (&PathBuf, &String)) -> Result<Self> {
176 let config: ExplorerConfig = path_and_network.0.try_into()?;
178 match config.get_network_config(path_and_network.1) {
180 Some(config) => Ok(config),
181 None => Err(Error::ConfigError(format!(
182 "Failed to retrieve network configuration for network: {}",
183 path_and_network.1
184 ))),
185 }
186 }
187}
188
189impl Default for ExplorerNetworkConfig {
192 fn default() -> Self {
193 Self::from_iter(&[""])
194 }
195}
196
197impl fmt::Debug for ExplorerNetworkConfig {
199 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
200 let mut debug_struct = f.debug_struct("ExplorerdConfig");
201 debug_struct
202 .field("rpc_listen", &self.rpc.rpc_listen.to_string().trim_end_matches('/'))
203 .field("db_path", &self.database)
204 .field("endpoint", &self.endpoint.to_string().trim_end_matches('/'));
205 debug_struct.finish()
206 }
207}
208
209impl FromStr for ExplorerNetworkConfig {
211 type Err = String;
212 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
213 let config: ExplorerNetworkConfig = toml::from_str(s)
214 .map_err(|e| format!("Failed to parse ExplorerdNetworkConfig: {}", e))?;
215 Ok(config)
216 }
217}
218
219#[cfg(test)]
220mod tests {
223 use std::path::Path;
224
225 use super::*;
226
227 use crate::test_utils::init_logger;
228
229 #[test]
232 fn test_explorerd_config_from_file() {
233 const CONFIG_PATH: &str = "explorerd_config.toml";
235 const ACTIVE_NETWORK: &str = "testnet";
236
237 const NETWORK_CONFIGS: &[(&str, &str, &str, &str)] = &[
238 (
239 "localnet",
240 "~/.local/share/darkfi/explorerd/localnet",
241 "tcp://127.0.0.1:8240/",
242 "tcp://127.0.0.1:14567/",
243 ),
244 (
245 "testnet",
246 "~/.local/share/darkfi/explorerd/testnet",
247 "tcp://127.0.0.1:8340/",
248 "tcp://127.0.0.1:14667/",
249 ),
250 (
251 "mainnet",
252 "~/.local/share/darkfi/explorerd/mainnet",
253 "tcp://127.0.0.1:8440/",
254 "tcp://127.0.0.1:14767/",
255 ),
256 ];
257
258 init_logger(simplelog::LevelFilter::Info, vec!["sled", "runtime", "net"]);
259
260 assert!(Path::new(CONFIG_PATH).exists());
262
263 let config = ExplorerConfig::new(CONFIG_PATH.to_string())
265 .expect("Failed to load configuration from file");
266
267 assert_eq!(config.network, ACTIVE_NETWORK);
269
270 assert_eq!(config.path.as_deref(), Some(CONFIG_PATH));
272
273 let active_config = config.active_network_config();
275 assert!(active_config.is_some(), "Active network configuration should not be None.");
276 let active_config = active_config.unwrap();
277 assert_eq!(active_config.database, NETWORK_CONFIGS[1].1); assert_eq!(active_config.endpoint.to_string(), NETWORK_CONFIGS[1].2);
279 assert_eq!(&active_config.rpc.rpc_listen.to_string(), NETWORK_CONFIGS[1].3);
280
281 for &(network, expected_db, expected_endpoint, expected_rpc) in NETWORK_CONFIGS {
283 let network_config = config.get_network_config(network);
284
285 if let Some(config) = network_config {
286 assert_eq!(config.database, expected_db);
287 assert_eq!(config.endpoint.to_string(), expected_endpoint);
288 assert_eq!(config.rpc.rpc_listen.to_string(), expected_rpc);
289 } else {
290 assert!(network_config.is_none(), "{} configuration is missing", network);
291 }
292 }
293
294 let config_path_buf = &PathBuf::from(CONFIG_PATH);
296 let mainnet_string = &String::from("mainnet");
297 let mainnet_config: ExplorerNetworkConfig = (config_path_buf, mainnet_string)
298 .try_into()
299 .expect("Failed to load explorer network config");
300 assert_eq!(mainnet_config.database, NETWORK_CONFIGS[2].1); assert_eq!(mainnet_config.endpoint.to_string(), NETWORK_CONFIGS[2].2);
302 assert_eq!(&mainnet_config.rpc.rpc_listen.to_string(), NETWORK_CONFIGS[2].3);
303 }
304}