Missing definitions handling

This commit is contained in:
Piotr Siuszko 2025-07-05 13:15:06 +02:00
parent 23ca435e73
commit 0ff3201c3c
2 changed files with 75 additions and 39 deletions

View File

@ -3,8 +3,7 @@
use alloc::borrow::Cow;
use bevy_platform::collections::HashMap;
use bevy_reflect::{
prelude::ReflectDefault, serde::ReflectSerializer, GetTypeRegistration, Reflect,
TypeRegistration, TypeRegistry,
prelude::ReflectDefault, serde::ReflectSerializer, GetTypeRegistration, Reflect, TypeRegistry,
};
use core::any::TypeId;
use serde::{Deserialize, Serialize};
@ -44,7 +43,26 @@ impl TypeRegistrySchemaReader for TypeRegistry {
extra_info: &SchemaTypesMetadata,
) -> Option<JsonSchemaBevyType> {
let type_reg = self.get(type_id)?;
let mut schema: JsonSchemaBevyType = (type_reg, extra_info).try_into().ok()?;
let mut definition = TypeInformation::from(type_reg)
.to_schema_type_info_with_metadata(extra_info)
.to_definition();
for missing in &definition.missing_definitions {
let reg_option = self.get(*missing);
if let Some(reg) = reg_option {
let missing_schema =
TypeInformation::from(reg).to_schema_type_info_with_metadata(extra_info);
let mis_def = missing_schema.to_definition();
definition.definitions.extend(mis_def.definitions);
if let Some(missing_id) = mis_def.id {
if !definition.definitions.contains_key(&missing_id) {
definition.definitions.insert(missing_id, missing_schema);
}
}
}
}
let mut schema: JsonSchemaBevyType = definition.into();
schema.reflect_type_data = extra_info.get_registered_reflect_types(type_reg);
schema.schema = Some(SchemaMarker.into());
schema.default_value = self.try_get_default_value_for_type_id(type_id);
@ -69,38 +87,6 @@ impl TypeRegistrySchemaReader for TypeRegistry {
}
}
/// Error type for invalid JSON Schema conversions.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InvalidJsonSchema {
/// The type cannot be converted to a valid JSON Schema.
InvalidType,
}
impl TryFrom<(&TypeRegistration, &SchemaTypesMetadata)> for JsonSchemaBevyType {
type Error = InvalidJsonSchema;
fn try_from(value: (&TypeRegistration, &SchemaTypesMetadata)) -> Result<Self, Self::Error> {
let (reg, metadata) = value;
// if let Some(s) = reg.data::<ReflectJsonSchema>() {
// return Ok(s.0.clone());
// }
let mut schema: JsonSchemaBevyType = TypeInformation::from(reg)
.to_schema_type_info_with_metadata(metadata)
.to_definition()
.into();
schema.reflect_type_data = metadata.get_registered_reflect_types(reg);
Ok(schema)
}
}
impl TryFrom<&TypeRegistration> for JsonSchemaBevyType {
type Error = InvalidJsonSchema;
fn try_from(value: &TypeRegistration) -> Result<Self, Self::Error> {
(value, &SchemaTypesMetadata::default()).try_into()
}
}
/// Identifies the JSON Schema version used in the schema.
#[derive(Deserialize, Serialize, Debug, Reflect, PartialEq, Clone)]
pub struct SchemaMarker;

View File

