Default values for schemas

This commit is contained in:
Piotr Siuszko 2025-06-20 14:31:16 +02:00
parent 2b20e8dae8
commit 8e4809f123
2 changed files with 64 additions and 89 deletions

View File

@ -25,7 +25,7 @@ use serde_json::{Map, Value};
use crate::{ use crate::{
error_codes, error_codes,
schemas::{ schemas::{
json_schema::{export_type, JsonSchemaBevyType}, json_schema::{JsonSchemaBevyType, TypeRegistrySchemaReader},
open_rpc::OpenRpcDocument, open_rpc::OpenRpcDocument,
}, },
BrpError, BrpResult, BrpError, BrpResult,
@ -1245,7 +1245,8 @@ pub fn export_registry_types(In(params): In<Option<Value>>, world: &World) -> Br
return None; return None;
} }
} }
let (id, schema) = export_type(type_reg, extra_info); let id = type_reg.type_id();
let schema = types.export_type_json_schema_for_id(id, extra_info)?;
if !filter.type_limit.with.is_empty() if !filter.type_limit.with.is_empty()
&& !filter && !filter
@ -1265,7 +1266,7 @@ pub fn export_registry_types(In(params): In<Option<Value>>, world: &World) -> Br
{ {
return None; return None;
} }
Some((id.to_string(), schema)) Some((type_reg.type_info().type_path().into(), schema))
}) })
.collect::<HashMap<String, JsonSchemaBevyType>>(); .collect::<HashMap<String, JsonSchemaBevyType>>();

View File

