]> Untitled Git - bdk-cli/commitdiff
feat(wallet-init): impl TryFrom for WalletOpts
authorVihiga Tyonum <withtvpeter@gmail.com>
Sun, 18 Jan 2026 23:18:21 +0000 (00:18 +0100)
committerVihiga Tyonum <withtvpeter@gmail.com>
Mon, 19 Jan 2026 15:17:42 +0000 (16:17 +0100)
- refactor config by impl TryFrom trait for
WalletOpts
- fix top-level network duplicate in config file

src/config.rs
src/handlers.rs

index f024f0beb68c9eebb6dd4d5ec41316c49213a5a0..69c0238db84a6daa73a62e9963af9fb14b4d3106 100644 (file)
@@ -10,14 +10,16 @@ use crate::commands::DatabaseType;
 use crate::commands::WalletOpts;
 use crate::error::BDKCliError as Error;
 use bdk_wallet::bitcoin::Network;
+#[cfg(any(feature = "sqlite", feature = "redb"))]
+use clap::ValueEnum;
 use serde::{Deserialize, Serialize};
 use std::collections::HashMap;
 use std::fs;
 use std::path::Path;
+use std::str::FromStr;
 
 #[derive(Debug, Serialize, Deserialize)]
 pub struct WalletConfig {
-    pub network: Network,
     pub wallets: HashMap<String, WalletConfigInner>,
 }
 
@@ -80,55 +82,45 @@ impl WalletConfig {
 
     /// Get config for a wallet
     pub fn get_wallet_opts(&self, wallet_name: &str) -> Result<WalletOpts, Error> {
-        let wallet_config = self
-            .wallets
+        self.wallets
             .get(wallet_name)
-            .ok_or_else(|| Error::Generic(format!("Wallet {wallet_name} not found in config")))?;
-
-        let _network = match wallet_config.network.as_str() {
-            "bitcoin" => Network::Bitcoin,
-            "testnet" => Network::Testnet,
-            "regtest" => Network::Regtest,
-            "signet" => Network::Signet,
-            "testnet4" => Network::Testnet4,
-            _ => {
-                return Err(Error::Generic("Invalid network".to_string()));
-            }
-        };
+            .ok_or_else(|| Error::Generic(format!("Wallet {wallet_name} not found in config")))?
+            .try_into()
+    }
+}
+
+impl TryFrom<&WalletConfigInner> for WalletOpts {
+    type Error = Error;
+
+    fn try_from(config: &WalletConfigInner) -> Result<Self, Self::Error> {
+        let _network = Network::from_str(&config.network)
+            .map_err(|_| Error::Generic("Invalid network".to_string()))?;
 
         #[cfg(any(feature = "sqlite", feature = "redb"))]
-        let database_type = match wallet_config.database_type.as_str() {
-            #[cfg(feature = "sqlite")]
-            "sqlite" => DatabaseType::Sqlite,
-            #[cfg(feature = "redb")]
-            "redb" => DatabaseType::Redb,
-            _ => {
-                return Err(Error::Generic("Invalid database type".to_string()));
-            }
-        };
+        let database_type = DatabaseType::from_str(&config.database_type, true)
+            .map_err(|_| Error::Generic("Invalid database type".to_string()))?;
+
         #[cfg(any(
             feature = "electrum",
             feature = "esplora",
             feature = "rpc",
             feature = "cbf"
         ))]
-        let client_type = match wallet_config.client_type.as_deref() {
-            #[cfg(feature = "electrum")]
-            Some("electrum") => ClientType::Electrum,
-            #[cfg(feature = "esplora")]
-            Some("esplora") => ClientType::Esplora,
-            #[cfg(feature = "rpc")]
-            Some("rpc") => ClientType::Rpc,
-            #[cfg(feature = "cbf")]
-            Some("cbf") => ClientType::Cbf,
-            _ => return Err(Error::Generic(format!("Invalid client type"))),
-        };
+        let client_type = config
+            .client_type
+            .as_deref()
+            .ok_or_else(|| Error::Generic("Client type missing".into()))
+            .and_then(|s| {
+                ClientType::from_str(s, true)
+                    .map_err(|_| Error::Generic("Invalid client type".into()))
+            })?;
 
         Ok(WalletOpts {
-            wallet: Some(wallet_config.wallet.clone()),
+            wallet: Some(config.wallet.clone()),
             verbose: false,
-            ext_descriptor: wallet_config.ext_descriptor.clone(),
-            int_descriptor: wallet_config.int_descriptor.clone(),
+            ext_descriptor: config.ext_descriptor.clone(),
+            int_descriptor: config.int_descriptor.clone(),
+
             #[cfg(any(
                 feature = "electrum",
                 feature = "esplora",
@@ -136,32 +128,135 @@ impl WalletConfig {
                 feature = "cbf"
             ))]
             client_type,
+
             #[cfg(any(feature = "sqlite", feature = "redb"))]
             database_type,
+
             #[cfg(any(feature = "electrum", feature = "esplora", feature = "rpc"))]
-            url: wallet_config
+            url: config
                 .server_url
                 .clone()
-                .ok_or_else(|| Error::Generic(format!("Server url not found")))?,
+                .ok_or_else(|| Error::Generic("Server url not found".into()))?,
+
             #[cfg(feature = "electrum")]
-            batch_size: wallet_config.batch_size.unwrap_or(10),
+            batch_size: config.batch_size.unwrap_or(10),
+
             #[cfg(feature = "esplora")]
