From: Vihiga Tyonum Date: Sun, 18 Jan 2026 23:18:21 +0000 (+0100) Subject: feat(wallet-init): impl TryFrom for WalletOpts X-Git-Url: http://internal-gitweb-vhost/%22https:/parse/scripts/database/-script/FromScriptException.WitnessProgram.html?a=commitdiff_plain;h=be31c14391176a9d38584f81082efbe540c0f70b;p=bdk-cli feat(wallet-init): impl TryFrom for WalletOpts - refactor config by impl TryFrom trait for WalletOpts - fix top-level network duplicate in config file --- diff --git a/src/config.rs b/src/config.rs index f024f0b..69c0238 100644 --- a/src/config.rs +++ b/src/config.rs @@ -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, } @@ -80,55 +82,45 @@ impl WalletConfig { /// Get config for a wallet pub fn get_wallet_opts(&self, wallet_name: &str) -> Result { - 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 { + 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 = (&inner).try_into(); + assert!(result.is_err()); + } +} diff --git a/src/handlers.rs b/src/handlers.rs index 46c3dc0..1f867b4 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -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)?;