@@ -10,14 +10,16 @@ use crate::commands::DatabaseType;
1010use crate :: commands:: WalletOpts ;
1111use crate :: error:: BDKCliError as Error ;
1212use bdk_wallet:: bitcoin:: Network ;
13+ #[ cfg( any( feature = "sqlite" , feature = "redb" ) ) ]
14+ use clap:: ValueEnum ;
1315use serde:: { Deserialize , Serialize } ;
1416use std:: collections:: HashMap ;
1517use std:: fs;
1618use std:: path:: Path ;
19+ use std:: str:: FromStr ;
1720
1821#[ derive( Debug , Serialize , Deserialize ) ]
1922pub 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+ }
0 commit comments