Skip to content

Commit be31c14

Browse files
committed
feat(wallet-init): impl TryFrom for WalletOpts
- refactor config by impl TryFrom trait for WalletOpts - fix top-level network duplicate in config file
1 parent 7bf0982 commit be31c14

File tree

2 files changed

+143
-50
lines changed

2 files changed

+143
-50
lines changed

src/config.rs

Lines changed: 143 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,16 @@ use crate::commands::DatabaseType;
1010
use crate::commands::WalletOpts;
1111
use crate::error::BDKCliError as Error;
1212
use bdk_wallet::bitcoin::Network;
13+
#[cfg(any(feature = "sqlite", feature = "redb"))]
14+
use clap::ValueEnum;
1315
use serde::{Deserialize, Serialize};
1416
use std::collections::HashMap;
1517
use std::fs;
1618
use std::path::Path;
19+
use std::str::FromStr;
1720

1821
#[derive(Debug, Serialize, Deserialize)]
1922
pub struct WalletConfig {
20-
pub network: Network,
2123
pub wallets: HashMap<String, WalletConfigInner>,
2224
}
2325

@@ -80,88 +82,181 @@ impl WalletConfig {
8082

8183
/// Get config for a wallet
8284
pub fn get_wallet_opts(&self, wallet_name: &str) -> Result<WalletOpts, Error> {
83-
let wallet_config = self
84-
.wallets
85+
self.wallets
8586
.get(wallet_name)
86-
.ok_or_else(|| Error::Generic(format!("Wallet {wallet_name} not found in config")))?;
87-
88-
let _network = match wallet_config.network.as_str() {
89-
"bitcoin" => Network::Bitcoin,
90-
"testnet" => Network::Testnet,
91-
"regtest" => Network::Regtest,
92-
"signet" => Network::Signet,
93-
"testnet4" => Network::Testnet4,
94-
_ => {
95-
return Err(Error::Generic("Invalid network".to_string()));
96-
}
97-
};
87+
.ok_or_else(|| Error::Generic(format!("Wallet {wallet_name} not found in config")))?
88+
.try_into()
89+
}
90+
}
91+
92+
impl TryFrom<&WalletConfigInner> for WalletOpts {
93+
type Error = Error;
94+
95+
fn try_from(config: &WalletConfigInner) -> Result<Self, Self::Error> {
96+
let _network = Network::from_str(&config.network)
97+
.map_err(|_| Error::Generic("Invalid network".to_string()))?;
9898

9999
#[cfg(any(feature = "sqlite", feature = "redb"))]
100-
let database_type = match wallet_config.database_type.as_str() {
101-
#[cfg(feature = "sqlite")]
102-
"sqlite" => DatabaseType::Sqlite,
103-
#[cfg(feature = "redb")]
104-
"redb" => DatabaseType::Redb,
105-
_ => {
106-
return Err(Error::Generic("Invalid database type".to_string()));
107-
}
108-
};
100+
let database_type = DatabaseType::from_str(&config.database_type, true)
101+
.map_err(|_| Error::Generic("Invalid database type".to_string()))?;
102+
109103
#[cfg(any(
110104
feature = "electrum",
111105
feature = "esplora",
112106
feature = "rpc",
113107
feature = "cbf"
114108
))]
115-
let client_type = match wallet_config.client_type.as_deref() {
116-
#[cfg(feature = "electrum")]
117-
Some("electrum") => ClientType::Electrum,
118-
#[cfg(feature = "esplora")]
119-
Some("esplora") => ClientType::Esplora,
120-
#[cfg(feature = "rpc")]
121-
Some("rpc") => ClientType::Rpc,
122-
#[cfg(feature = "cbf")]
123-
Some("cbf") => ClientType::Cbf,
124-
_ => return Err(Error::Generic(format!("Invalid client type"))),
125-
};
109+
let client_type = config
110+
.client_type
111+
.as_deref()
112+
.ok_or_else(|| Error::Generic("Client type missing".into()))
113+
.and_then(|s| {
114+
ClientType::from_str(s, true)
115+
.map_err(|_| Error::Generic("Invalid client type".into()))
116+
})?;
126117

