use std::collections::HashMap;
use async_trait::async_trait;
use crate::constants::{
NETWORK_DEVNET, NETWORK_LOCALNET, NETWORK_MAINNET, NETWORK_TESTNET, URL_DEVNET, URL_LOCALNET,
URL_MAINNET, URL_TESTNET,
};
#[derive(Debug, Clone)]
pub struct Config {
pub rpc_url: String,
pub default_wallet: Option<String>,
pub network: String,
pub default_balance_unit: String,
pub verbose_mode: bool,
pub custom_rpc_urls: HashMap<String, String>,
pub wallet_defaults: HashMap<String, usize>,
}
impl Default for Config {
fn default() -> Self {
Config {
rpc_url: URL_LOCALNET.to_string(),
default_wallet: None,
network: NETWORK_LOCALNET.to_string(),
default_balance_unit: "rlo".to_string(),
verbose_mode: false,
custom_rpc_urls: HashMap::new(),
wallet_defaults: HashMap::new(),
}
}
}
impl Config {
pub fn set_network(&mut self, network: &str) {
self.network = network.to_string();
match network {
NETWORK_LOCALNET => self.rpc_url = URL_LOCALNET.to_string(),
NETWORK_DEVNET => self.rpc_url = URL_DEVNET.to_string(),
NETWORK_TESTNET => self.rpc_url = URL_TESTNET.to_string(),
NETWORK_MAINNET => self.rpc_url = URL_MAINNET.to_string(),
_ => {} }
if let Some(custom_url) = self.custom_rpc_urls.get(network) {
self.rpc_url = custom_url.clone();
}
}
pub fn set_custom_rpc_url(&mut self, network: &str, url: &str) {
self.custom_rpc_urls
.insert(network.to_string(), url.to_string());
if self.network == network {
self.rpc_url = url.to_string();
}
}
pub fn set_wallet_default_account(&mut self, wallet_name: &str, account_index: usize) {
self.wallet_defaults
.insert(wallet_name.to_string(), account_index);
}
pub fn get_wallet_default_account(&self, wallet_name: &str) -> usize {
self.wallet_defaults.get(wallet_name).copied().unwrap_or(0)
}
pub fn get_rpc_url(&self) -> &str {
&self.rpc_url
}
pub fn get_rpc_url_for_network(&self, network: &str) -> String {
if let Some(custom_url) = self.custom_rpc_urls.get(network) {
return custom_url.clone();
}
match network {
NETWORK_LOCALNET => URL_LOCALNET.to_string(),
NETWORK_DEVNET => URL_DEVNET.to_string(),
NETWORK_TESTNET => URL_TESTNET.to_string(),
NETWORK_MAINNET => URL_MAINNET.to_string(),
_ => URL_LOCALNET.to_string(), }
}
}
#[async_trait]
pub trait ConfigProvider {
type Error;
async fn load(&self) -> Result<Config, Self::Error>;
async fn save(&self, config: &Config) -> Result<(), Self::Error>;
}
#[derive(Debug)]
pub struct InMemoryConfigProvider {
config: std::sync::Arc<std::sync::RwLock<Config>>,
}
impl InMemoryConfigProvider {
pub fn new(config: Config) -> Self {
InMemoryConfigProvider {
config: std::sync::Arc::new(std::sync::RwLock::new(config)),
}
}
}
impl Default for InMemoryConfigProvider {
fn default() -> Self {
Self::new(Config::default())
}
}
#[async_trait]
impl ConfigProvider for InMemoryConfigProvider {
type Error = String;
async fn load(&self) -> Result<Config, Self::Error> {
Ok(self
.config
.read()
.map_err(|e| format!("Failed to read config: {}", e))?
.clone())
}
async fn save(&self, config: &Config) -> Result<(), Self::Error> {
let mut guard = self
.config
.write()
.map_err(|e| format!("Failed to write config: {}", e))?;
*guard = config.clone();
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_default() {
let config = Config::default();
assert_eq!(config.network, NETWORK_LOCALNET);
assert_eq!(config.rpc_url, URL_LOCALNET);
assert_eq!(config.default_balance_unit, "rlo");
assert!(!config.verbose_mode);
}
#[test]
fn test_set_network() {
let mut config = Config::default();
config.set_network(NETWORK_DEVNET);
assert_eq!(config.network, NETWORK_DEVNET);
assert_eq!(config.rpc_url, URL_DEVNET);
config.set_network(NETWORK_MAINNET);
assert_eq!(config.network, NETWORK_MAINNET);
assert_eq!(config.rpc_url, URL_MAINNET);
}
#[test]
fn test_custom_rpc_url() {
let mut config = Config::default();
config.set_custom_rpc_url(NETWORK_LOCALNET, "http://custom:8080");
config.set_network(NETWORK_LOCALNET);
assert_eq!(config.rpc_url, "http://custom:8080");
}
#[test]
fn test_wallet_defaults() {
let mut config = Config::default();
config.set_wallet_default_account("test_wallet", 5);
assert_eq!(config.get_wallet_default_account("test_wallet"), 5);
assert_eq!(config.get_wallet_default_account("nonexistent"), 0);
}
#[tokio::test]
async fn test_in_memory_provider() {
let config = Config {
verbose_mode: true,
..Default::default()
};
let provider = InMemoryConfigProvider::new(config.clone());
let loaded = provider.load().await.unwrap();
assert!(loaded.verbose_mode);
let mut new_config = loaded;
new_config.network = NETWORK_DEVNET.to_string();
provider.save(&new_config).await.unwrap();
let reloaded = provider.load().await.unwrap();
assert_eq!(reloaded.network, NETWORK_DEVNET);
}
}