-            parallel_requests: wallet_config.parallel_requests.unwrap_or(5),
+            parallel_requests: config.parallel_requests.unwrap_or(5),
+
             #[cfg(feature = "rpc")]
             basic_auth: (
-                wallet_config
-                    .rpc_user
-                    .clone()
-                    .unwrap_or_else(|| "user".into()),
-                wallet_config
+                config.rpc_user.clone().unwrap_or_else(|| "user".into()),
+                config
                     .rpc_password
                     .clone()
                     .unwrap_or_else(|| "password".into()),
             ),
+
             #[cfg(feature = "rpc")]
-            cookie: wallet_config.cookie.clone(),
+            cookie: config.cookie.clone(),
+
             #[cfg(feature = "cbf")]
             compactfilter_opts: crate::commands::CompactFilterOpts { conn_count: 2 },
         })
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use std::convert::TryInto;
+
+    #[test]
+    fn test_wallet_config_inner_to_opts_conversion() {
+        let wallet_config = WalletConfigInner {
+            wallet: "test_wallet".to_string(),
+            network: "testnet4".to_string(),
+            ext_descriptor: "wpkh([07234a14/84'/1'/0']tpubDCSgT6PaVLQH9h2TAxKryhvkEurUBcYRJc9dhTcMDyahhWiMWfEWvQQX89yaw7w7XU8bcVujoALfxq59VkFATri3Cxm5mkp9kfHfRFDckEh/0/*)#429nsxmg".to_string(),
+            int_descriptor: Some("wpkh([07234a14/84'/1'/0']tpubDCSgT6PaVLQH9h2TAxKryhvkEurUBcYRJc9dhTcMDyahhWiMWfEWvQQX89yaw7w7XU8bcVujoALfxq59VkFATri3Cxm5mkp9kfHfRFDckEh/1/*)#y7qjdnts".to_string()),
+            #[cfg(any(feature = "sqlite", feature = "redb"))]
+            database_type: "sqlite".to_string(),
+            #[cfg(any(feature = "electrum", feature = "esplora", feature = "rpc", feature = "cbf"))]
+            client_type: Some("esplora".to_string()),
+            #[cfg(any(feature = "electrum", feature = "esplora", feature = "rpc"))]
+            server_url: Some(" https://blockstream.info/testnet4/api".to_string()),
+            #[cfg(feature = "electrum")]
+            batch_size: None,
+            #[cfg(feature = "esplora")]
+            parallel_requests: None,
+            #[cfg(feature = "rpc")]
+            rpc_user: None,
+            #[cfg(feature = "rpc")]
+            rpc_password: None,
+            #[cfg(feature = "rpc")]
+            cookie: None,
+        };
+
+        let opts: WalletOpts = (&wallet_config)
+            .try_into()
+            .expect("Conversion should succeed");
+
+        assert_eq!(opts.wallet, Some("test_wallet".to_string()));
+        assert_eq!(
+            opts.ext_descriptor,
+            "wpkh([07234a14/84'/1'/0']tpubDCSgT6PaVLQH9h2TAxKryhvkEurUBcYRJc9dhTcMDyahhWiMWfEWvQQX89yaw7w7XU8bcVujoALfxq59VkFATri3Cxm5mkp9kfHfRFDckEh/0/*)#429nsxmg"
+        );
+
+        #[cfg(any(
+            feature = "electrum",
+            feature = "esplora",
+            feature = "rpc",
+            feature = "cbf"
+        ))]
+        assert_eq!(opts.client_type, ClientType::Esplora);
+
+        #[cfg(feature = "sqlite")]
+        assert_eq!(opts.database_type, DatabaseType::Sqlite);
+
+        #[cfg(feature = "electrum")]
+        assert_eq!(opts.batch_size, 10);
+
+        #[cfg(feature = "esplora")]
+        assert_eq!(opts.parallel_requests, 5);
+    }
+
+    #[cfg(any(
+        feature = "electrum",
+        feature = "esplora",
+        feature = "rpc",
+        feature = "cbf"
+    ))]
+    #[test]
+    fn test_invalid_client_type_fails() {
+        let inner = WalletConfigInner {
+            wallet: "test".to_string(),
+            network: "regtest".to_string(),
+            ext_descriptor: "desc".to_string(),
+            int_descriptor: None,
+            #[cfg(any(feature = "sqlite", feature = "redb"))]
+            database_type: "sqlite".to_string(),
+            #[cfg(any(
+                feature = "electrum",
+                feature = "esplora",
+                feature = "rpc",
+                feature = "cbf"
+            ))]
+            client_type: Some("invalid_backend".to_string()),
+            #[cfg(any(feature = "electrum", feature = "esplora", feature = "rpc"))]
+            server_url: Some("url".to_string()),
+            #[cfg(feature = "electrum")]
+            batch_size: None,
+            #[cfg(feature = "esplora")]
+            parallel_requests: None,
+            #[cfg(feature = "rpc")]
+            rpc_user: None,
+            #[cfg(feature = "rpc")]
+            rpc_password: None,
+            #[cfg(feature = "rpc")]
+            cookie: None,
+        };
+
+        let result: Result<WalletOpts, Error> = (&inner).try_into();
+        assert!(result.is_err());
+    }
+}
index 46c3dc0e5bf26e9aa5a0d6713fd8fa93042c9943..1f867b41928f821a450007d953d7c755da35267e 100644 (file)
@@ -781,7 +781,6 @@ pub fn handle_config_subcommand(
     }
 
     let mut config = WalletConfig::load(datadir)?.unwrap_or(WalletConfig {
-        network,
         wallets: HashMap::new(),
     });
 
@@ -853,7 +852,6 @@ pub fn handle_config_subcommand(
         cookie: wallet_opts.cookie.clone(),
     };
 
-    config.network = network;
     config.wallets.insert(wallet.clone(), wallet_config);
     config.save(datadir)?;