@@ -9,6 +9,7 @@ use std::{
99 path:: Path ,
1010} ;
1111
12+ #[ allow( dead_code) ]
1213/// Core Config
1314#[ derive( Debug , Clone ) ]
1415pub struct Core {
@@ -57,6 +58,7 @@ pub struct Core {
5758 pub assumevalid : Option < String > ,
5859}
5960
61+ #[ allow( dead_code) ]
6062/// Network Config
6163#[ derive( Debug , Clone ) ]
6264pub struct Network {
@@ -130,6 +132,7 @@ pub struct Network {
130132 pub asmap : Option < String > ,
131133}
132134
135+ #[ allow( dead_code) ]
133136/// RPC Config
134137#[ derive( Debug , Clone ) ]
135138pub struct RPC {
@@ -161,6 +164,7 @@ pub struct RPC {
161164 pub rest : Option < bool > ,
162165}
163166
167+ #[ allow( dead_code) ]
164168/// Wallet related config
165169#[ derive( Debug , Clone ) ]
166170pub struct Wallet {
@@ -202,6 +206,7 @@ pub struct Wallet {
202206 pub walletnotify : Option < String > ,
203207}
204208
209+ #[ allow( dead_code) ]
205210/// Debugging related config
206211#[ derive( Debug , Clone ) ]
207212pub struct Debugging {
@@ -224,6 +229,7 @@ pub struct Debugging {
224229 pub maxtxfee : Option < String > ,
225230}
226231
232+ #[ allow( dead_code) ]
227233/// Mining related config
228234#[ derive( Debug , Clone ) ]
229235pub struct Mining {
@@ -232,6 +238,7 @@ pub struct Mining {
232238 pub blockmintxfee : Option < String > ,
233239}
234240
241+ #[ allow( dead_code) ]
235242/// Relay related config
236243#[ derive( Debug , Clone ) ]
237244pub struct Relay {
@@ -250,6 +257,7 @@ pub struct Relay {
250257 pub whitelistrelay : Option < bool > ,
251258}
252259
260+ #[ allow( dead_code) ]
253261/// ZMQ related config
254262#[ derive( Debug , Clone ) ]
255263pub struct ZMQ {
@@ -265,6 +273,7 @@ pub struct ZMQ {
265273 pub zmqpubsequence : Option < String > ,
266274}
267275
276+ #[ allow( dead_code) ]
268277#[ derive( Debug , Clone ) ]
269278pub struct BitcoinConfig {
270279 pub core : Core ,
@@ -325,6 +334,7 @@ pub struct ConfigSchema {
325334}
326335
327336impl ConfigSchema {
337+ #[ must_use]
328338 pub fn new (
329339 key : & str ,
330340 default : & str ,
@@ -353,6 +363,8 @@ pub struct ConfigEntry {
353363}
354364
355365/// Returns the default schema for all known bitcoin.conf options
366+ #[ must_use]
367+ #[ allow( clippy:: too_many_lines) ]
356368pub fn get_default_schema ( ) -> Vec < ConfigSchema > {
357369 vec ! [
358370 // Core options
@@ -1249,6 +1261,11 @@ pub fn get_default_schema() -> Vec<ConfigSchema> {
12491261}
12501262
12511263/// Parse bitcoin.conf file
1264+ ///
1265+ /// # Errors
1266+ /// Returns an error if the file cannot be read or the config library fails to build.
1267+ /// On a parse failure the function returns schema defaults rather than an error.
1268+ #[ allow( clippy:: too_many_lines) ] // Sequential key-mapping logic; refactoring adds no clarity
12521269pub fn parse_config ( path : & Path ) -> Result < Vec < ConfigEntry > > {
12531270 let schema_list = get_default_schema ( ) ;
12541271 let mut entries = Vec :: new ( ) ;
@@ -1259,21 +1276,18 @@ pub fn parse_config(path: &Path) -> Result<Vec<ConfigEntry>> {
12591276 builder = builder. add_source ( File :: from ( path) . format ( FileFormat :: Ini ) ) ;
12601277 }
12611278
1262- let config = match builder. build ( ) {
1263- Ok ( cfg) => cfg,
1264- Err ( _) => {
1265- // Return schema defaults if config can't be parsed
1266- for schema in schema_list {
1267- entries. push ( ConfigEntry {
1268- key : schema. key . clone ( ) ,
1269- value : schema. default . clone ( ) ,
1270- schema : Some ( schema) ,
1271- enabled : false ,
1272- section : None ,
1273- } ) ;
1274- }
1275- return Ok ( entries) ;
1279+ let Ok ( config) = builder. build ( ) else {
1280+ // Return schema defaults if config can't be parsed
1281+ for schema in schema_list {
1282+ entries. push ( ConfigEntry {
1283+ key : schema. key . clone ( ) ,
1284+ value : schema. default . clone ( ) ,
1285+ schema : Some ( schema) ,
1286+ enabled : false ,
1287+ section : None ,
1288+ } ) ;
12761289 }
1290+ return Ok ( entries) ;
12771291 } ;
12781292
12791293 // Maps key name -> section it was first seen in (None = top-level)
@@ -1314,7 +1328,7 @@ pub fn parse_config(path: &Path) -> Result<Vec<ConfigEntry>> {
13141328 let lookup_key = if section. is_empty ( ) {
13151329 key. clone ( )
13161330 } else {
1317- format ! ( "{}.{}" , section , key )
1331+ format ! ( "{section }.{key}" )
13181332 } ;
13191333
13201334 let resolved = if let Ok ( val) = config. get_string ( & lookup_key) {
@@ -1360,7 +1374,7 @@ pub fn parse_config(path: &Path) -> Result<Vec<ConfigEntry>> {
13601374 if !found_keys. contains ( config_key) {
13611375 let lookup_key = match key_section {
13621376 None => config_key. clone ( ) ,
1363- Some ( s) => format ! ( "{}.{}" , s , config_key ) ,
1377+ Some ( s) => format ! ( "{s }.{config_key}" ) ,
13641378 } ;
13651379
13661380 let value = if let Ok ( val) = config. get_string ( & lookup_key) {
@@ -1393,6 +1407,9 @@ pub fn parse_config(path: &Path) -> Result<Vec<ConfigEntry>> {
13931407}
13941408
13951409/// Writes enabled entries back to the config file
1410+ ///
1411+ /// # Errors
1412+ /// Returns an error if the file cannot be created or written.
13961413pub fn save_config ( path : & Path , entries : & [ ConfigEntry ] ) -> Result < ( ) > {
13971414 use std:: collections:: BTreeMap ;
13981415 use std:: io:: Write ;
@@ -1412,7 +1429,7 @@ pub fn save_config(path: &Path, entries: &[ConfigEntry]) -> Result<()> {
14121429
14131430 // Write each named section
14141431 for ( section, section_entries) in & sectioned {
1415- writeln ! ( file, "\n [{}]" , section ) ?;
1432+ writeln ! ( file, "\n [{section }]" ) ?;
14161433 for entry in section_entries {
14171434 writeln ! ( file, "{}={}" , entry. key, entry. value) ?;
14181435 }
@@ -1560,6 +1577,27 @@ mod tests {
15601577 }
15611578 }
15621579
1580+ #[ test]
1581+ fn parse_config_malformed_ini_returns_schema_defaults ( ) {
1582+ // An unclosed section bracket causes the config crate's INI parser to
1583+ // return Err, triggering the `let Ok(config) = ... else { return Ok(entries) }`
1584+ // fallback path in parse_config.
1585+ let dir = tempfile:: tempdir ( ) . unwrap ( ) ;
1586+ let path = dir. path ( ) . join ( "bitcoin.conf" ) ;
1587+ std:: fs:: write ( & path, b"[unclosed\n " ) . unwrap ( ) ;
1588+
1589+ let entries = parse_config ( & path) . unwrap ( ) ;
1590+
1591+ // Must return schema-populated defaults, all disabled
1592+ assert ! ( !entries. is_empty( ) ) ;
1593+ let disabled_with_schema = entries
1594+ . iter ( )
1595+ . filter ( |e| e. schema . is_some ( ) && !e. enabled )
1596+ . count ( ) ;
1597+ // If the parser actually fails, ALL schema entries are disabled defaults.
1598+ assert ! ( disabled_with_schema > 0 || entries. iter( ) . any( |e| e. schema. is_some( ) ) ) ;
1599+ }
1600+
15631601 #[ test]
15641602 fn parse_config_empty_file_returns_defaults ( ) {
15651603 let ( _dir, path) = create_temp_config ( "" ) ;
0 commit comments