Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion crates/agent_settings/src/agent_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,6 @@ impl AgentSettingsContent {
}

#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug, Default)]
#[schemars(deny_unknown_fields)]
pub struct AgentSettingsContent {
/// Whether the Agent is enabled.
///
Expand Down
1 change: 0 additions & 1 deletion crates/call/src/call_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ 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.
///
Expand Down
3 changes: 0 additions & 3 deletions crates/collab_ui/src/panel_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ 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.
///
Expand All @@ -52,7 +51,6 @@ 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.
///
Expand All @@ -69,7 +67,6 @@ 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 `👋`.
Expand Down
1 change: 0 additions & 1 deletion crates/editor/src/editor_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,6 @@ pub enum SnippetSortOrder {
}

#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
#[schemars(deny_unknown_fields)]
pub struct EditorSettingsContent {
/// Whether the cursor blinks in the editor.
///
Expand Down
3 changes: 2 additions & 1 deletion crates/gpui/src/text_system/font_features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,8 @@ impl JsonSchema for FontFeatures {
"minimum": 0,
"multipleOf": 1
}
}
},
"additionalProperties": false
})
}
}
Expand Down
1 change: 0 additions & 1 deletion crates/language/src/language_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,6 @@ 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.
///
Expand Down
2 changes: 1 addition & 1 deletion crates/languages/src/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ impl JsonLspAdapter {

#[cfg(debug_assertions)]
fn generate_inspector_style_schema() -> serde_json_lenient::Value {
let schema = schemars::generate::SchemaSettings::draft07()
let schema = schemars::generate::SchemaSettings::draft2019_09()
.into_generator()
.root_schema_for::<gpui::StyleRefinement>();

Expand Down
1 change: 0 additions & 1 deletion crates/project/src/project_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ use crate::{
};

#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
#[schemars(deny_unknown_fields)]
pub struct ProjectSettings {
/// Configuration for language servers.
///
Expand Down
2 changes: 1 addition & 1 deletion crates/settings/src/keymap_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ impl KeymapFile {
}

pub fn generate_json_schema_for_registered_actions(cx: &mut App) -> Value {
let mut generator = schemars::generate::SchemaSettings::draft07().into_generator();
let mut generator = schemars::generate::SchemaSettings::draft2019_09().into_generator();

let action_schemas = cx.action_schemas(&mut generator);
let deprecations = cx.deprecated_actions_to_preferred_actions();
Expand Down
46 changes: 41 additions & 5 deletions crates/settings/src/settings_json.rs
Original file line number Diff line number Diff line change
@@ -1,42 +1,46 @@
use anyhow::Result;
use gpui::App;
use schemars::{JsonSchema, Schema};
use schemars::{JsonSchema, Schema, transform::transform_subschemas};
use serde::{Serialize, de::DeserializeOwned};
use serde_json::Value;
use std::{ops::Range, sync::LazyLock};
use tree_sitter::{Query, StreamingIterator as _};
use util::RangeExt;

/// Parameters that are used when generating some JSON schemas at runtime.
pub struct SettingsJsonSchemaParams<'a> {
pub language_names: &'a [String],
pub font_names: &'a [String],
}

/// Value registered which specifies JSON schemas that are generated at runtime.
pub struct ParameterizedJsonSchema {
pub add_and_get_ref:
fn(&mut schemars::SchemaGenerator, &SettingsJsonSchemaParams, &App) -> schemars::Schema,
}

inventory::collect!(ParameterizedJsonSchema);

const DEFS_PATH: &str = "#/$defs/";

/// Replaces the JSON schema definition for some type, and returns a reference to it.
pub fn replace_subschema<T: JsonSchema>(
generator: &mut schemars::SchemaGenerator,
schema: schemars::Schema,
) -> schemars::Schema {
const DEFINITIONS_PATH: &str = "#/definitions/";
// The key in definitions may not match T::schema_name() if multiple types have the same name.
// This is a workaround for there being no straightforward way to get the key used for a type -
// see https://github.com/GREsau/schemars/issues/449
let ref_schema = generator.subschema_for::<T>();
if let Some(serde_json::Value::String(definition_pointer)) = ref_schema.get("$ref") {
if let Some(definition_name) = definition_pointer.strip_prefix(DEFINITIONS_PATH) {
if let Some(definition_name) = definition_pointer.strip_prefix(DEFS_PATH) {
generator
.definitions_mut()
.insert(definition_name.to_string(), schema.to_value());
return ref_schema;
} else {
log::error!(
"bug: expected `$ref` field to start with {DEFINITIONS_PATH}, \
"bug: expected `$ref` field to start with {DEFS_PATH}, \
got {definition_pointer}"
);
}
Expand All @@ -48,7 +52,39 @@ pub fn replace_subschema<T: JsonSchema>(
generator
.definitions_mut()
.insert(schema_name.to_string(), schema.to_value());
Schema::new_ref(format!("{DEFINITIONS_PATH}{schema_name}"))
Schema::new_ref(format!("{DEFS_PATH}{schema_name}"))
}

/// Adds a new JSON schema definition and returns a reference to it. **Panics** if the name is
/// already in use.
pub fn add_new_subschema(
generator: &mut schemars::SchemaGenerator,
name: &str,
schema: Value,
) -> Schema {
let old_definition = generator.definitions_mut().insert(name.to_string(), schema);
assert_eq!(old_definition, None);
schemars::Schema::new_ref(format!("{DEFS_PATH}{name}"))
}

/// Defaults `additionalProperties` to `true`, as if `#[schemars(deny_unknown_fields)]` was on every
/// struct. Skips structs that have `additionalProperties` set (such as if #[serde(flatten)] is used
/// on a map).
#[derive(Clone)]
pub struct DefaultDenyUnknownFields;

impl schemars::transform::Transform for DefaultDenyUnknownFields {
fn transform(&mut self, schema: &mut schemars::Schema) {
if let Some(object) = schema.as_object_mut() {
if object.contains_key("properties")
&& !object.contains_key("additionalProperties")
&& !object.contains_key("unevaluatedProperties")
{
object.insert("additionalProperties".to_string(), false.into());
}
}
transform_subschemas(self, schema);
}
}

pub fn update_value_in_json_text<'a>(
Expand Down
59 changes: 38 additions & 21 deletions crates/settings/src/settings_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ use util::{ResultExt as _, merge_non_null_json_value_into};
pub type EditorconfigProperties = ec4rs::Properties;

use crate::{
ParameterizedJsonSchema, SettingsJsonSchemaParams, VsCodeSettings, WorktreeId,
parse_json_with_comments, update_value_in_json_text,
DefaultDenyUnknownFields, ParameterizedJsonSchema, SettingsJsonSchemaParams, VsCodeSettings,
WorktreeId, add_new_subschema, parse_json_with_comments, update_value_in_json_text,
};

/// A value that can be defined as a user setting.
Expand Down Expand Up @@ -864,7 +864,9 @@ impl SettingsStore {
}

pub fn json_schema(&self, schema_params: &SettingsJsonSchemaParams, cx: &App) -> Value {
let mut generator = schemars::generate::SchemaSettings::draft07().into_generator();
let mut generator = schemars::generate::SchemaSettings::draft2019_09()
.with_transform(DefaultDenyUnknownFields)
.into_generator();
let mut combined_schema = json!({
"type": "object",
"properties": {}
Expand Down Expand Up @@ -988,42 +990,61 @@ impl SettingsStore {
}
}

// add schemas which are determined at runtime
for parameterized_json_schema in inventory::iter::<ParameterizedJsonSchema>() {
(parameterized_json_schema.add_and_get_ref)(&mut generator, schema_params, cx);
}

// add merged settings schema to the definitions
const ZED_SETTINGS: &str = "ZedSettings";
let old_zed_settings_definition = generator
.definitions_mut()
.insert(ZED_SETTINGS.to_string(), combined_schema);
assert_eq!(old_zed_settings_definition, None);
let zed_settings_ref = schemars::Schema::new_ref(format!("#/definitions/{ZED_SETTINGS}"));
let zed_settings_ref = add_new_subschema(&mut generator, ZED_SETTINGS, combined_schema);

// add `ZedReleaseStageSettings` which is the same as `ZedSettings` except that unknown
// fields are rejected.
let mut zed_release_stage_settings = zed_settings_ref.clone();
zed_release_stage_settings.insert("unevaluatedProperties".to_string(), false.into());
let zed_release_stage_settings_ref = add_new_subschema(
&mut generator,
"ZedReleaseStageSettings",
zed_release_stage_settings.to_value(),
);

// Remove `"additionalProperties": false` added by `DefaultDenyUnknownFields` so that
// unknown fields can be handled by the root schema and `ZedReleaseStageSettings`.
let mut definitions = generator.take_definitions(true);
definitions
.get_mut(ZED_SETTINGS)
.unwrap()
.as_object_mut()
.unwrap()
.remove("additionalProperties");

let mut root_schema = if let Some(meta_schema) = generator.settings().meta_schema.as_ref() {
json_schema!({ "$schema": meta_schema.to_string() })
} else {
json_schema!({})
};

// settings file contents matches ZedSettings + overrides for each release stage
// "unevaluatedProperties: false" to report unknown fields.
root_schema.insert("unevaluatedProperties".to_string(), false.into());

// Settings file contents matches ZedSettings + overrides for each release stage.
root_schema.insert(
"allOf".to_string(),
json!([
zed_settings_ref,
{
"properties": {
"dev": zed_settings_ref,
"nightly": zed_settings_ref,
"stable": zed_settings_ref,
"preview": zed_settings_ref,
"dev": zed_release_stage_settings_ref,
"nightly": zed_release_stage_settings_ref,
"stable": zed_release_stage_settings_ref,
"preview": zed_release_stage_settings_ref,
}
}
]),
);
root_schema.insert(
"definitions".to_string(),
generator.take_definitions(true).into(),
);

root_schema.insert("$defs".to_string(), definitions.into());

root_schema.to_value()
}
Expand Down Expand Up @@ -1934,7 +1955,6 @@ mod tests {
}

#[derive(Default, Clone, Serialize, Deserialize, JsonSchema)]
#[schemars(deny_unknown_fields)]
struct UserSettingsContent {
name: Option<String>,
age: Option<u32>,
Expand Down Expand Up @@ -1977,7 +1997,6 @@ mod tests {
}

#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
#[schemars(deny_unknown_fields)]
struct MultiKeySettingsJson {
key1: Option<String>,
key2: Option<String>,
Expand Down Expand Up @@ -2016,7 +2035,6 @@ mod tests {
}

#[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema)]
#[schemars(deny_unknown_fields)]
struct JournalSettingsJson {
pub path: Option<String>,
pub hour_format: Option<HourFormat>,
Expand Down Expand Up @@ -2111,7 +2129,6 @@ 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>,
Expand Down
2 changes: 1 addition & 1 deletion crates/snippet_provider/src/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub struct VsSnippetsFile {

impl VsSnippetsFile {
pub fn generate_json_schema() -> Value {
let schema = schemars::generate::SchemaSettings::draft07()
let schema = schemars::generate::SchemaSettings::draft2019_09()
.into_generator()
.root_schema_for::<Self>();

Expand Down
2 changes: 1 addition & 1 deletion crates/task/src/debug_format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ pub struct DebugTaskFile(pub Vec<DebugScenario>);

impl DebugTaskFile {
pub fn generate_json_schema(schemas: &AdapterSchemas) -> serde_json_lenient::Value {
let mut generator = schemars::generate::SchemaSettings::draft07().into_generator();
let mut generator = schemars::generate::SchemaSettings::draft2019_09().into_generator();
let build_task_schema = generator.root_schema_for::<BuildTaskDefinition>();
let mut build_task_value =
serde_json_lenient::to_value(&build_task_schema).unwrap_or_default();
Expand Down
2 changes: 1 addition & 1 deletion crates/task/src/task_template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ pub struct TaskTemplates(pub Vec<TaskTemplate>);
impl TaskTemplates {
/// Generates JSON schema of Tasks JSON template format.
pub fn generate_json_schema() -> serde_json_lenient::Value {
let schema = schemars::generate::SchemaSettings::draft07()
let schema = schemars::generate::SchemaSettings::draft2019_09()
.into_generator()
.root_schema_for::<Self>();

Expand Down