Schema Types Metadata
This commit is contained in:
parent
de79d3f363
commit
2f082b63e9
@ -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"
|
||||
|
@ -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>>();
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user