@ -1071,6 +1071,9 @@ pub struct SchemaDefinition {
pub schema: JsonSchemaBevyType,
/// The properties of the schema.
pub definitions: HashMap<TypeReferenceId, SchemaTypeInfo>,
/// Missing definitions of the schema.
/// Could be the case for the types that are stored as generic arguments.
pub missing_definitions: Vec<TypeId>,
}
impl From<SchemaDefinition> for JsonSchemaBevyType {
@ -1237,11 +1240,13 @@ impl SchemaTypeInfo {
pub fn to_definition(&self) -> SchemaDefinition {
let mut id: Option<TypeReferenceId> = self.ty_info.try_get_type_reference_id();
let mut definitions: HashMap<TypeReferenceId, SchemaTypeInfo> = HashMap::new();
let mut missing_definitions: Vec<TypeId> = Vec::new();
if let Some(custom_schema) = &self.ty_info.try_get_custom_schema() {
return SchemaDefinition {
id,
schema: custom_schema.0.clone(),
definitions,
missing_definitions,
};
}
let range = self.get_range();
@ -1298,7 +1303,9 @@ impl SchemaTypeInfo {
id,
schema: _,
definitions: field_definitions,
missing_definitions: key_missing_definitions,
} = key.to_definition();
missing_definitions.extend(key_missing_definitions);
if let Some(id) = id {
definitions.insert(id, key.clone());
definitions.extend(field_definitions);
@ -1310,7 +1317,10 @@ impl SchemaTypeInfo {
id,
schema: _,
definitions: field_definitions,
missing_definitions: value_missing_definitions,
} = value.to_definition();
missing_definitions.extend(value_missing_definitions);
if let Some(id) = id {
definitions.insert(id, value.clone());
definitions.extend(field_definitions);
@ -1405,6 +1415,7 @@ impl SchemaTypeInfo {
..Default::default()
},
definitions: HashMap::new(),
missing_definitions,
};
}
}
@ -1430,7 +1441,9 @@ impl SchemaTypeInfo {
id,
schema: _,
definitions: field_definitions,
missing_definitions: field_missing_definitions,
} = field_schema.to_definition();
missing_definitions.extend(field_missing_definitions);
definitions.extend(field_definitions);
let Some(id) = id else { continue };
if !definitions.contains_key(&id) {
@ -1450,7 +1463,9 @@ impl SchemaTypeInfo {
id,
schema: new_schema_type,
definitions: field_definitions,
missing_definitions: field_missing_definitions,
} = field_schema.to_definition();
missing_definitions.extend(field_missing_definitions);
definitions.extend(field_definitions);
if let Some(id) = id {
definitions.insert(id.clone(), field_schema);
@ -1519,8 +1534,10 @@ impl SchemaTypeInfo {
id,
schema: _,
definitions: field_definitions,
missing_definitions: field_missing_definitions,
} = field_schema.to_definition();
definitions.extend(field_definitions);
missing_definitions.extend(field_missing_definitions);
let Some(id) = id else { continue };
if !definitions.contains_key(&id) {
definitions.insert(id, field_schema);
@ -1546,7 +1563,9 @@ impl SchemaTypeInfo {
id,
schema: _,
definitions: field_definitions,
missing_definitions: field_missing_definitions,
} = items_schema.to_definition();
missing_definitions.extend(field_missing_definitions);
definitions.extend(field_definitions);
if let Some(id) = id {
definitions.insert(id, items_schema);
@ -1561,7 +1580,8 @@ impl SchemaTypeInfo {
ty_info: TypeInformation::Type(Box::new(*generic.ty())),
..(**schema_type_info).clone()
};
let definition = schema_optional.to_definition();
missing_definitions.push(generic.type_id());
let definition = schema_optional.clone().to_definition();
definitions.extend(definition.definitions);
schema.ref_type = None;
schema.schema_type = None;
@ -1570,7 +1590,7 @@ impl SchemaTypeInfo {
schema_type: Some(SchemaTypeVariant::Single(SchemaType::Null)),
..Default::default()
}),
Box::new(definition.schema),
Box::new(schema_optional.to_ref_schema()),
];
}
}
@ -1578,6 +1598,7 @@ impl SchemaTypeInfo {
id,
schema,
definitions,
missing_definitions,
}
}
}
@ -1813,10 +1834,12 @@ where
#[cfg(test)]
pub(super) mod tests {
use bevy_ecs::{component::Component, name::Name};
use bevy_ecs::{component::Component, name::Name, reflect::AppTypeRegistry};
use bevy_platform::collections::HashMap;
use bevy_reflect::GetTypeRegistration;
use crate::schemas::json_schema::TypeRegistrySchemaReader;
use super::*;
/// Validate a JSON schema against a set of valid and invalid instances.
@ -1962,7 +1985,7 @@ pub(super) mod tests {
serde_json::json!({"a": 5555,"b": 5555}),
],
);
let atr = bevy_ecs::reflect::AppTypeRegistry::default();
let atr = AppTypeRegistry::default();
{
let mut register = atr.write();
register.register::<bevy_math::Vec3>();
@ -2078,6 +2101,33 @@ pub(super) mod tests {
);
}
#[test]
fn optional_tests() {
#[derive(Reflect, Default, Deserialize, Serialize)]
pub struct ArrayComponent {
pub array: [u8; 3],
}
let atr = AppTypeRegistry::default();
{
let mut register = atr.write();
register.register::<ArrayComponent>();
register.register::<Option<ArrayComponent>>();
}
let type_registry = atr.read();
let schema = type_registry
.export_type_json_schema::<Option<ArrayComponent>>(&Default::default())
.expect("Failed to export type JSON schema");
validate::<Option<ArrayComponent>>(
schema,
&[None, Some(ArrayComponent { array: [5, 1, 9] })],
&[
serde_json::json!({"array": [1, 2, 3]}),
serde_json::Value::Null,
],
&[serde_json::json!({"array": [1999, 2, 3]})],
);
}
#[test]
fn reflect_struct_with_array() {
#[derive(Reflect, Default, Deserialize, Serialize)]