Schema Types Metadata

This commit is contained in:
Piotr Siuszko 2025-06-07 12:34:04 +02:00
parent de79d3f363
commit 2f082b63e9
4 changed files with 148 additions and 40 deletions

View File

@ -26,6 +26,7 @@ bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-fea
"std",
"serialize",
] }
bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" }
# other
anyhow = "1"

View File

@ -24,7 +24,7 @@ use serde_json::{Map, Value};
use crate::{
error_codes,
schemas::{json_schema::JsonSchemaBevyType, open_rpc::OpenRpcDocument},
schemas::{json_schema::{export_type, JsonSchemaBevyType}, open_rpc::OpenRpcDocument},
BrpError, BrpResult,
};
@ -1223,13 +1223,25 @@ pub fn export_registry_types(In(params): In<Option<Value>>, world: &World) -> Br
Some(params) => parse(params)?,
};
let extra_info = world.resource::<crate::schemas::SchemaTypesMetadata>();
let types = world.resource::<AppTypeRegistry>();
let types = types.read();
let schemas = types
.iter()
.map(crate::schemas::json_schema::export_type)
.filter(|(_, schema)| {
if let Some(crate_name) = &schema.crate_name {
.filter(|type_reg| {
// EXTRA FILTER, not decided yet if gonna make it in the end
match type_reg.type_info() {
bevy_reflect::TypeInfo::Tuple(_) => return false,
bevy_reflect::TypeInfo::TupleStruct(_) => return false,
bevy_reflect::TypeInfo::List(_) => return false,
bevy_reflect::TypeInfo::Array(_) => return false,
bevy_reflect::TypeInfo::Map(_) => return false,
bevy_reflect::TypeInfo::Set(_) => return false,
_ => {}
}
let path_table = type_reg.type_info().type_path_table();
if let Some(crate_name) = &path_table.crate_name() {
if !filter.with_crates.is_empty()
&& !filter.with_crates.iter().any(|c| crate_name.eq(c))
{
@ -1241,6 +1253,12 @@ pub fn export_registry_types(In(params): In<Option<Value>>, world: &World) -> Br
return false;
}
}
true
})
.flat_map(|e| {
let (id, schema) = export_type(e, extra_info);
if !filter.type_limit.with.is_empty()
&& !filter
.type_limit
@ -1248,7 +1266,7 @@ pub fn export_registry_types(In(params): In<Option<Value>>, world: &World) -> Br
.iter()
.any(|c| schema.reflect_types.iter().any(|cc| c.eq(cc)))
{
return false;
return None;
}
if !filter.type_limit.without.is_empty()
&& filter
@ -1257,10 +1275,9 @@ pub fn export_registry_types(In(params): In<Option<Value>>, world: &World) -> Br
.iter()
.any(|c| schema.reflect_types.iter().any(|cc| c.eq(cc)))
{
return false;
return None;
}
true
Some((id, schema))
})
.collect::<HashMap<String, JsonSchemaBevyType>>();

View File

@ -1,47 +1,61 @@
//! Module with JSON Schema type for Bevy Registry Types.
//! It tries to follow this standard: <https://json-schema.org/specification>
use bevy_ecs::reflect::{ReflectComponent, ReflectResource};
use bevy_platform::collections::HashMap;
use bevy_reflect::{
prelude::ReflectDefault, NamedField, OpaqueInfo, ReflectDeserialize, ReflectSerialize,
TypeInfo, TypeRegistration, VariantInfo,
GetTypeRegistration, NamedField, OpaqueInfo, TypeInfo, TypeRegistration, TypeRegistry, VariantInfo
};
use core::any::TypeId;
use serde::{Deserialize, Serialize};
use serde_json::{json, Map, Value};
/// Exports schema info for a given type
pub fn export_type(reg: &TypeRegistration) -> (String, JsonSchemaBevyType) {
(reg.type_info().type_path().to_owned(), reg.into())
}
use crate::schemas::SchemaTypesMetadata;
fn get_registered_reflect_types(reg: &TypeRegistration) -> Vec<String> {
// Vec could be moved to allow registering more types by game maker.
let registered_reflect_types: [(TypeId, &str); 5] = [
{ (TypeId::of::<ReflectComponent>(), "Component") },
{ (TypeId::of::<ReflectResource>(), "Resource") },
{ (TypeId::of::<ReflectDefault>(), "Default") },
{ (TypeId::of::<ReflectSerialize>(), "Serialize") },
{ (TypeId::of::<ReflectDeserialize>(), "Deserialize") },
];
let mut result = Vec::new();
for (id, name) in registered_reflect_types {
if reg.data_by_id(id).is_some() {
result.push(name.to_owned());
}
/// Helper trait for converting TypeRegistration to JsonSchemaBevyType
pub trait TypeRegistrySchemaReader {
/// Export type JSON Schema with definitions.
/// It can be useful for generating schemas for assets validation.
fn export_type_json_schema<T: GetTypeRegistration>(
&self,
extra_info: &SchemaTypesMetadata,
) -> Option<JsonSchemaBevyType> {
self.export_type_json_schema_for_id(extra_info, T::get_type_registration().type_id())
}
result
/// Export type JSON Schema with definitions.
/// It can be useful for generating schemas for assets validation.
fn export_type_json_schema_for_id(
&self,
extra_info: &SchemaTypesMetadata,
type_id: TypeId,
) -> Option<JsonSchemaBevyType>;
}
impl From<&TypeRegistration> for JsonSchemaBevyType {
fn from(reg: &TypeRegistration) -> Self {
impl TypeRegistrySchemaReader for TypeRegistry {
fn export_type_json_schema_for_id(
&self,
extra_info: &SchemaTypesMetadata,
type_id: TypeId,
) -> Option<JsonSchemaBevyType> {
let type_reg = self.get(type_id)?;
Some((type_reg,extra_info).into())
}
}
/// Exports schema info for a given type
pub fn export_type(reg: &TypeRegistration, metadata: &SchemaTypesMetadata) -> (String, JsonSchemaBevyType) {
(reg.type_info().type_path().to_owned(), (reg,metadata).into())
}
impl From<(&TypeRegistration, &SchemaTypesMetadata)> for JsonSchemaBevyType {
fn from(value: (&TypeRegistration, &SchemaTypesMetadata)) -> Self {
let (reg, metadata) = value;
let t = reg.type_info();
let binding = t.type_path_table();
let short_path = binding.short_path();
let type_path = binding.path();
let mut typed_schema = JsonSchemaBevyType {
reflect_types: get_registered_reflect_types(reg),
reflect_types: metadata.get_registered_reflect_types(reg),
short_path: short_path.to_owned(),
type_path: type_path.to_owned(),
crate_name: binding.crate_name().map(str::to_owned),
@ -351,8 +365,11 @@ impl SchemaJsonReference for &NamedField {
#[cfg(test)]
mod tests {
use super::*;
use bevy_ecs::prelude::ReflectResource;
use bevy_ecs::{component::Component, reflect::AppTypeRegistry, resource::Resource};
use bevy_reflect::Reflect;
use bevy_reflect::prelude::ReflectDefault;
use bevy_ecs::prelude::ReflectComponent;
use bevy_reflect::{Reflect,ReflectSerialize, ReflectDeserialize};
#[test]
fn reflect_export_struct() {
@ -373,7 +390,7 @@ mod tests {
.get(TypeId::of::<Foo>())
.expect("SHOULD BE REGISTERED")
.clone();
let (_, schema) = export_type(&foo_registration);
let (_, schema) = export_type(&foo_registration, &SchemaTypesMetadata::default());
assert!(
!schema.reflect_types.contains(&"Component".to_owned()),
@ -418,7 +435,7 @@ mod tests {
.get(TypeId::of::<EnumComponent>())
.expect("SHOULD BE REGISTERED")
.clone();
let (_, schema) = export_type(&foo_registration);
let (_, schema) = export_type(&foo_registration,&SchemaTypesMetadata::default());
assert!(
schema.reflect_types.contains(&"Component".to_owned()),
"Should be a component"
@ -453,7 +470,7 @@ mod tests {
.get(TypeId::of::<EnumComponent>())
.expect("SHOULD BE REGISTERED")
.clone();
let (_, schema) = export_type(&foo_registration);
let (_, schema) = export_type(&foo_registration,&SchemaTypesMetadata::default());
assert!(
!schema.reflect_types.contains(&"Component".to_owned()),
"Should not be a component"
@ -482,7 +499,7 @@ mod tests {
.get(TypeId::of::<TupleStructType>())
.expect("SHOULD BE REGISTERED")
.clone();
let (_, schema) = export_type(&foo_registration);
let (_, schema) = export_type(&foo_registration, &SchemaTypesMetadata::default());
assert!(
schema.reflect_types.contains(&"Component".to_owned()),
"Should be a component"
@ -513,7 +530,7 @@ mod tests {
.get(TypeId::of::<Foo>())
.expect("SHOULD BE REGISTERED")
.clone();
let (_, schema) = export_type(&foo_registration);
let (_, schema) = export_type(&foo_registration, &SchemaTypesMetadata::default());
let schema_as_value = serde_json::to_value(&schema).expect("Should serialize");
let value = json!({
"shortPath": "Foo",
@ -538,6 +555,32 @@ mod tests {
"a"
]
});
assert_eq!(schema_as_value, value);
assert_normalized_values(schema_as_value, value);
}
fn assert_normalized_values(one: Value, two: Value){
let mut one = one.clone();
normalize_json(&mut one);
let mut two = two.clone();
normalize_json(&mut two);
assert_eq!(one,two);
/// Recursively sorts arrays in a serde_json::Value
fn normalize_json(value: &mut Value) {
match value {
Value::Array(arr) => {
for v in arr.iter_mut() {
normalize_json(v);
}
arr.sort_by(|a, b| a.to_string().cmp(&b.to_string())); // Sort by stringified version
}
Value::Object(map) => {
for (_k, v) in map.iter_mut() {
normalize_json(v);
}
}
_ => {}
}
}
}
}

View File

@ -1,4 +1,51 @@
//! Module with schemas used for various BRP endpoints
use std::any::TypeId;
use bevy_asset::{ReflectAsset, ReflectHandle};
use bevy_ecs::{reflect::{ReflectComponent, ReflectResource}, resource::Resource};
use bevy_platform::collections::HashMap;
use bevy_reflect::{prelude::ReflectDefault, Reflect, ReflectDeserialize, ReflectSerialize, TypeData, TypeRegistration};
pub mod json_schema;
pub mod open_rpc;
/// Holds mapping of reflect data types to strings,
/// later on used in Bevy Json Schema.
#[derive(Debug, Resource, Reflect)]
#[reflect(Resource)]
pub struct SchemaTypesMetadata {
/// data types id mapping to strings.
pub data_types: HashMap<TypeId, String>,
}
impl Default for SchemaTypesMetadata {
fn default() -> Self {
let mut data_types = Self {
data_types: Default::default(),
};
data_types.register_type::<ReflectComponent>("Component");
data_types.register_type::<ReflectResource>("Resource");
data_types.register_type::<ReflectDefault>("Default");
data_types.register_type::<ReflectAsset>("Asset");
data_types.register_type::<ReflectHandle>("AssetHandle");
data_types.register_type::<ReflectSerialize>("Serialize");
data_types.register_type::<ReflectDeserialize>("Deserialize");
data_types
}
}
impl SchemaTypesMetadata {
/// Map TypeId of TypeData to string
pub fn register_type<T: TypeData>(&mut self, name: impl Into<String>) {
self.data_types.insert(TypeId::of::<T>(), name.into());
}
/// build reflect types list for a given type registration
pub fn get_registered_reflect_types(&self, reg: &TypeRegistration) -> Vec<String> {
self.data_types
.iter()
.flat_map(|(id, name)| reg.data_by_id(*id).and(Some(name.clone())))
.collect()
}
}