Fix more instances of JSON schema getting clobbered when attaching references (#15339)

Marshall Bowers created

This PR extends the fix from #15336 to more places that had the same
issue.

An `add_references_to_properties` helper function has been added to
handle these cases uniformly.

Release Notes:

- N/A

Change summary

Cargo.lock                               |  1 
crates/language/src/language_settings.rs | 16 +++-------
crates/settings/Cargo.toml               |  1 
crates/settings/src/json_schema.rs       | 36 ++++++++++++++++++++++++++
crates/settings/src/settings.rs          |  2 +
crates/terminal/src/terminal_settings.rs | 28 +++++++-------------
crates/theme/src/settings.rs             | 35 +++++++------------------
7 files changed, 65 insertions(+), 54 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -9645,6 +9645,7 @@ dependencies = [
  "gpui",
  "indoc",
  "lazy_static",
+ "log",
  "paths",
  "pretty_assertions",
  "release_channel",

crates/language/src/language_settings.rs 🔗

@@ -16,7 +16,7 @@ use serde::{
     Deserialize, Deserializer, Serialize,
 };
 use serde_json::Value;
-use settings::{Settings, SettingsLocation, SettingsSources};
+use settings::{add_references_to_properties, Settings, SettingsLocation, SettingsSources};
 use std::{num::NonZeroU32, path::Path, sync::Arc};
 use util::serde::default_true;
 
@@ -1009,16 +1009,10 @@ impl settings::Settings for AllLanguageSettings {
             .definitions
             .extend([("Languages".into(), languages_object_schema.into())]);
 
-        root_schema
-            .schema
-            .object
-            .as_mut()
-            .unwrap()
-            .properties
-            .extend([(
-                "languages".to_owned(),
-                Schema::new_ref("#/definitions/Languages".into()),
-            )]);
+        add_references_to_properties(
+            &mut root_schema,
+            &[("languages", "#/definitions/Languages")],
+        );
 
         root_schema
     }

crates/settings/Cargo.toml 🔗

@@ -22,6 +22,7 @@ fs.workspace = true
 futures.workspace = true
 gpui.workspace = true
 lazy_static.workspace = true
+log.workspace = true
 paths.workspace = true
 release_channel.workspace = true
 rust-embed.workspace = true

crates/settings/src/json_schema.rs 🔗

@@ -0,0 +1,36 @@
+use schemars::schema::{RootSchema, Schema};
+
+type PropertyName<'a> = &'a str;
+type ReferencePath<'a> = &'a str;
+
+/// Modifies the provided [`RootSchema`] by adding references to all of the specified properties.
+///
+/// # Examples
+///
+/// ```
+/// # let root_schema = RootSchema::default();
+/// add_references_to_properties(&mut root_schema, &[
+///     ("property_a", "#/definitions/DefinitionA"),
+///     ("property_b", "#/definitions/DefinitionB"),
+/// ])
+/// ```
+pub fn add_references_to_properties(
+    root_schema: &mut RootSchema,
+    properties_with_references: &[(PropertyName, ReferencePath)],
+) {
+    for (property, definition) in properties_with_references {
+        let Some(schema) = root_schema.schema.object().properties.get_mut(*property) else {
+            log::warn!("property '{property}' not found in JSON schema");
+            continue;
+        };
+
+        match schema {
+            Schema::Object(schema) => {
+                schema.reference = Some(definition.to_string());
+            }
+            Schema::Bool(_) => {
+                // Boolean schemas can't have references.
+            }
+        }
+    }
+}

crates/settings/src/settings.rs 🔗

@@ -1,4 +1,5 @@
 mod editable_setting_control;
+mod json_schema;
 mod keymap_file;
 mod settings_file;
 mod settings_store;
@@ -9,6 +10,7 @@ use std::{borrow::Cow, str};
 use util::asset_str;
 
 pub use editable_setting_control::*;
+pub use json_schema::*;
 pub use keymap_file::KeymapFile;
 pub use settings_file::*;
 pub use settings_store::{

crates/terminal/src/terminal_settings.rs 🔗

@@ -4,12 +4,12 @@ use gpui::{
 };
 use schemars::{
     gen::SchemaGenerator,
-    schema::{ArrayValidation, InstanceType, RootSchema, Schema, SchemaObject},
+    schema::{ArrayValidation, InstanceType, RootSchema, SchemaObject},
     JsonSchema,
 };
 use serde_derive::{Deserialize, Serialize};
 use serde_json::Value;
-use settings::{SettingsJsonSchemaParams, SettingsSources};
+use settings::{add_references_to_properties, SettingsJsonSchemaParams, SettingsSources};
 use std::path::PathBuf;
 use task::Shell;
 
@@ -231,22 +231,14 @@ impl settings::Settings for TerminalSettings {
             ("FontFamilies".into(), font_family_schema.into()),
             ("FontFallbacks".into(), font_fallback_schema.into()),
         ]);
-        root_schema
-            .schema
-            .object
-            .as_mut()
-            .unwrap()
-            .properties
-            .extend([
-                (
-                    "font_family".to_owned(),
-                    Schema::new_ref("#/definitions/FontFamilies".into()),
-                ),
-                (
-                    "font_fallbacks".to_owned(),
-                    Schema::new_ref("#/definitions/FontFallbacks".into()),
-                ),
-            ]);
+
+        add_references_to_properties(
+            &mut root_schema,
+            &[
+                ("font_family", "#/definitions/FontFamilies"),
+                ("font_fallbacks", "#/definitions/FontFallbacks"),
+            ],
+        );
 
         root_schema
     }

crates/theme/src/settings.rs 🔗

@@ -15,7 +15,7 @@ use schemars::{
 };
 use serde::{Deserialize, Serialize};
 use serde_json::Value;
-use settings::{Settings, SettingsJsonSchemaParams, SettingsSources};
+use settings::{add_references_to_properties, Settings, SettingsJsonSchemaParams, SettingsSources};
 use std::sync::Arc;
 use util::ResultExt as _;
 
@@ -649,30 +649,15 @@ impl settings::Settings for ThemeSettings {
             ("FontFallbacks".into(), font_fallback_schema.into()),
         ]);
 
-        // The list of properties that should reference another definition in
-        // the schema.
-        let properties_with_references = vec![
-            ("buffer_font_family", "#/definitions/FontFamilies"),
-            ("buffer_font_fallbacks", "#/definitions/FontFallbacks"),
-            ("ui_font_family", "#/definitions/FontFamilies"),
-            ("ui_font_fallbacks", "#/definitions/FontFallbacks"),
-        ];
-
-        for (property, definition) in properties_with_references {
-            let Some(schema) = root_schema.schema.object().properties.get_mut(property) else {
-                log::warn!("property '{property}' not found in JSON schema");
-                continue;
-            };
-
-            match schema {
-                Schema::Object(schema) => {
-                    schema.reference = Some(definition.into());
-                }
-                Schema::Bool(_) => {
-                    // Boolean schemas can't have references.
-                }
-            }
-        }
+        add_references_to_properties(
+            &mut root_schema,
+            &[
+                ("buffer_font_family", "#/definitions/FontFamilies"),
+                ("buffer_font_fallbacks", "#/definitions/FontFallbacks"),
+                ("ui_font_family", "#/definitions/FontFamilies"),
+                ("ui_font_fallbacks", "#/definitions/FontFallbacks"),
+            ],
+        );
 
         root_schema
     }