Deny unknown keys in settings in JSON schema so user gets warnings but settings still parses (#30583)

Ben Kunkle created

Closes #ISSUE

Release Notes:

- Improved checking of Zed settings so that unrecognized keys show
warnings while editing them

Change summary

crates/assistant_settings/src/assistant_settings.rs | 5 +++++
crates/call/src/call_settings.rs                    | 1 +
crates/collab_ui/src/panel_settings.rs              | 3 +++
crates/editor/src/editor_settings.rs                | 1 +
crates/language/src/language_settings.rs            | 1 +
crates/project/src/project_settings.rs              | 1 +
crates/settings/src/settings_store.rs               | 8 ++++++--
7 files changed, 18 insertions(+), 2 deletions(-)

Detailed changes

crates/assistant_settings/src/assistant_settings.rs 🔗

@@ -41,6 +41,7 @@ pub enum NotifyWhenAgentWaiting {
 
 #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
 #[serde(tag = "name", rename_all = "snake_case")]
+#[schemars(deny_unknown_fields)]
 pub enum AssistantProviderContentV1 {
     #[serde(rename = "zed.dev")]
     ZedDotDev { default_model: Option<CloudModel> },
@@ -543,6 +544,7 @@ impl AssistantSettingsContent {
 
 #[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)]
 #[serde(tag = "version")]
+#[schemars(deny_unknown_fields)]
 pub enum VersionedAssistantSettingsContent {
     #[serde(rename = "1")]
     V1(AssistantSettingsContentV1),
@@ -576,6 +578,7 @@ impl Default for VersionedAssistantSettingsContent {
 }
 
 #[derive(Clone, Serialize, Deserialize, JsonSchema, Debug, Default)]
+#[schemars(deny_unknown_fields)]
 pub struct AssistantSettingsContentV2 {
     /// Whether the Assistant is enabled.
     ///
@@ -734,6 +737,7 @@ pub struct ContextServerPresetContent {
 }
 
 #[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)]
+#[schemars(deny_unknown_fields)]
 pub struct AssistantSettingsContentV1 {
     /// Whether the Assistant is enabled.
     ///
@@ -763,6 +767,7 @@ pub struct AssistantSettingsContentV1 {
 }
 
 #[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)]
+#[schemars(deny_unknown_fields)]
 pub struct LegacyAssistantSettingsContent {
     /// Whether to show the assistant panel button in the status bar.
     ///

crates/call/src/call_settings.rs 🔗

@@ -12,6 +12,7 @@ pub struct CallSettings {
 
 /// Configuration of voice calls in Zed.
 #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
+#[schemars(deny_unknown_fields)]
 pub struct CallSettingsContent {
     /// Whether the microphone should be muted when joining a channel or a call.
     ///

crates/collab_ui/src/panel_settings.rs 🔗

@@ -28,6 +28,7 @@ pub struct ChatPanelSettings {
 }
 
 #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
+#[schemars(deny_unknown_fields)]
 pub struct ChatPanelSettingsContent {
     /// When to show the panel button in the status bar.
     ///
@@ -51,6 +52,7 @@ pub struct NotificationPanelSettings {
 }
 
 #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
+#[schemars(deny_unknown_fields)]
 pub struct PanelSettingsContent {
     /// Whether to show the panel button in the status bar.
     ///
@@ -67,6 +69,7 @@ pub struct PanelSettingsContent {
 }
 
 #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
+#[schemars(deny_unknown_fields)]
 pub struct MessageEditorSettings {
     /// Whether to automatically replace emoji shortcodes with emoji characters.
     /// For example: typing `:wave:` gets replaced with `👋`.

crates/editor/src/editor_settings.rs 🔗

@@ -332,6 +332,7 @@ pub enum SnippetSortOrder {
 }
 
 #[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
+#[schemars(deny_unknown_fields)]
 pub struct EditorSettingsContent {
     /// Whether the cursor blinks in the editor.
     ///

crates/language/src/language_settings.rs 🔗

@@ -381,6 +381,7 @@ fn default_lsp_fetch_timeout_ms() -> u64 {
 
 /// The settings for a particular language.
 #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
+#[schemars(deny_unknown_fields)]
 pub struct LanguageSettingsContent {
     /// How many columns a tab should occupy.
     ///

crates/project/src/project_settings.rs 🔗

@@ -36,6 +36,7 @@ use crate::{
 };
 
 #[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
+#[schemars(deny_unknown_fields)]
 pub struct ProjectSettings {
     /// Configuration for language servers.
     ///

crates/settings/src/settings_store.rs 🔗

@@ -1966,7 +1966,8 @@ mod tests {
     }
 
     #[derive(Default, Clone, Serialize, Deserialize, JsonSchema)]
-    struct UserSettingsJson {
+    #[schemars(deny_unknown_fields)]
+    struct UserSettingsContent {
         name: Option<String>,
         age: Option<u32>,
         staff: Option<bool>,
@@ -1974,7 +1975,7 @@ mod tests {
 
     impl Settings for UserSettings {
         const KEY: Option<&'static str> = Some("user");
-        type FileContent = UserSettingsJson;
+        type FileContent = UserSettingsContent;
 
         fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
             sources.json_merge()
@@ -2008,6 +2009,7 @@ mod tests {
     }
 
     #[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
+    #[schemars(deny_unknown_fields)]
     struct MultiKeySettingsJson {
         key1: Option<String>,
         key2: Option<String>,
@@ -2046,6 +2048,7 @@ mod tests {
     }
 
     #[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema)]
+    #[schemars(deny_unknown_fields)]
     struct JournalSettingsJson {
         pub path: Option<String>,
         pub hour_format: Option<HourFormat>,
@@ -2076,6 +2079,7 @@ mod tests {
     }
 
     #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
+    #[schemars(deny_unknown_fields)]
     struct LanguageSettingEntry {
         language_setting_1: Option<bool>,
         language_setting_2: Option<bool>,