127118
Ok(WalletOpts {
128-
wallet: Some(wallet_config.wallet.clone()),
119+
wallet: Some(config.wallet.clone()),
129120
verbose: false,
130-
ext_descriptor: wallet_config.ext_descriptor.clone(),
131-
int_descriptor: wallet_config.int_descriptor.clone(),
121+
ext_descriptor: config.ext_descriptor.clone(),
122+
int_descriptor: config.int_descriptor.clone(),
123+
132124
#[cfg(any(
133125
feature = "electrum",
134126
feature = "esplora",
135127
feature = "rpc",
136128
feature = "cbf"
137129
))]
138130
client_type,
131+
139132
#[cfg(any(feature = "sqlite", feature = "redb"))]
140133
database_type,
134+
141135
#[cfg(any(feature = "electrum", feature = "esplora", feature = "rpc"))]
142-
url: wallet_config
136+
url: config
143137
.server_url
144138
.clone()
145-
.ok_or_else(|| Error::Generic(format!("Server url not found")))?,
139+
.ok_or_else(|| Error::Generic("Server url not found".into()))?,
140+
146141
#[cfg(feature = "electrum")]
147-
batch_size: wallet_config.batch_size.unwrap_or(10),
142+
batch_size: config.batch_size.unwrap_or(10),
143+
148144
#[cfg(feature = "esplora")]
149-
parallel_requests: wallet_config.parallel_requests.unwrap_or(5),
145+
parallel_requests: config.parallel_requests.unwrap_or(5),
146+
150147
#[cfg(feature = "rpc")]
151148
basic_auth: (
152-
wallet_config
153-
.rpc_user
154-
.clone()
155-
.unwrap_or_else(|| "user".into()),
156-
wallet_config
149+
config.rpc_user.clone().unwrap_or_else(|| "user".into()),
150+
config
157151
.rpc_password
158152
.clone()
159153
.unwrap_or_else(|| "password".into()),
160154
),
155+
161156
#[cfg(feature = "rpc")]
162-
cookie: wallet_config.cookie.clone(),
157+
cookie: config.cookie.clone(),
158+
163159
#[cfg(feature = "cbf")]
164160
compactfilter_opts: crate::commands::CompactFilterOpts { conn_count: 2 },
165161
})
166162
}
167163
}
164+
165+
#[cfg(test)]
166+
mod tests {
167+
use super::*;
168+
use std::convert::TryInto;
169+
170+
#[test]
171+
fn test_wallet_config_inner_to_opts_conversion() {
172+
let wallet_config = WalletConfigInner {
173+
wallet: "test_wallet".to_string(),
174+
network: "testnet4".to_string(),
175+
ext_descriptor: "wpkh([07234a14/84'/1'/0']tpubDCSgT6PaVLQH9h2TAxKryhvkEurUBcYRJc9dhTcMDyahhWiMWfEWvQQX89yaw7w7XU8bcVujoALfxq59VkFATri3Cxm5mkp9kfHfRFDckEh/0/*)#429nsxmg".to_string(),
176+
int_descriptor: Some("wpkh([07234a14/84'/1'/0']tpubDCSgT6PaVLQH9h2TAxKryhvkEurUBcYRJc9dhTcMDyahhWiMWfEWvQQX89yaw7w7XU8bcVujoALfxq59VkFATri3Cxm5mkp9kfHfRFDckEh/1/*)#y7qjdnts".to_string()),
177+
#[cfg(any(feature = "sqlite", feature = "redb"))]
178+
database_type: "sqlite".to_string(),
179+
#[cfg(any(feature = "electrum", feature = "esplora", feature = "rpc", feature = "cbf"))]
180+
client_type: Some("esplora".to_string()),
181+
#[cfg(any(feature = "electrum", feature = "esplora", feature = "rpc"))]
182+
server_url: Some(" https://blockstream.info/testnet4/api".to_string()),
183+
#[cfg(feature = "electrum")]
184+
batch_size: None,
185+
#[cfg(feature = "esplora")]
186+
parallel_requests: None,
187+
#[cfg(feature = "rpc")]
188+
rpc_user: None,
189+
#[cfg(feature = "rpc")]
190+
rpc_password: None,
191+
#[cfg(feature = "rpc")]
192+
cookie: None,
193+
};
194+
195+
let opts: WalletOpts = (&wallet_config)
196+
.try_into()
197+
.expect("Conversion should succeed");
198+
199+
assert_eq!(opts.wallet, Some("test_wallet".to_string()));
200+
assert_eq!(
201+
opts.ext_descriptor,
202+
"wpkh([07234a14/84'/1'/0']tpubDCSgT6PaVLQH9h2TAxKryhvkEurUBcYRJc9dhTcMDyahhWiMWfEWvQQX89yaw7w7XU8bcVujoALfxq59VkFATri3Cxm5mkp9kfHfRFDckEh/0/*)#429nsxmg"
203+
);
204+
205+
#[cfg(any(
206+
feature = "electrum",
207+
feature = "esplora",
208+
feature = "rpc",
209+
feature = "cbf"
210+
))]
211+
assert_eq!(opts.client_type, ClientType::Esplora);
212+
213+
#[cfg(feature = "sqlite")]
214+
assert_eq!(opts.database_type, DatabaseType::Sqlite);
215+
216+
#[cfg(feature = "electrum")]
217+
assert_eq!(opts.batch_size, 10);
218+
219+
#[cfg(feature = "esplora")]
220+
assert_eq!(opts.parallel_requests, 5);
221+
}
222+
223+
#[cfg(any(
224+
feature = "electrum",
225+
feature = "esplora",
226+
feature = "rpc",
227+
feature = "cbf"
228+
))]
229+
#[test]
230+
fn test_invalid_client_type_fails() {
231+
let inner = WalletConfigInner {
232+
wallet: "test".to_string(),
233+
network: "regtest".to_string(),
234+
ext_descriptor: "desc".to_string(),
235+
int_descriptor: None,
236+
#[cfg(any(feature = "sqlite", feature = "redb"))]
237+
database_type: "sqlite".to_string(),
238+
#[cfg(any(
239+
feature = "electrum",
240+
feature = "esplora",
241+
feature = "rpc",
242+
feature = "cbf"
243+
))]
244+
client_type: Some("invalid_backend".to_string()),
245+
#[cfg(any(feature = "electrum", feature = "esplora", feature = "rpc"))]
246+
server_url: Some("url".to_string()),
247+
#[cfg(feature = "electrum")]
248+
batch_size: None,
249+
#[cfg(feature = "esplora")]
250+
parallel_requests: None,
251+
#[cfg(feature = "rpc")]
252+
rpc_user: None,
253+
#[cfg(feature = "rpc")]
254+
rpc_password: None,
255+
#[cfg(feature = "rpc")]
256+
cookie: None,
257+
};
258+
259+
let result: Result<WalletOpts, Error> = (&inner).try_into();
260+
assert!(result.is_err());
261+
}
262+
}

src/handlers.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -781,7 +781,6 @@ pub fn handle_config_subcommand(
781781
}
782782

783783
let mut config = WalletConfig::load(datadir)?.unwrap_or(WalletConfig {
784-
network,
785784
wallets: HashMap::new(),
786785
});
787786

@@ -853,7 +852,6 @@ pub fn handle_config_subcommand(
853852
cookie: wallet_opts.cookie.clone(),
854853
};
855854

856-
config.network = network;
857855
config.wallets.insert(wallet.clone(), wallet_config);
858856
config.save(datadir)?;
859857

0 commit comments

Comments
 (0)