@@ -41,6 +41,22 @@ impl ConfigResolver {
4141 ( "cli.stdin_buffer_limit" , "10485760" ) ,
4242 ( "cli.auto_approve" , "false" ) ,
4343 ( "cli.help_text_max_length" , "1000" ) ,
44+ // Namespace-mode aliases (apcore >= 0.15.0 Config Bus)
45+ ( "apcore-cli.stdin_buffer_limit" , "10485760" ) ,
46+ ( "apcore-cli.auto_approve" , "false" ) ,
47+ ( "apcore-cli.help_text_max_length" , "1000" ) ,
48+ ( "apcore-cli.logging_level" , "WARNING" ) ,
49+ ] ;
50+
51+ /// Namespace key → legacy key mapping for backward compatibility.
52+ const NAMESPACE_MAP : & ' static [ ( & ' static str , & ' static str ) ] = & [
53+ ( "apcore-cli.stdin_buffer_limit" , "cli.stdin_buffer_limit" ) ,
54+ ( "apcore-cli.auto_approve" , "cli.auto_approve" ) ,
55+ (
56+ "apcore-cli.help_text_max_length" ,
57+ "cli.help_text_max_length" ,
58+ ) ,
59+ ( "apcore-cli.logging_level" , "logging.level" ) ,
4460 ] ;
4561
4662 /// Create a new `ConfigResolver`.
@@ -94,16 +110,36 @@ impl ConfigResolver {
94110 }
95111
96112 // Tier 3: Config file — key must be present in the flattened map.
113+ // Try both namespace and legacy keys for backward compatibility.
97114 if let Some ( ref file_map) = self . config_file {
98115 if let Some ( value) = file_map. get ( key) {
99116 return Some ( value. clone ( ) ) ;
100117 }
118+ // Try alternate key (namespace ↔ legacy)
119+ if let Some ( alt) = Self :: alternate_key ( key) {
120+ if let Some ( value) = file_map. get ( alt) {
121+ return Some ( value. clone ( ) ) ;
122+ }
123+ }
101124 }
102125
103126 // Tier 4: Built-in defaults.
104127 self . defaults . get ( key) . map ( |s| s. to_string ( ) )
105128 }
106129
130+ /// Look up the alternate key (namespace ↔ legacy) for backward compatibility.
131+ fn alternate_key ( key : & str ) -> Option < & ' static str > {
132+ for & ( ns, legacy) in Self :: NAMESPACE_MAP {
133+ if key == ns {
134+ return Some ( legacy) ;
135+ }
136+ if key == legacy {
137+ return Some ( ns) ;
138+ }
139+ }
140+ None
141+ }
142+
107143 /// Load and flatten a YAML config file into dot-notation keys.
108144 ///
109145 /// Returns `None` if the file does not exist or cannot be parsed.
@@ -346,4 +382,116 @@ mod tests {
346382 let result = resolver. flatten_dict ( map) ;
347383 assert_eq ! ( result. get( "a.b.c" ) , Some ( & "deep" . to_string( ) ) ) ;
348384 }
385+
386+ // ---- Namespace-aware config resolution (apcore >= 0.15.0) ----
387+
388+ #[ test]
389+ fn test_defaults_contain_namespace_keys ( ) {
390+ let resolver = ConfigResolver :: new ( None , None ) ;
391+ for key in [
392+ "apcore-cli.stdin_buffer_limit" ,
393+ "apcore-cli.auto_approve" ,
394+ "apcore-cli.help_text_max_length" ,
395+ "apcore-cli.logging_level" ,
396+ ] {
397+ assert ! (
398+ resolver. defaults. contains_key( key) ,
399+ "missing namespace default: {key}"
400+ ) ;
401+ }
402+ }
403+
404+ #[ test]
405+ fn test_alternate_key_namespace_to_legacy ( ) {
406+ assert_eq ! (
407+ ConfigResolver :: alternate_key( "apcore-cli.stdin_buffer_limit" ) ,
408+ Some ( "cli.stdin_buffer_limit" )
409+ ) ;
410+ assert_eq ! (
411+ ConfigResolver :: alternate_key( "apcore-cli.auto_approve" ) ,
412+ Some ( "cli.auto_approve" )
413+ ) ;
414+ assert_eq ! (
415+ ConfigResolver :: alternate_key( "apcore-cli.logging_level" ) ,
416+ Some ( "logging.level" )
417+ ) ;
418+ }
419+
420+ #[ test]
421+ fn test_alternate_key_legacy_to_namespace ( ) {
422+ assert_eq ! (
423+ ConfigResolver :: alternate_key( "cli.stdin_buffer_limit" ) ,
424+ Some ( "apcore-cli.stdin_buffer_limit" )
425+ ) ;
426+ assert_eq ! (
427+ ConfigResolver :: alternate_key( "cli.auto_approve" ) ,
428+ Some ( "apcore-cli.auto_approve" )
429+ ) ;
430+ assert_eq ! (
431+ ConfigResolver :: alternate_key( "logging.level" ) ,
432+ Some ( "apcore-cli.logging_level" )
433+ ) ;
434+ }
435+
436+ #[ test]
437+ fn test_alternate_key_unknown_returns_none ( ) {
438+ assert_eq ! ( ConfigResolver :: alternate_key( "unknown.key" ) , None ) ;
439+ assert_eq ! ( ConfigResolver :: alternate_key( "extensions.root" ) , None ) ;
440+ }
441+
442+ #[ test]
443+ fn test_resolve_namespace_key_from_legacy_file ( ) {
444+ // Simulate a config file with legacy "cli.stdin_buffer_limit" key
445+ let mut file_map = HashMap :: new ( ) ;
446+ file_map. insert ( "cli.stdin_buffer_limit" . to_string ( ) , "5242880" . to_string ( ) ) ;
447+ let resolver = ConfigResolver {
448+ cli_flags : HashMap :: new ( ) ,
449+ config_file : Some ( file_map) ,
450+ config_path : None ,
451+ defaults : ConfigResolver :: DEFAULTS . iter ( ) . copied ( ) . collect ( ) ,
452+ } ;
453+ // Querying the namespace key should find the legacy key via fallback
454+ let result = resolver. resolve ( "apcore-cli.stdin_buffer_limit" , None , None ) ;
455+ assert_eq ! ( result, Some ( "5242880" . to_string( ) ) ) ;
456+ }
457+
458+ #[ test]
459+ fn test_resolve_legacy_key_from_namespace_file ( ) {
460+ // Simulate a config file with namespace "apcore-cli.auto_approve" key
461+ let mut file_map = HashMap :: new ( ) ;
462+ file_map. insert ( "apcore-cli.auto_approve" . to_string ( ) , "true" . to_string ( ) ) ;
463+ let resolver = ConfigResolver {
464+ cli_flags : HashMap :: new ( ) ,
465+ config_file : Some ( file_map) ,
466+ config_path : None ,
467+ defaults : ConfigResolver :: DEFAULTS . iter ( ) . copied ( ) . collect ( ) ,
468+ } ;
469+ // Querying the legacy key should find the namespace key via fallback
470+ let result = resolver. resolve ( "cli.auto_approve" , None , None ) ;
471+ assert_eq ! ( result, Some ( "true" . to_string( ) ) ) ;
472+ }
473+
474+ #[ test]
475+ fn test_direct_key_takes_precedence_over_alternate ( ) {
476+ let mut file_map = HashMap :: new ( ) ;
477+ file_map. insert ( "cli.help_text_max_length" . to_string ( ) , "500" . to_string ( ) ) ;
478+ file_map. insert (
479+ "apcore-cli.help_text_max_length" . to_string ( ) ,
480+ "2000" . to_string ( ) ,
481+ ) ;
482+ let resolver = ConfigResolver {
483+ cli_flags : HashMap :: new ( ) ,
484+ config_file : Some ( file_map) ,
485+ config_path : None ,
486+ defaults : ConfigResolver :: DEFAULTS . iter ( ) . copied ( ) . collect ( ) ,
487+ } ;
488+ assert_eq ! (
489+ resolver. resolve( "cli.help_text_max_length" , None , None ) ,
490+ Some ( "500" . to_string( ) )
491+ ) ;
492+ assert_eq ! (
493+ resolver. resolve( "apcore-cli.help_text_max_length" , None , None ) ,
494+ Some ( "2000" . to_string( ) )
495+ ) ;
496+ }
349497}
0 commit comments