@@ -718,9 +718,15 @@ impl Config {
718718 let mut should_update = false ;
719719
720720 if let Some ( change) = change. user_config_change {
721- if let Ok ( change) = toml:: from_str ( & change) {
721+ if let Ok ( table) = toml:: from_str ( & change) {
722+ validate_toml_table (
723+ GlobalLocalConfigInput :: FIELDS ,
724+ & table,
725+ & mut String :: new ( ) ,
726+ & mut toml_errors,
727+ ) ;
722728 config. user_config =
723- Some ( GlobalLocalConfigInput :: from_toml ( change , & mut toml_errors) ) ;
729+ Some ( GlobalLocalConfigInput :: from_toml ( table , & mut toml_errors) ) ;
724730 should_update = true ;
725731 }
726732 }
@@ -748,21 +754,39 @@ impl Config {
748754 }
749755
750756 if let Some ( change) = change. root_ratoml_change {
751- if let Ok ( change) = toml:: from_str ( & change) {
752- config. root_ratoml =
753- Some ( GlobalLocalConfigInput :: from_toml ( change, & mut toml_errors) ) ;
754- should_update = true ;
757+ match toml:: from_str ( & change) {
758+ Ok ( table) => {
759+ validate_toml_table (
760+ GlobalLocalConfigInput :: FIELDS ,
761+ & table,
762+ & mut String :: new ( ) ,
763+ & mut toml_errors,
764+ ) ;
765+ config. root_ratoml =
766+ Some ( GlobalLocalConfigInput :: from_toml ( table, & mut toml_errors) ) ;
767+ should_update = true ;
768+ }
769+ Err ( e) => toml_errors. push ( ( "invalid toml file" . to_owned ( ) , e) ) ,
755770 }
756771 }
757772
758773 if let Some ( change) = change. ratoml_file_change {
759774 for ( source_root_id, ( _, text) ) in change {
760775 if let Some ( text) = text {
761- if let Ok ( change) = toml:: from_str ( & text) {
762- config. ratoml_files . insert (
763- source_root_id,
764- LocalConfigInput :: from_toml ( & change, & mut toml_errors) ,
765- ) ;
776+ match toml:: from_str ( & text) {
777+ Ok ( table) => {
778+ validate_toml_table (
779+ & [ LocalConfigInput :: FIELDS ] ,
780+ & table,
781+ & mut String :: new ( ) ,
782+ & mut toml_errors,
783+ ) ;
784+ config. ratoml_files . insert (
785+ source_root_id,
786+ LocalConfigInput :: from_toml ( & table, & mut toml_errors) ,
787+ ) ;
788+ }
789+ Err ( e) => toml_errors. push ( ( "invalid toml file" . to_owned ( ) , e) ) ,
766790 }
767791 }
768792 }
@@ -792,7 +816,7 @@ impl Config {
792816 scope,
793817 ) {
794818 Some ( snippet) => config. snippets . push ( snippet) ,
795- None => error_sink. 0 . push ( ConfigErrorInner :: JsonError (
819+ None => error_sink. 0 . push ( ConfigErrorInner :: Json (
796820 format ! ( "snippet {name} is invalid" ) ,
797821 <serde_json:: Error as serde:: de:: Error >:: custom (
798822 "snippet path is invalid or triggers are missing" ,
@@ -801,8 +825,11 @@ impl Config {
801825 }
802826 }
803827
828+ error_sink. 0 . extend ( json_errors. into_iter ( ) . map ( |( a, b) | ConfigErrorInner :: Json ( a, b) ) ) ;
829+ error_sink. 0 . extend ( toml_errors. into_iter ( ) . map ( |( a, b) | ConfigErrorInner :: Toml ( a, b) ) ) ;
830+
804831 if config. check_command ( ) . is_empty ( ) {
805- error_sink. 0 . push ( ConfigErrorInner :: JsonError (
832+ error_sink. 0 . push ( ConfigErrorInner :: Json (
806833 "/check/command" . to_owned ( ) ,
807834 serde_json:: Error :: custom ( "expected a non-empty string" ) ,
808835 ) ) ;
@@ -836,14 +863,9 @@ impl ConfigChange {
836863 vfs_path : VfsPath ,
837864 content : Option < String > ,
838865 ) -> Option < ( VfsPath , Option < String > ) > {
839- if let Some ( changes) = self . ratoml_file_change . as_mut ( ) {
840- changes. insert ( source_root, ( vfs_path, content) )
841- } else {
842- let mut map = FxHashMap :: default ( ) ;
843- map. insert ( source_root, ( vfs_path, content) ) ;
844- self . ratoml_file_change = Some ( map) ;
845- None
846- }
866+ self . ratoml_file_change
867+ . get_or_insert_with ( Default :: default)
868+ . insert ( source_root, ( vfs_path, content) )
847869 }
848870
849871 pub fn change_user_config ( & mut self , content : Option < String > ) {
@@ -1058,7 +1080,7 @@ pub struct ClientCommandsConfig {
10581080
10591081#[ derive( Debug ) ]
10601082pub enum ConfigErrorInner {
1061- JsonError ( String , serde_json:: Error ) ,
1083+ Json ( String , serde_json:: Error ) ,
10621084 Toml ( String , toml:: de:: Error ) ,
10631085}
10641086
@@ -1074,7 +1096,7 @@ impl ConfigError {
10741096impl fmt:: Display for ConfigError {
10751097 fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
10761098 let errors = self . 0 . iter ( ) . format_with ( "\n " , |inner, f| match inner {
1077- ConfigErrorInner :: JsonError ( key, e) => {
1099+ ConfigErrorInner :: Json ( key, e) => {
10781100 f ( key) ?;
10791101 f ( & ": " ) ?;
10801102 f ( e)
@@ -2607,6 +2629,8 @@ macro_rules! _config_data {
26072629
26082630 #[ allow( unused, clippy:: ptr_arg) ]
26092631 impl $input {
2632+ const FIELDS : & ' static [ & ' static str ] = & [ $( stringify!( $field) ) ,* ] ;
2633+
26102634 fn from_json( json: & mut serde_json:: Value , error_sink: & mut Vec <( String , serde_json:: Error ) >) -> Self {
26112635 Self { $(
26122636 $field: get_field(
@@ -2645,8 +2669,7 @@ macro_rules! _config_data {
26452669 mod $modname {
26462670 #[ test]
26472671 fn fields_are_sorted( ) {
2648- let field_names: & ' static [ & ' static str ] = & [ $( stringify!( $field) ) ,* ] ;
2649- field_names. windows( 2 ) . for_each( |w| assert!( w[ 0 ] <= w[ 1 ] , "{} <= {} does not hold" , w[ 0 ] , w[ 1 ] ) ) ;
2672+ super :: $input:: FIELDS . windows( 2 ) . for_each( |w| assert!( w[ 0 ] <= w[ 1 ] , "{} <= {} does not hold" , w[ 0 ] , w[ 1 ] ) ) ;
26502673 }
26512674 }
26522675 } ;
@@ -2711,6 +2734,8 @@ struct GlobalLocalConfigInput {
27112734}
27122735
27132736impl GlobalLocalConfigInput {
2737+ const FIELDS : & ' static [ & ' static [ & ' static str ] ] =
2738+ & [ GlobalConfigInput :: FIELDS , LocalConfigInput :: FIELDS ] ;
27142739 fn from_toml (
27152740 toml : toml:: Table ,
27162741 error_sink : & mut Vec < ( String , toml:: de:: Error ) > ,
@@ -3148,6 +3173,35 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json
31483173 map. into ( )
31493174}
31503175
3176+ fn validate_toml_table (
3177+ known_ptrs : & [ & [ & ' static str ] ] ,
3178+ toml : & toml:: Table ,
3179+ ptr : & mut String ,
3180+ error_sink : & mut Vec < ( String , toml:: de:: Error ) > ,
3181+ ) {
3182+ let verify = |ptr : & String | known_ptrs. iter ( ) . any ( |ptrs| ptrs. contains ( & ptr. as_str ( ) ) ) ;
3183+
3184+ let l = ptr. len ( ) ;
3185+ for ( k, v) in toml {
3186+ if !ptr. is_empty ( ) {
3187+ ptr. push ( '_' ) ;
3188+ }
3189+ ptr. push_str ( k) ;
3190+
3191+ match v {
3192+ // This is a table config, any entry in it is therefor valid
3193+ toml:: Value :: Table ( _) if verify ( ptr) => ( ) ,
3194+ toml:: Value :: Table ( table) => validate_toml_table ( known_ptrs, table, ptr, error_sink) ,
3195+ _ if !verify ( ptr) => {
3196+ error_sink. push ( ( ptr. clone ( ) , toml:: de:: Error :: custom ( "unexpected field" ) ) )
3197+ }
3198+ _ => ( ) ,
3199+ }
3200+
3201+ ptr. truncate ( l) ;
3202+ }
3203+ }
3204+
31513205#[ cfg( test) ]
31523206fn manual ( fields : & [ SchemaField ] ) -> String {
31533207 fields. iter ( ) . fold ( String :: new ( ) , |mut acc, ( field, _ty, doc, default) | {
@@ -3387,4 +3441,60 @@ mod tests {
33873441 matches!( config. flycheck( ) , FlycheckConfig :: CargoCommand { options, .. } if options. target_dir == Some ( Utf8PathBuf :: from( "other_folder" ) ) )
33883442 ) ;
33893443 }
3444+
3445+ #[ test]
3446+ fn toml_unknown_key ( ) {
3447+ let config = Config :: new (
3448+ AbsPathBuf :: try_from ( project_root ( ) ) . unwrap ( ) ,
3449+ Default :: default ( ) ,
3450+ vec ! [ ] ,
3451+ None ,
3452+ None ,
3453+ ) ;
3454+
3455+ let mut change = ConfigChange :: default ( ) ;
3456+
3457+ change. change_root_ratoml ( Some (
3458+ toml:: toml! {
3459+ [ cargo. cfgs]
3460+ these = "these"
3461+ should = "should"
3462+ be = "be"
3463+ valid = "valid"
3464+
3465+ [ invalid. config]
3466+ err = "error"
3467+
3468+ [ cargo]
3469+ target = "ok"
3470+
3471+ // FIXME: This should be an error
3472+ [ cargo. sysroot]
3473+ non-table = "expected"
3474+ }
3475+ . to_string ( ) ,
3476+ ) ) ;
3477+
3478+ let ( _, e, _) = config. apply_change ( change) ;
3479+ expect_test:: expect![ [ r#"
3480+ ConfigError(
3481+ [
3482+ Toml(
3483+ "invalid_config_err",
3484+ Error {
3485+ inner: Error {
3486+ inner: TomlError {
3487+ message: "unexpected field",
3488+ raw: None,
3489+ keys: [],
3490+ span: None,
3491+ },
3492+ },
3493+ },
3494+ ),
3495+ ],
3496+ )
3497+ "# ] ]
3498+ . assert_debug_eq ( & e) ;
3499+ }
33903500}
0 commit comments