@ -1,8 +1,10 @@
//! Module with JSON Schema type for Bevy Registry Types. //! Module with JSON Schema type for Bevy Registry Types.
//! It tries to follow this standard: <https://json-schema.org/specification> //! It tries to follow this standard: <https://json-schema.org/specification>
use alloc::borrow::Cow;
use bevy_platform::collections::HashMap; use bevy_platform::collections::HashMap;
use bevy_reflect::{GetTypeRegistration, Reflect, TypeRegistration, TypeRegistry}; use bevy_reflect::{
prelude::ReflectDefault, serde::ReflectSerializer, GetTypeRegistration, Reflect,
TypeRegistration, TypeRegistry,
};
use core::any::TypeId; use core::any::TypeId;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value; use serde_json::Value;
@ -19,33 +21,48 @@ pub trait TypeRegistrySchemaReader {
&self, &self,
extra_info: &SchemaTypesMetadata, extra_info: &SchemaTypesMetadata,
) -> Option<JsonSchemaBevyType> { ) -> Option<JsonSchemaBevyType> {
self.export_type_json_schema_for_id(extra_info, TypeId::of::<T>()) self.export_type_json_schema_for_id(TypeId::of::<T>(), extra_info)
} }
/// Export type JSON Schema. /// Export type JSON Schema.
fn export_type_json_schema_for_id( fn export_type_json_schema_for_id(
&self, &self,
extra_info: &SchemaTypesMetadata,
type_id: TypeId, type_id: TypeId,
extra_info: &SchemaTypesMetadata,
) -> Option<JsonSchemaBevyType>; ) -> Option<JsonSchemaBevyType>;
/// Try to get default value for type id.
fn try_get_default_value_for_type_id(&self, type_id: TypeId) -> Option<Value>;
} }
impl TypeRegistrySchemaReader for TypeRegistry { impl TypeRegistrySchemaReader for TypeRegistry {
fn export_type_json_schema_for_id( fn export_type_json_schema_for_id(
&self, &self,
extra_info: &SchemaTypesMetadata,
type_id: TypeId, type_id: TypeId,
extra_info: &SchemaTypesMetadata,
) -> Option<JsonSchemaBevyType> { ) -> Option<JsonSchemaBevyType> {
let type_reg = self.get(type_id)?; let type_reg = self.get(type_id)?;
Some((type_reg, extra_info).into()) let mut schema: JsonSchemaBevyType = (type_reg, extra_info).into();
} schema.default_value = self.try_get_default_value_for_type_id(type_id);
}
/// Exports schema info for a given type Some(schema)
pub fn export_type( }
reg: &TypeRegistration,
metadata: &SchemaTypesMetadata, fn try_get_default_value_for_type_id(&self, type_id: TypeId) -> Option<Value> {
) -> (Cow<'static, str>, JsonSchemaBevyType) { let type_reg = self.get(type_id)?;
(reg.type_info().type_path().into(), (reg, metadata).into()) let default_data = type_reg.data::<ReflectDefault>()?;
let default = default_data.default();
let serializer = ReflectSerializer::new(&*default, self);
let value_object = serde_json::to_value(serializer)
.ok()
.and_then(|v| v.as_object().cloned())?;
if value_object.len() == 1 {
if let Some((_, value)) = value_object.into_iter().next() {
return Some(value);
}
}
None
}
} }
impl From<(&TypeRegistration, &SchemaTypesMetadata)> for JsonSchemaBevyType { impl From<(&TypeRegistration, &SchemaTypesMetadata)> for JsonSchemaBevyType {
@ -58,7 +75,6 @@ impl From<(&TypeRegistration, &SchemaTypesMetadata)> for JsonSchemaBevyType {
return JsonSchemaBevyType::default(); return JsonSchemaBevyType::default();
}; };
typed_schema.reflect_types = metadata.get_registered_reflect_types(reg); typed_schema.reflect_types = metadata.get_registered_reflect_types(reg);
*typed_schema *typed_schema
} }
} }
@ -175,6 +191,9 @@ pub struct JsonSchemaBevyType {
/// Type description /// Type description
#[serde(skip_serializing_if = "Option::is_none", default)] #[serde(skip_serializing_if = "Option::is_none", default)]
pub description: Option<String>, pub description: Option<String>,
/// Default value for the schema.
#[serde(skip_serializing_if = "Option::is_none", default, rename = "default")]
pub default_value: Option<Value>,
} }
/// Represents different types of JSON Schema values that can be used in schema definitions. /// Represents different types of JSON Schema values that can be used in schema definitions.
@ -361,6 +380,21 @@ mod tests {
use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize}; use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize};
use serde_json::json; use serde_json::json;
fn export_type<T: GetTypeRegistration + 'static>() -> JsonSchemaBevyType {
let atr = AppTypeRegistry::default();
{
let mut register = atr.write();
register.register::<T>();
}
let type_registry = atr.read();
let Some(schema) =
type_registry.export_type_json_schema::<T>(&SchemaTypesMetadata::default())
else {
panic!("Failed to export JSON schema for Foo");
};
schema
}
#[test] #[test]
fn reflect_export_struct() { fn reflect_export_struct() {
#[derive(Reflect, Resource, Default, Deserialize, Serialize)] #[derive(Reflect, Resource, Default, Deserialize, Serialize)]
@ -370,26 +404,7 @@ mod tests {
#[reflect(@10..=15i16)] #[reflect(@10..=15i16)]
b: Option<i16>, b: Option<i16>,
} }
let schema = export_type::<Foo>();
let atr = AppTypeRegistry::default();
{
let mut register = atr.write();
register.register::<Foo>();
}
let type_registry = atr.read();
let foo_registration = type_registry
.get(TypeId::of::<Foo>())
.expect("SHOULD BE REGISTERED")
.clone();
// for field in foo_registration
// .type_info()
// .as_struct()
// .expect("msg")
// .iter()
// {
// eprintln!("{}: {:#?}", field.name(), field.build_schema_type_info());
// }
let (_, schema) = export_type(&foo_registration, &SchemaTypesMetadata::default());
eprintln!("{}", serde_json::to_string_pretty(&schema).expect("msg")); eprintln!("{}", serde_json::to_string_pretty(&schema).expect("msg"));
assert!( assert!(
@ -421,18 +436,7 @@ mod tests {
#[default] #[default]
NoValue, NoValue,
} }
let schema = export_type::<EnumComponent>();
let atr = AppTypeRegistry::default();
{
let mut register = atr.write();
register.register::<EnumComponent>();
}
let type_registry = atr.read();
let foo_registration = type_registry
.get(TypeId::of::<EnumComponent>())
.expect("SHOULD BE REGISTERED")
.clone();
let (_, schema) = export_type(&foo_registration, &SchemaTypesMetadata::default());
eprintln!("{}", serde_json::to_string_pretty(&schema).expect("msg")); eprintln!("{}", serde_json::to_string_pretty(&schema).expect("msg"));
assert!( assert!(
schema.reflect_types.contains(&"Component".to_owned()), schema.reflect_types.contains(&"Component".to_owned()),
@ -457,18 +461,7 @@ mod tests {
#[default] #[default]
NoValue, NoValue,
} }
let schema = export_type::<EnumComponent>();
let atr = AppTypeRegistry::default();
{
let mut register = atr.write();
register.register::<EnumComponent>();
}
let type_registry = atr.read();
let foo_registration = type_registry
.get(TypeId::of::<EnumComponent>())
.expect("SHOULD BE REGISTERED")
.clone();
let (_, schema) = export_type(&foo_registration, &SchemaTypesMetadata::default());
assert!( assert!(
!schema.reflect_types.contains(&"Component".to_owned()), !schema.reflect_types.contains(&"Component".to_owned()),
"Should not be a component" "Should not be a component"
@ -512,11 +505,9 @@ mod tests {
let mut metadata = SchemaTypesMetadata::default(); let mut metadata = SchemaTypesMetadata::default();
metadata.map_type_data::<ReflectCustomData>("CustomData"); metadata.map_type_data::<ReflectCustomData>("CustomData");
let type_registry = atr.read(); let type_registry = atr.read();
let foo_registration = type_registry let schema = type_registry
.get(TypeId::of::<EnumComponent>()) .export_type_json_schema::<EnumComponent>(&metadata)
.expect("SHOULD BE REGISTERED") .expect("Failed to export");
.clone();
let (_, schema) = export_type(&foo_registration, &metadata);
assert!( assert!(
!metadata.has_type_data::<ReflectComponent>(&schema.reflect_types), !metadata.has_type_data::<ReflectComponent>(&schema.reflect_types),
"Should not be a component" "Should not be a component"
@ -543,17 +534,7 @@ mod tests {
#[reflect(Component, Default, Serialize, Deserialize)] #[reflect(Component, Default, Serialize, Deserialize)]
struct TupleStructType(usize, i32); struct TupleStructType(usize, i32);
let atr = AppTypeRegistry::default(); let schema = export_type::<TupleStructType>();
{
let mut register = atr.write();
register.register::<TupleStructType>();
}
let type_registry = atr.read();
let foo_registration = type_registry
.get(TypeId::of::<TupleStructType>())
.expect("SHOULD BE REGISTERED")
.clone();
let (_, schema) = export_type(&foo_registration, &SchemaTypesMetadata::default());
assert!( assert!(
schema.reflect_types.contains(&"Component".to_owned()), schema.reflect_types.contains(&"Component".to_owned()),
"Should be a component" "Should be a component"
@ -575,17 +556,7 @@ mod tests {
a: u16, a: u16,
} }
let atr = AppTypeRegistry::default(); let schema = export_type::<Foo>();
{
let mut register = atr.write();
register.register::<Foo>();
}
let type_registry = atr.read();
let foo_registration = type_registry
.get(TypeId::of::<Foo>())
.expect("SHOULD BE REGISTERED")
.clone();
let (_, schema) = export_type(&foo_registration, &SchemaTypesMetadata::default());
let schema_as_value = serde_json::to_value(&schema).expect("Should serialize"); let schema_as_value = serde_json::to_value(&schema).expect("Should serialize");
eprintln!("{:#?}", &schema_as_value); eprintln!("{:#?}", &schema_as_value);
let value = json!({ let value = json!({
@ -597,6 +568,9 @@ mod tests {
"Resource", "Resource",
"Default", "Default",
], ],
"default": {
"a": 0
},
"kind": "Struct", "kind": "Struct",
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,