Default `#[schemars(deny_unknown_fields)] for json-language-server schemas (#33883)
Followup to #33678, doing the same thing for all JSON Schema files provided to json-language-server Release Notes: * Added warnings for unknown fields when editing `tasks.json` / `snippets.json`.
This commit is contained in:
parent
38544e514a
commit
ed7552d3e3
14 changed files with 136 additions and 149 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -17348,6 +17348,7 @@ dependencies = [
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"regex",
|
"regex",
|
||||||
"rust-embed",
|
"rust-embed",
|
||||||
|
"schemars",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_json_lenient",
|
"serde_json_lenient",
|
||||||
|
|
|
@ -18,10 +18,10 @@ use serde::{
|
||||||
|
|
||||||
use settings::{
|
use settings::{
|
||||||
ParameterizedJsonSchema, Settings, SettingsLocation, SettingsSources, SettingsStore,
|
ParameterizedJsonSchema, Settings, SettingsLocation, SettingsSources, SettingsStore,
|
||||||
replace_subschema,
|
|
||||||
};
|
};
|
||||||
use shellexpand;
|
use shellexpand;
|
||||||
use std::{borrow::Cow, num::NonZeroU32, path::Path, slice, sync::Arc};
|
use std::{borrow::Cow, num::NonZeroU32, path::Path, slice, sync::Arc};
|
||||||
|
use util::schemars::replace_subschema;
|
||||||
use util::serde::default_true;
|
use util::serde::default_true;
|
||||||
|
|
||||||
/// Initializes the language settings.
|
/// Initializes the language settings.
|
||||||
|
|
|
@ -272,6 +272,7 @@ impl JsonLspAdapter {
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
fn generate_inspector_style_schema() -> serde_json_lenient::Value {
|
fn generate_inspector_style_schema() -> serde_json_lenient::Value {
|
||||||
let schema = schemars::generate::SchemaSettings::draft2019_09()
|
let schema = schemars::generate::SchemaSettings::draft2019_09()
|
||||||
|
.with_transform(util::schemars::DefaultDenyUnknownFields)
|
||||||
.into_generator()
|
.into_generator()
|
||||||
.root_schema_for::<gpui::StyleRefinement>();
|
.root_schema_for::<gpui::StyleRefinement>();
|
||||||
|
|
||||||
|
|
|
@ -427,6 +427,10 @@ impl KeymapFile {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_json_schema_for_registered_actions(cx: &mut App) -> Value {
|
pub fn generate_json_schema_for_registered_actions(cx: &mut App) -> Value {
|
||||||
|
// instead of using DefaultDenyUnknownFields, actions typically use
|
||||||
|
// `#[serde(deny_unknown_fields)]` so that these cases are reported as parse failures. This
|
||||||
|
// is because the rest of the keymap will still load in these cases, whereas other settings
|
||||||
|
// files would not.
|
||||||
let mut generator = schemars::generate::SchemaSettings::draft2019_09().into_generator();
|
let mut generator = schemars::generate::SchemaSettings::draft2019_09().into_generator();
|
||||||
|
|
||||||
let action_schemas = cx.action_schemas(&mut generator);
|
let action_schemas = cx.action_schemas(&mut generator);
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use gpui::App;
|
use gpui::App;
|
||||||
use schemars::{JsonSchema, Schema, transform::transform_subschemas};
|
|
||||||
use serde::{Serialize, de::DeserializeOwned};
|
use serde::{Serialize, de::DeserializeOwned};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::{ops::Range, sync::LazyLock};
|
use std::{ops::Range, sync::LazyLock};
|
||||||
|
@ -21,63 +20,6 @@ pub struct ParameterizedJsonSchema {
|
||||||
|
|
||||||
inventory::collect!(ParameterizedJsonSchema);
|
inventory::collect!(ParameterizedJsonSchema);
|
||||||
|
|
||||||
const DEFS_PATH: &str = "#/$defs/";
|
|
||||||
|
|
||||||
/// Replaces the JSON schema definition for some type if it is in use (in the definitions list), and
|
|
||||||
/// returns a reference to it.
|
|
||||||
///
|
|
||||||
/// This asserts that JsonSchema::schema_name() + "2" does not exist because this indicates that
|
|
||||||
/// there are multiple types that use this name, and unfortunately schemars APIs do not support
|
|
||||||
/// resolving this ambiguity - see https://github.com/GREsau/schemars/issues/449
|
|
||||||
///
|
|
||||||
/// This takes a closure for `schema` because some settings types are not available on the remote
|
|
||||||
/// server, and so will crash when attempting to access e.g. GlobalThemeRegistry.
|
|
||||||
pub fn replace_subschema<T: JsonSchema>(
|
|
||||||
generator: &mut schemars::SchemaGenerator,
|
|
||||||
schema: impl Fn() -> schemars::Schema,
|
|
||||||
) -> schemars::Schema {
|
|
||||||
// fallback on just using the schema name, which could collide.
|
|
||||||
let schema_name = T::schema_name();
|
|
||||||
let definitions = generator.definitions_mut();
|
|
||||||
assert!(!definitions.contains_key(&format!("{schema_name}2")));
|
|
||||||
if definitions.contains_key(schema_name.as_ref()) {
|
|
||||||
definitions.insert(schema_name.to_string(), schema().to_value());
|
|
||||||
}
|
|
||||||
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>(
|
pub fn update_value_in_json_text<'a>(
|
||||||
text: &mut String,
|
text: &mut String,
|
||||||
key_path: &mut Vec<&'a str>,
|
key_path: &mut Vec<&'a str>,
|
||||||
|
|
|
@ -6,7 +6,7 @@ use futures::{FutureExt, StreamExt, channel::mpsc, future::LocalBoxFuture};
|
||||||
use gpui::{App, AsyncApp, BorrowAppContext, Global, Task, UpdateGlobal};
|
use gpui::{App, AsyncApp, BorrowAppContext, Global, Task, UpdateGlobal};
|
||||||
|
|
||||||
use paths::{EDITORCONFIG_NAME, local_settings_file_relative_path, task_file_name};
|
use paths::{EDITORCONFIG_NAME, local_settings_file_relative_path, task_file_name};
|
||||||
use schemars::{JsonSchema, json_schema};
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize, de::DeserializeOwned};
|
use serde::{Deserialize, Serialize, de::DeserializeOwned};
|
||||||
use serde_json::{Value, json};
|
use serde_json::{Value, json};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
@ -18,14 +18,16 @@ use std::{
|
||||||
str::{self, FromStr},
|
str::{self, FromStr},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
use util::{
|
||||||
use util::{ResultExt as _, merge_non_null_json_value_into};
|
ResultExt as _, merge_non_null_json_value_into,
|
||||||
|
schemars::{DefaultDenyUnknownFields, add_new_subschema},
|
||||||
|
};
|
||||||
|
|
||||||
pub type EditorconfigProperties = ec4rs::Properties;
|
pub type EditorconfigProperties = ec4rs::Properties;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
DefaultDenyUnknownFields, ParameterizedJsonSchema, SettingsJsonSchemaParams, VsCodeSettings,
|
ParameterizedJsonSchema, SettingsJsonSchemaParams, VsCodeSettings, WorktreeId,
|
||||||
WorktreeId, add_new_subschema, parse_json_with_comments, update_value_in_json_text,
|
parse_json_with_comments, update_value_in_json_text,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A value that can be defined as a user setting.
|
/// A value that can be defined as a user setting.
|
||||||
|
@ -1019,19 +1021,19 @@ impl SettingsStore {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.remove("additionalProperties");
|
.remove("additionalProperties");
|
||||||
|
|
||||||
let mut root_schema = if let Some(meta_schema) = generator.settings().meta_schema.as_ref() {
|
let meta_schema = generator
|
||||||
json_schema!({ "$schema": meta_schema.to_string() })
|
.settings()
|
||||||
} else {
|
.meta_schema
|
||||||
json_schema!({})
|
.as_ref()
|
||||||
};
|
.expect("meta_schema should be present in schemars settings")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
// "unevaluatedProperties: false" to report unknown fields.
|
json!({
|
||||||
root_schema.insert("unevaluatedProperties".to_string(), false.into());
|
"$schema": meta_schema,
|
||||||
|
"title": "Zed Settings",
|
||||||
// Settings file contents matches ZedSettings + overrides for each release stage.
|
"unevaluatedProperties": false,
|
||||||
root_schema.insert(
|
// ZedSettings + settings overrides for each release stage
|
||||||
"allOf".to_string(),
|
"allOf": [
|
||||||
json!([
|
|
||||||
zed_settings_ref,
|
zed_settings_ref,
|
||||||
{
|
{
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -1041,12 +1043,9 @@ impl SettingsStore {
|
||||||
"preview": zed_release_stage_settings_ref,
|
"preview": zed_release_stage_settings_ref,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]),
|
],
|
||||||
);
|
"$defs": definitions,
|
||||||
|
})
|
||||||
root_schema.insert("$defs".to_string(), definitions.into());
|
|
||||||
|
|
||||||
root_schema.to_value()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn recompute_values(
|
fn recompute_values(
|
||||||
|
|
|
@ -3,6 +3,7 @@ use schemars::{JsonSchema, json_schema};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde_json_lenient::Value;
|
use serde_json_lenient::Value;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use util::schemars::DefaultDenyUnknownFields;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct VsSnippetsFile {
|
pub struct VsSnippetsFile {
|
||||||
|
@ -13,6 +14,7 @@ pub struct VsSnippetsFile {
|
||||||
impl VsSnippetsFile {
|
impl VsSnippetsFile {
|
||||||
pub fn generate_json_schema() -> Value {
|
pub fn generate_json_schema() -> Value {
|
||||||
let schema = schemars::generate::SchemaSettings::draft2019_09()
|
let schema = schemars::generate::SchemaSettings::draft2019_09()
|
||||||
|
.with_transform(DefaultDenyUnknownFields)
|
||||||
.into_generator()
|
.into_generator()
|
||||||
.root_schema_for::<Self>();
|
.root_schema_for::<Self>();
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
use anyhow::Result;
|
|
||||||
use gpui::SharedString;
|
use gpui::SharedString;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::json;
|
|
||||||
|
|
||||||
/// Represents a schema for a specific adapter
|
/// JSON schema for a specific adapter
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||||
pub struct AdapterSchema {
|
pub struct AdapterSchema {
|
||||||
/// The adapter name identifier
|
/// The adapter name identifier
|
||||||
|
@ -16,47 +14,3 @@ pub struct AdapterSchema {
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
pub struct AdapterSchemas(pub Vec<AdapterSchema>);
|
pub struct AdapterSchemas(pub Vec<AdapterSchema>);
|
||||||
|
|
||||||
impl AdapterSchemas {
|
|
||||||
pub fn generate_json_schema(&self) -> Result<serde_json_lenient::Value> {
|
|
||||||
let adapter_conditions = self
|
|
||||||
.0
|
|
||||||
.iter()
|
|
||||||
.map(|adapter_schema| {
|
|
||||||
let adapter_name = adapter_schema.adapter.to_string();
|
|
||||||
json!({
|
|
||||||
"if": {
|
|
||||||
"properties": {
|
|
||||||
"adapter": { "const": adapter_name }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"then": adapter_schema.schema
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let schema = serde_json_lenient::json!({
|
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
||||||
"title": "Debug Adapter Configurations",
|
|
||||||
"description": "Configuration for debug adapters. Schema changes based on the selected adapter.",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "object",
|
|
||||||
"required": ["adapter", "label"],
|
|
||||||
"properties": {
|
|
||||||
"adapter": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "The name of the debug adapter"
|
|
||||||
},
|
|
||||||
"label": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "The name of the debug configuration"
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"allOf": adapter_conditions
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(serde_json_lenient::to_value(schema)?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::net::Ipv4Addr;
|
use std::net::Ipv4Addr;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use util::debug_panic;
|
use util::{debug_panic, schemars::add_new_subschema};
|
||||||
|
|
||||||
use crate::{TaskTemplate, adapter_schema::AdapterSchemas};
|
use crate::{TaskTemplate, adapter_schema::AdapterSchemas};
|
||||||
|
|
||||||
|
@ -286,11 +286,10 @@ pub struct DebugScenario {
|
||||||
pub struct DebugTaskFile(pub Vec<DebugScenario>);
|
pub struct DebugTaskFile(pub Vec<DebugScenario>);
|
||||||
|
|
||||||
impl DebugTaskFile {
|
impl DebugTaskFile {
|
||||||
pub fn generate_json_schema(schemas: &AdapterSchemas) -> serde_json_lenient::Value {
|
pub fn generate_json_schema(schemas: &AdapterSchemas) -> serde_json::Value {
|
||||||
let mut generator = schemars::generate::SchemaSettings::draft2019_09().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 =
|
let mut build_task_value = BuildTaskDefinition::json_schema(&mut generator).to_value();
|
||||||
serde_json_lenient::to_value(&build_task_schema).unwrap_or_default();
|
|
||||||
|
|
||||||
if let Some(template_object) = build_task_value
|
if let Some(template_object) = build_task_value
|
||||||
.get_mut("anyOf")
|
.get_mut("anyOf")
|
||||||
|
@ -322,32 +321,54 @@ impl DebugTaskFile {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let task_definitions = build_task_value.get("$defs").cloned().unwrap_or_default();
|
|
||||||
|
|
||||||
let adapter_conditions = schemas
|
let adapter_conditions = schemas
|
||||||
.0
|
.0
|
||||||
.iter()
|
.iter()
|
||||||
.map(|adapter_schema| {
|
.map(|adapter_schema| {
|
||||||
let adapter_name = adapter_schema.adapter.to_string();
|
let adapter_name = adapter_schema.adapter.to_string();
|
||||||
serde_json::json!({
|
add_new_subschema(
|
||||||
"if": {
|
&mut generator,
|
||||||
"properties": {
|
&format!("{adapter_name}DebugSettings"),
|
||||||
"adapter": { "const": adapter_name }
|
serde_json::json!({
|
||||||
}
|
"if": {
|
||||||
},
|
"properties": {
|
||||||
"then": adapter_schema.schema
|
"adapter": { "const": adapter_name }
|
||||||
})
|
}
|
||||||
|
},
|
||||||
|
"then": adapter_schema.schema
|
||||||
|
}),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
serde_json_lenient::json!({
|
let build_task_definition_ref = add_new_subschema(
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
&mut generator,
|
||||||
|
BuildTaskDefinition::schema_name().as_ref(),
|
||||||
|
build_task_value,
|
||||||
|
);
|
||||||
|
|
||||||
|
let meta_schema = generator
|
||||||
|
.settings()
|
||||||
|
.meta_schema
|
||||||
|
.as_ref()
|
||||||
|
.expect("meta_schema should be present in schemars settings")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
serde_json::json!({
|
||||||
|
"$schema": meta_schema,
|
||||||
"title": "Debug Configurations",
|
"title": "Debug Configurations",
|
||||||
"description": "Configuration for debug scenarios",
|
"description": "Configuration for debug scenarios",
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["adapter", "label"],
|
"required": ["adapter", "label"],
|
||||||
|
// TODO: Uncommenting this will cause json-language-server to provide warnings for
|
||||||
|
// unrecognized properties. It should be enabled if/when there's an adapter JSON
|
||||||
|
// schema that's comprehensive. In order to not get warnings for the other schemas,
|
||||||
|
// `additionalProperties` or `unevaluatedProperties` (to handle "allOf" etc style
|
||||||
|
// schema combinations) could be set to `true` for that schema.
|
||||||
|
//
|
||||||
|
// "unevaluatedProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"adapter": {
|
"adapter": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
@ -357,7 +378,7 @@ impl DebugTaskFile {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "The name of the debug configuration"
|
"description": "The name of the debug configuration"
|
||||||
},
|
},
|
||||||
"build": build_task_value,
|
"build": build_task_definition_ref,
|
||||||
"tcp_connection": {
|
"tcp_connection": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "Optional TCP connection information for connecting to an already running debug adapter",
|
"description": "Optional TCP connection information for connecting to an already running debug adapter",
|
||||||
|
@ -380,7 +401,7 @@ impl DebugTaskFile {
|
||||||
},
|
},
|
||||||
"allOf": adapter_conditions
|
"allOf": adapter_conditions
|
||||||
},
|
},
|
||||||
"$defs": task_definitions
|
"$defs": generator.take_definitions(true),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use util::schemars::DefaultDenyUnknownFields;
|
||||||
use util::serde::default_true;
|
use util::serde::default_true;
|
||||||
use util::{ResultExt, truncate_and_remove_front};
|
use util::{ResultExt, truncate_and_remove_front};
|
||||||
|
|
||||||
|
@ -116,6 +117,7 @@ impl TaskTemplates {
|
||||||
/// Generates JSON schema of Tasks JSON template format.
|
/// Generates JSON schema of Tasks JSON template format.
|
||||||
pub fn generate_json_schema() -> serde_json_lenient::Value {
|
pub fn generate_json_schema() -> serde_json_lenient::Value {
|
||||||
let schema = schemars::generate::SchemaSettings::draft2019_09()
|
let schema = schemars::generate::SchemaSettings::draft2019_09()
|
||||||
|
.with_transform(DefaultDenyUnknownFields)
|
||||||
.into_generator()
|
.into_generator()
|
||||||
.root_schema_for::<Self>();
|
.root_schema_for::<Self>();
|
||||||
|
|
||||||
|
|
|
@ -12,9 +12,10 @@ use gpui::{
|
||||||
use refineable::Refineable;
|
use refineable::Refineable;
|
||||||
use schemars::{JsonSchema, json_schema};
|
use schemars::{JsonSchema, json_schema};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use settings::{ParameterizedJsonSchema, Settings, SettingsSources, replace_subschema};
|
use settings::{ParameterizedJsonSchema, Settings, SettingsSources};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use util::ResultExt as _;
|
use util::ResultExt as _;
|
||||||
|
use util::schemars::replace_subschema;
|
||||||
|
|
||||||
const MIN_FONT_SIZE: Pixels = px(6.0);
|
const MIN_FONT_SIZE: Pixels = px(6.0);
|
||||||
const MIN_LINE_HEIGHT: f32 = 1.0;
|
const MIN_LINE_HEIGHT: f32 = 1.0;
|
||||||
|
|
|
@ -30,6 +30,7 @@ log.workspace = true
|
||||||
rand = { workspace = true, optional = true }
|
rand = { workspace = true, optional = true }
|
||||||
regex.workspace = true
|
regex.workspace = true
|
||||||
rust-embed.workspace = true
|
rust-embed.workspace = true
|
||||||
|
schemars.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
serde_json_lenient.workspace = true
|
serde_json_lenient.workspace = true
|
||||||
|
|
58
crates/util/src/schemars.rs
Normal file
58
crates/util/src/schemars.rs
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
use schemars::{JsonSchema, transform::transform_subschemas};
|
||||||
|
|
||||||
|
const DEFS_PATH: &str = "#/$defs/";
|
||||||
|
|
||||||
|
/// Replaces the JSON schema definition for some type if it is in use (in the definitions list), and
|
||||||
|
/// returns a reference to it.
|
||||||
|
///
|
||||||
|
/// This asserts that JsonSchema::schema_name() + "2" does not exist because this indicates that
|
||||||
|
/// there are multiple types that use this name, and unfortunately schemars APIs do not support
|
||||||
|
/// resolving this ambiguity - see https://github.com/GREsau/schemars/issues/449
|
||||||
|
///
|
||||||
|
/// This takes a closure for `schema` because some settings types are not available on the remote
|
||||||
|
/// server, and so will crash when attempting to access e.g. GlobalThemeRegistry.
|
||||||
|
pub fn replace_subschema<T: JsonSchema>(
|
||||||
|
generator: &mut schemars::SchemaGenerator,
|
||||||
|
schema: impl Fn() -> schemars::Schema,
|
||||||
|
) -> schemars::Schema {
|
||||||
|
// fallback on just using the schema name, which could collide.
|
||||||
|
let schema_name = T::schema_name();
|
||||||
|
let definitions = generator.definitions_mut();
|
||||||
|
assert!(!definitions.contains_key(&format!("{schema_name}2")));
|
||||||
|
if definitions.contains_key(schema_name.as_ref()) {
|
||||||
|
definitions.insert(schema_name.to_string(), schema().to_value());
|
||||||
|
}
|
||||||
|
schemars::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: serde_json::Value,
|
||||||
|
) -> schemars::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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ pub mod fs;
|
||||||
pub mod markdown;
|
pub mod markdown;
|
||||||
pub mod paths;
|
pub mod paths;
|
||||||
pub mod redact;
|
pub mod redact;
|
||||||
|
pub mod schemars;
|
||||||
pub mod serde;
|
pub mod serde;
|
||||||
pub mod shell_env;
|
pub mod shell_env;
|
||||||
pub mod size;
|
pub mod size;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue