Reworked building type dependencies list
This commit is contained in:
parent
0ff3201c3c
commit
64807b0d38
@ -27,9 +27,9 @@ use serde_json::{Map, Value};
|
|||||||
use crate::{
|
use crate::{
|
||||||
error_codes,
|
error_codes,
|
||||||
schemas::{
|
schemas::{
|
||||||
json_schema::{JsonSchemaBevyType, SchemaMarker, TypeRegistrySchemaReader},
|
json_schema::{JsonSchemaBevyType, SchemaMarker},
|
||||||
open_rpc::OpenRpcDocument,
|
open_rpc::OpenRpcDocument,
|
||||||
reflect_info::TypeInformation,
|
reflect_info::TypeDefinitionBuilder,
|
||||||
},
|
},
|
||||||
BrpError, BrpResult,
|
BrpError, BrpResult,
|
||||||
};
|
};
|
||||||
@ -367,9 +367,11 @@ pub struct BrpJsonSchemaQueryFilter {
|
|||||||
impl BrpJsonSchemaQueryFilter {
|
impl BrpJsonSchemaQueryFilter {
|
||||||
/// Check if the filter should skip a type registration based on the crate name.
|
/// Check if the filter should skip a type registration based on the crate name.
|
||||||
pub fn should_skip_for_crate(&self, type_registration: &TypeRegistration) -> bool {
|
pub fn should_skip_for_crate(&self, type_registration: &TypeRegistration) -> bool {
|
||||||
let Some(crate_name) = type_registration.type_info().type_path_table().crate_name() else {
|
let crate_name = type_registration
|
||||||
return false;
|
.type_info()
|
||||||
};
|
.type_path_table()
|
||||||
|
.crate_name()
|
||||||
|
.unwrap_or_default();
|
||||||
if !self.with_crates.is_empty() && !self.with_crates.iter().any(|c| crate_name.eq(c)) {
|
if !self.with_crates.is_empty() && !self.with_crates.iter().any(|c| crate_name.eq(c)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -1393,7 +1395,7 @@ fn export_registry_types_typed(
|
|||||||
filter: BrpJsonSchemaQueryFilter,
|
filter: BrpJsonSchemaQueryFilter,
|
||||||
world: &World,
|
world: &World,
|
||||||
) -> Result<JsonSchemaBevyType, BrpError> {
|
) -> Result<JsonSchemaBevyType, BrpError> {
|
||||||
let extra_info = world.resource::<crate::schemas::SchemaTypesMetadata>();
|
let metadata = world.resource::<crate::schemas::SchemaTypesMetadata>();
|
||||||
let types = world.resource::<AppTypeRegistry>();
|
let types = world.resource::<AppTypeRegistry>();
|
||||||
let types = types.read();
|
let types = types.read();
|
||||||
let mut schema = JsonSchemaBevyType {
|
let mut schema = JsonSchemaBevyType {
|
||||||
@ -1403,14 +1405,14 @@ fn export_registry_types_typed(
|
|||||||
),
|
),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
schema.definitions = types
|
let definitions: Vec<TypeId> = types
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|type_reg| {
|
.filter_map(|type_reg| {
|
||||||
if filter.should_skip_for_crate(type_reg) {
|
if filter.should_skip_for_crate(type_reg) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
if !filter.type_limit.is_empty() {
|
if !filter.type_limit.is_empty() {
|
||||||
let registered_types = extra_info.get_registered_reflect_types(type_reg);
|
let registered_types = metadata.get_registered_reflect_types(type_reg);
|
||||||
if filter
|
if filter
|
||||||
.type_limit
|
.type_limit
|
||||||
.should_skip_type(registered_types.as_slice())
|
.should_skip_type(registered_types.as_slice())
|
||||||
@ -1418,23 +1420,23 @@ fn export_registry_types_typed(
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let id = type_reg.type_id();
|
let type_id = type_reg.type_id();
|
||||||
let schema_type_info = TypeInformation::TypeRegistration(type_reg.clone())
|
let mut dep_ids = types.get_type_dependencies(type_id);
|
||||||
.to_schema_type_info_with_metadata(extra_info);
|
dep_ids.insert(type_id);
|
||||||
let schema_id = schema_type_info.ty_info.try_get_type_reference_id()?;
|
Some(dep_ids)
|
||||||
let mut definition = schema_type_info.to_definition();
|
|
||||||
definition.schema.default_value = types.try_get_default_value_for_type_id(id);
|
|
||||||
definition.schema.schema = None;
|
|
||||||
let mut definitions = vec![(schema_id, Box::new(definition.schema))];
|
|
||||||
let extra_defs = definition.definitions.iter().map(|(id, definition)| {
|
|
||||||
let def = definition.to_definition();
|
|
||||||
(id.clone(), Box::new(def.schema))
|
|
||||||
});
|
|
||||||
definitions.extend(extra_defs);
|
|
||||||
Some(definitions)
|
|
||||||
})
|
})
|
||||||
.flatten()
|
.flatten()
|
||||||
.collect();
|
.collect();
|
||||||
|
schema.definitions = definitions
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|id| {
|
||||||
|
let result = types.build_schema_for_type_id(id, metadata);
|
||||||
|
let Some((Some(schema_id), schema)) = result else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
Some((schema_id, Box::new(schema)))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
Ok(schema)
|
Ok(schema)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1678,12 +1680,11 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
use bevy_ecs::component::Component;
|
use bevy_ecs::component::Component;
|
||||||
|
#[cfg(feature = "bevy_math")]
|
||||||
use bevy_math::Vec3;
|
use bevy_math::Vec3;
|
||||||
use bevy_reflect::Reflect;
|
use bevy_reflect::Reflect;
|
||||||
|
|
||||||
use crate::schemas::{
|
use crate::schemas::{reflect_info::TypeReferenceId, SchemaTypesMetadata};
|
||||||
reflect_info::TypeReferenceId, ReflectJsonSchemaForceAsArray, SchemaTypesMetadata,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
@ -1710,11 +1711,13 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(feature = "bevy_math")]
|
||||||
fn export_schema_test() {
|
fn export_schema_test() {
|
||||||
#[derive(Reflect, Default, Deserialize, Serialize)]
|
#[derive(Reflect, Default, Deserialize, Serialize)]
|
||||||
pub struct OtherStruct {
|
pub struct OtherStruct {
|
||||||
/// FIELD DOC
|
/// FIELD DOC
|
||||||
pub field: String,
|
pub field: String,
|
||||||
|
pub second_field: Option<(u8, u8)>,
|
||||||
}
|
}
|
||||||
/// STRUCT DOC
|
/// STRUCT DOC
|
||||||
#[derive(Reflect, Default, Deserialize, Serialize)]
|
#[derive(Reflect, Default, Deserialize, Serialize)]
|
||||||
@ -1742,6 +1745,8 @@ mod tests {
|
|||||||
let mut world = World::new();
|
let mut world = World::new();
|
||||||
let atr = AppTypeRegistry::default();
|
let atr = AppTypeRegistry::default();
|
||||||
{
|
{
|
||||||
|
use crate::schemas::ReflectJsonSchemaForceAsArray;
|
||||||
|
|
||||||
let mut register = atr.write();
|
let mut register = atr.write();
|
||||||
register.register::<NestedStruct>();
|
register.register::<NestedStruct>();
|
||||||
register.register::<Vec3>();
|
register.register::<Vec3>();
|
||||||
@ -1750,21 +1755,32 @@ mod tests {
|
|||||||
world.insert_resource(atr);
|
world.insert_resource(atr);
|
||||||
world.insert_resource(SchemaTypesMetadata::default());
|
world.insert_resource(SchemaTypesMetadata::default());
|
||||||
let response = export_registry_types_ext(BrpJsonSchemaQueryFilter::default(), &world);
|
let response = export_registry_types_ext(BrpJsonSchemaQueryFilter::default(), &world);
|
||||||
|
let schema_value = serde_json::to_value(&response).unwrap();
|
||||||
|
_ = jsonschema::options()
|
||||||
|
.with_draft(jsonschema::Draft::Draft202012)
|
||||||
|
.build(&schema_value)
|
||||||
|
.expect("Failed to validate json schema");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
response.definitions.len(),
|
response.definitions.len(),
|
||||||
5,
|
6,
|
||||||
"Expected 5 definitions, got: {:#?}",
|
"Expected 6 definitions, got: {}: {}",
|
||||||
response.definitions.keys()
|
response.definitions.keys().len(),
|
||||||
|
serde_json::to_string_pretty(&response).unwrap_or_default()
|
||||||
);
|
);
|
||||||
let response = export_registry_types_ext(
|
let response = export_registry_types_ext(
|
||||||
BrpJsonSchemaQueryFilter {
|
BrpJsonSchemaQueryFilter {
|
||||||
without_crates: vec!["bevy_remote".to_string()],
|
with_crates: vec!["glam".to_string()],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
&world,
|
&world,
|
||||||
);
|
);
|
||||||
assert_eq!(response.definitions.len(), 1);
|
assert_eq!(
|
||||||
|
response.definitions.len(),
|
||||||
|
1,
|
||||||
|
"Expected 1 definition, got: {}: {:?}",
|
||||||
|
response.definitions.keys().len(),
|
||||||
|
response.definitions.keys()
|
||||||
|
);
|
||||||
{
|
{
|
||||||
let first = response.definitions.iter().next().expect("Should have one");
|
let first = response.definitions.iter().next().expect("Should have one");
|
||||||
assert_eq!(first.0, &TypeReferenceId::from("glam-Vec3"));
|
assert_eq!(first.0, &TypeReferenceId::from("glam-Vec3"));
|
||||||
@ -1776,7 +1792,13 @@ mod tests {
|
|||||||
},
|
},
|
||||||
&world,
|
&world,
|
||||||
);
|
);
|
||||||
assert_eq!(response.definitions.len(), 4);
|
assert_eq!(
|
||||||
|
response.definitions.len(),
|
||||||
|
5,
|
||||||
|
"Expected 5 definitions, got: {}: {:?}",
|
||||||
|
response.definitions.len(),
|
||||||
|
response.definitions.keys()
|
||||||
|
);
|
||||||
let response = export_registry_types_ext(
|
let response = export_registry_types_ext(
|
||||||
BrpJsonSchemaQueryFilter {
|
BrpJsonSchemaQueryFilter {
|
||||||
type_limit: JsonSchemaTypeLimit {
|
type_limit: JsonSchemaTypeLimit {
|
||||||
@ -1787,7 +1809,13 @@ mod tests {
|
|||||||
},
|
},
|
||||||
&world,
|
&world,
|
||||||
);
|
);
|
||||||
assert_eq!(response.definitions.len(), 4);
|
assert_eq!(
|
||||||
|
response.definitions.len(),
|
||||||
|
5,
|
||||||
|
"Expected 5 definitions, got: {}: {:?}",
|
||||||
|
response.definitions.len(),
|
||||||
|
response.definitions.keys()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn export_registry_types_ext(
|
fn export_registry_types_ext(
|
||||||
|
@ -47,17 +47,15 @@ impl TypeRegistrySchemaReader for TypeRegistry {
|
|||||||
let mut definition = TypeInformation::from(type_reg)
|
let mut definition = TypeInformation::from(type_reg)
|
||||||
.to_schema_type_info_with_metadata(extra_info)
|
.to_schema_type_info_with_metadata(extra_info)
|
||||||
.to_definition();
|
.to_definition();
|
||||||
for missing in &definition.missing_definitions {
|
for dependency in &definition.dependencies {
|
||||||
let reg_option = self.get(*missing);
|
let reg_option = self.get(*dependency);
|
||||||
if let Some(reg) = reg_option {
|
if let Some(reg) = reg_option {
|
||||||
let missing_schema =
|
let missing_schema =
|
||||||
TypeInformation::from(reg).to_schema_type_info_with_metadata(extra_info);
|
TypeInformation::from(reg).to_schema_type_info_with_metadata(extra_info);
|
||||||
let mis_def = missing_schema.to_definition();
|
let mis_def = missing_schema.to_definition();
|
||||||
definition.definitions.extend(mis_def.definitions);
|
definition.definitions.extend(mis_def.definitions);
|
||||||
if let Some(missing_id) = mis_def.id {
|
if let Some(missing_id) = mis_def.id {
|
||||||
if !definition.definitions.contains_key(&missing_id) {
|
definition.definitions.insert(missing_id, missing_schema);
|
||||||
definition.definitions.insert(missing_id, missing_schema);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ pub(crate) trait RegisterReflectJsonSchemas {
|
|||||||
fn register_schema_base_types(&mut self) {
|
fn register_schema_base_types(&mut self) {
|
||||||
#[cfg(feature = "bevy_math")]
|
#[cfg(feature = "bevy_math")]
|
||||||
{
|
{
|
||||||
// self.register_type_data_internal::<bevy_math::Vec2, ReflectJsonSchemaForceAsArray>();
|
self.register_type_data_internal::<bevy_math::Vec2, ReflectJsonSchemaForceAsArray>();
|
||||||
self.register_type_data_internal::<bevy_math::Vec3, ReflectJsonSchemaForceAsArray>();
|
self.register_type_data_internal::<bevy_math::Vec3, ReflectJsonSchemaForceAsArray>();
|
||||||
}
|
}
|
||||||
self.register_type_internal::<OpenRpcDocument>();
|
self.register_type_internal::<OpenRpcDocument>();
|
||||||
@ -65,7 +65,9 @@ impl RegisterReflectJsonSchemas for bevy_reflect::TypeRegistry {
|
|||||||
T: Reflect + bevy_reflect::TypePath,
|
T: Reflect + bevy_reflect::TypePath,
|
||||||
D: TypeData + FromType<T>,
|
D: TypeData + FromType<T>,
|
||||||
{
|
{
|
||||||
self.register_type_data::<T, D>();
|
if self.contains(TypeId::of::<T>()) {
|
||||||
|
self.register_type_data::<T, D>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_type_internal<T>(&mut self)
|
fn register_type_internal<T>(&mut self)
|
||||||
|
@ -2,11 +2,11 @@
|
|||||||
use alloc::borrow::Cow;
|
use alloc::borrow::Cow;
|
||||||
use alloc::sync::Arc;
|
use alloc::sync::Arc;
|
||||||
use bevy_derive::{Deref, DerefMut};
|
use bevy_derive::{Deref, DerefMut};
|
||||||
use bevy_platform::collections::HashMap;
|
use bevy_platform::collections::{HashMap, HashSet};
|
||||||
use bevy_reflect::attributes::CustomAttributes;
|
use bevy_reflect::attributes::CustomAttributes;
|
||||||
use bevy_reflect::{
|
use bevy_reflect::{
|
||||||
EnumInfo, GenericInfo, NamedField, Reflect, Type, TypeInfo, TypePathTable, TypeRegistration,
|
EnumInfo, GenericInfo, NamedField, Reflect, Type, TypeInfo, TypePathTable, TypeRegistration,
|
||||||
UnnamedField, VariantInfo,
|
TypeRegistry, UnnamedField, VariantInfo,
|
||||||
};
|
};
|
||||||
use bevy_utils::{default, TypeIdMap};
|
use bevy_utils::{default, TypeIdMap};
|
||||||
use core::any::TypeId;
|
use core::any::TypeId;
|
||||||
@ -79,7 +79,7 @@ impl From<&str> for TypeReferenceId {
|
|||||||
|
|
||||||
/// Information about the attributes of a field.
|
/// Information about the attributes of a field.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct FieldInformation {
|
pub(crate) struct FieldInformation {
|
||||||
/// Field specific data
|
/// Field specific data
|
||||||
field: SchemaFieldData,
|
field: SchemaFieldData,
|
||||||
/// Type information of the field.
|
/// Type information of the field.
|
||||||
@ -88,7 +88,7 @@ pub struct FieldInformation {
|
|||||||
|
|
||||||
/// Information about the field type.
|
/// Information about the field type.
|
||||||
#[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Hash, Default)]
|
#[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Hash, Default)]
|
||||||
pub enum FieldType {
|
pub(crate) enum FieldType {
|
||||||
/// Named field type.
|
/// Named field type.
|
||||||
Named,
|
Named,
|
||||||
/// Unnamed field type.
|
/// Unnamed field type.
|
||||||
@ -100,7 +100,7 @@ pub enum FieldType {
|
|||||||
|
|
||||||
/// Information about the attributes of a field.
|
/// Information about the attributes of a field.
|
||||||
#[derive(Clone, Debug, Deref, DerefMut, Default)]
|
#[derive(Clone, Debug, Deref, DerefMut, Default)]
|
||||||
pub struct FieldsInformation {
|
pub(crate) struct FieldsInformation {
|
||||||
/// Fields information.
|
/// Fields information.
|
||||||
#[deref]
|
#[deref]
|
||||||
fields: Vec<FieldInformation>,
|
fields: Vec<FieldInformation>,
|
||||||
@ -144,7 +144,7 @@ impl From<&TypeInformation> for Option<FieldsInformation> {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Deref)]
|
#[derive(Clone, Debug, Deref)]
|
||||||
/// Information about the attributes of a field.
|
/// Information about the attributes of a field.
|
||||||
pub struct AttributesInformation(Arc<TypeIdMap<Box<dyn Reflect>>>);
|
pub(crate) struct AttributesInformation(Arc<TypeIdMap<Box<dyn Reflect>>>);
|
||||||
|
|
||||||
impl From<&CustomAttributes> for AttributesInformation {
|
impl From<&CustomAttributes> for AttributesInformation {
|
||||||
fn from(attributes: &CustomAttributes) -> Self {
|
fn from(attributes: &CustomAttributes) -> Self {
|
||||||
@ -173,7 +173,7 @@ impl AttributeInfoReflect for CustomAttributes {
|
|||||||
/// sources in the Bevy reflection system, allowing for flexible handling of type
|
/// sources in the Bevy reflection system, allowing for flexible handling of type
|
||||||
/// metadata during schema generation.
|
/// metadata during schema generation.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum TypeInformation {
|
pub(crate) enum TypeInformation {
|
||||||
/// Contains a complete type registration with all associated metadata.
|
/// Contains a complete type registration with all associated metadata.
|
||||||
///
|
///
|
||||||
/// This variant holds a full `TypeRegistration` which includes type info,
|
/// This variant holds a full `TypeRegistration` which includes type info,
|
||||||
@ -206,6 +206,14 @@ pub enum TypeInformation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TypeInformation {
|
impl TypeInformation {
|
||||||
|
/// Find the type registration in the registry.
|
||||||
|
pub fn find_in_registry<'a>(&self, registry: &'a TypeRegistry) -> Option<&'a TypeRegistration> {
|
||||||
|
match self.try_get_type_path_table() {
|
||||||
|
Some(path_table) => registry.get_with_type_path(path_table.path()),
|
||||||
|
None => registry.get(self.type_id()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Try to get a regex pattern for the type.
|
/// Try to get a regex pattern for the type.
|
||||||
pub fn try_get_regex_for_type(&self) -> Option<Cow<'static, str>> {
|
pub fn try_get_regex_for_type(&self) -> Option<Cow<'static, str>> {
|
||||||
let primitive_type = self.try_get_primitive_type()?;
|
let primitive_type = self.try_get_primitive_type()?;
|
||||||
@ -315,16 +323,6 @@ impl TypeInformation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the type of the type.
|
|
||||||
pub fn try_get_type(&self) -> Option<&Type> {
|
|
||||||
match self {
|
|
||||||
TypeInformation::TypeInfo(type_info) => Some(type_info.ty()),
|
|
||||||
TypeInformation::TypeRegistration(reg) => Some(reg.type_info().ty()),
|
|
||||||
TypeInformation::Type(t) => Some(&**t),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns whether the type is forced as an array.
|
/// Returns whether the type is forced as an array.
|
||||||
pub fn is_forced_as_array(&self) -> bool {
|
pub fn is_forced_as_array(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
@ -365,7 +363,9 @@ impl TypeInformation {
|
|||||||
type_info.type_id()
|
type_info.type_id()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TypeInformation::TypeRegistration(type_registration) => type_registration.type_id(),
|
TypeInformation::TypeRegistration(type_registration) => {
|
||||||
|
type_registration.type_info().type_id()
|
||||||
|
}
|
||||||
TypeInformation::VariantInfo(variant_info) => match &**variant_info {
|
TypeInformation::VariantInfo(variant_info) => match &**variant_info {
|
||||||
VariantInfo::Struct(struct_info) => struct_info.type_id(),
|
VariantInfo::Struct(struct_info) => struct_info.type_id(),
|
||||||
VariantInfo::Tuple(tuple_variant_info) => {
|
VariantInfo::Tuple(tuple_variant_info) => {
|
||||||
@ -454,7 +454,7 @@ impl TryFrom<&TypeInformation> for TypeReferenceId {
|
|||||||
|
|
||||||
/// Represents the data of a field in a schema.
|
/// Represents the data of a field in a schema.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SchemaFieldData {
|
pub(crate) struct SchemaFieldData {
|
||||||
/// Name of the field.
|
/// Name of the field.
|
||||||
pub name: Option<Cow<'static, str>>,
|
pub name: Option<Cow<'static, str>>,
|
||||||
/// Index of the field. Can be provided for named fields when the data is obtained from containing struct definition.
|
/// Index of the field. Can be provided for named fields when the data is obtained from containing struct definition.
|
||||||
@ -483,10 +483,6 @@ impl SchemaFieldData {
|
|||||||
None => Cow::Owned(format!("[{}]", self.index.unwrap_or(0))),
|
None => Cow::Owned(format!("[{}]", self.index.unwrap_or(0))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Returns the index of the field.
|
|
||||||
pub fn index(&self) -> usize {
|
|
||||||
self.index.unwrap_or(0)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stores information about the location and id of a reference in a JSON schema.
|
/// Stores information about the location and id of a reference in a JSON schema.
|
||||||
@ -889,7 +885,7 @@ pub(super) fn is_non_zero_number_type(t: TypeId) -> bool {
|
|||||||
/// Enum representing the internal schema type information for different Rust types.
|
/// Enum representing the internal schema type information for different Rust types.
|
||||||
/// This enum categorizes how different types should be represented in JSON schema.
|
/// This enum categorizes how different types should be represented in JSON schema.
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub enum InternalSchemaType {
|
pub(crate) enum InternalSchemaType {
|
||||||
/// Represents array-like types (Vec, arrays, lists, sets).
|
/// Represents array-like types (Vec, arrays, lists, sets).
|
||||||
Array {
|
Array {
|
||||||
/// Element type information for the array.
|
/// Element type information for the array.
|
||||||
@ -927,6 +923,116 @@ pub enum InternalSchemaType {
|
|||||||
#[default]
|
#[default]
|
||||||
NoInfo,
|
NoInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl InternalSchemaType {
|
||||||
|
/// Returns the dependencies of the type.
|
||||||
|
pub(super) fn get_dependencies(&self, registry: &TypeRegistry) -> HashSet<TypeId> {
|
||||||
|
let mut dependencies = HashSet::new();
|
||||||
|
match &self {
|
||||||
|
InternalSchemaType::Array {
|
||||||
|
element_ty,
|
||||||
|
min_size: _,
|
||||||
|
max_size: _,
|
||||||
|
} => {
|
||||||
|
if let Some(reg) = element_ty.find_in_registry(registry) {
|
||||||
|
let info = TypeInformation::from(reg);
|
||||||
|
if !info.is_primitive_type() {
|
||||||
|
let subschema: InternalSchemaType = (&info).into();
|
||||||
|
dependencies.insert(reg.type_id());
|
||||||
|
dependencies.extend(subschema.get_dependencies(registry));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InternalSchemaType::EnumHolder(variant_infos) => {
|
||||||
|
for variant_info in variant_infos {
|
||||||
|
let sub_schema = InternalSchemaType::EnumVariant(variant_info.clone());
|
||||||
|
dependencies.extend(sub_schema.get_dependencies(registry));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InternalSchemaType::EnumVariant(variant_info) => match variant_info {
|
||||||
|
VariantInfo::Struct(struct_variant_info) => {
|
||||||
|
for field in struct_variant_info.iter() {
|
||||||
|
if let Some(reg) = registry.get(field.type_id()) {
|
||||||
|
let info = TypeInformation::from(reg);
|
||||||
|
if !info.is_primitive_type() {
|
||||||
|
let subschema: InternalSchemaType = (&info).into();
|
||||||
|
dependencies.insert(info.type_id());
|
||||||
|
dependencies.extend(subschema.get_dependencies(registry));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VariantInfo::Tuple(tuple_variant_info) => {
|
||||||
|
for field in tuple_variant_info.iter() {
|
||||||
|
if let Some(reg) = registry.get(field.type_id()) {
|
||||||
|
let info = TypeInformation::from(reg);
|
||||||
|
if !info.is_primitive_type() {
|
||||||
|
let subschema: InternalSchemaType = (&info).into();
|
||||||
|
dependencies.insert(info.type_id());
|
||||||
|
dependencies.extend(subschema.get_dependencies(registry));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VariantInfo::Unit(_) => {}
|
||||||
|
},
|
||||||
|
InternalSchemaType::FieldsHolder(fields_information) => {
|
||||||
|
for field in fields_information.iter() {
|
||||||
|
let Some(reg) = field.type_info.find_in_registry(registry) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if SchemaType::try_get_primitive_type_from_type_id(reg.type_id()).is_some() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let info = TypeInformation::from(reg);
|
||||||
|
let subschema: InternalSchemaType = (&info).into();
|
||||||
|
dependencies.insert(info.type_id());
|
||||||
|
dependencies.extend(subschema.get_dependencies(registry));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InternalSchemaType::Optional {
|
||||||
|
generic,
|
||||||
|
schema_type_info: _,
|
||||||
|
} => {
|
||||||
|
if let Some(reg) = registry.get(generic.type_id()) {
|
||||||
|
let info = TypeInformation::from(reg);
|
||||||
|
if !info.is_primitive_type() {
|
||||||
|
let subschema: InternalSchemaType = (&info).into();
|
||||||
|
dependencies.insert(info.type_id());
|
||||||
|
dependencies.extend(subschema.get_dependencies(registry));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InternalSchemaType::Map { key, value } => {
|
||||||
|
if let Some(reg) = registry.get(key.type_id()) {
|
||||||
|
let info = TypeInformation::from(reg);
|
||||||
|
if !info.is_primitive_type() {
|
||||||
|
let subschema: InternalSchemaType = (&info).into();
|
||||||
|
dependencies.insert(info.type_id());
|
||||||
|
dependencies.extend(subschema.get_dependencies(registry));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(reg) = registry.get(value.type_id()) {
|
||||||
|
let info = TypeInformation::from(reg);
|
||||||
|
if !info.is_primitive_type() {
|
||||||
|
let subschema: InternalSchemaType = (&info).into();
|
||||||
|
dependencies.insert(info.type_id());
|
||||||
|
dependencies.extend(subschema.get_dependencies(registry));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InternalSchemaType::RegularType(ty) => {
|
||||||
|
_ = dependencies.insert(ty.type_id());
|
||||||
|
}
|
||||||
|
InternalSchemaType::Regular(t) => {
|
||||||
|
_ = dependencies.insert(*t);
|
||||||
|
}
|
||||||
|
InternalSchemaType::NoInfo => {}
|
||||||
|
}
|
||||||
|
dependencies
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<&TypeInformation> for InternalSchemaType {
|
impl From<&TypeInformation> for InternalSchemaType {
|
||||||
fn from(value: &TypeInformation) -> Self {
|
fn from(value: &TypeInformation) -> Self {
|
||||||
let field_information: Option<FieldsInformation> = value.into();
|
let field_information: Option<FieldsInformation> = value.into();
|
||||||
@ -1064,7 +1170,7 @@ impl From<&FieldInformation> for SchemaTypeInfo {
|
|||||||
/// This struct aggregates all the necessary information to generate a JSON schema
|
/// This struct aggregates all the necessary information to generate a JSON schema
|
||||||
/// from Rust type information obtained through reflection.
|
/// from Rust type information obtained through reflection.
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct SchemaDefinition {
|
pub(crate) struct SchemaDefinition {
|
||||||
/// The type reference ID of the schema.
|
/// The type reference ID of the schema.
|
||||||
pub id: Option<TypeReferenceId>,
|
pub id: Option<TypeReferenceId>,
|
||||||
/// The JSON schema type of the schema.
|
/// The JSON schema type of the schema.
|
||||||
@ -1073,7 +1179,7 @@ pub struct SchemaDefinition {
|
|||||||
pub definitions: HashMap<TypeReferenceId, SchemaTypeInfo>,
|
pub definitions: HashMap<TypeReferenceId, SchemaTypeInfo>,
|
||||||
/// Missing definitions of the schema.
|
/// Missing definitions of the schema.
|
||||||
/// Could be the case for the types that are stored as generic arguments.
|
/// Could be the case for the types that are stored as generic arguments.
|
||||||
pub missing_definitions: Vec<TypeId>,
|
pub dependencies: Vec<TypeId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<SchemaDefinition> for JsonSchemaBevyType {
|
impl From<SchemaDefinition> for JsonSchemaBevyType {
|
||||||
@ -1093,7 +1199,7 @@ impl From<SchemaDefinition> for JsonSchemaBevyType {
|
|||||||
/// This struct aggregates all the necessary information to generate a JSON schema
|
/// This struct aggregates all the necessary information to generate a JSON schema
|
||||||
/// from Rust type information obtained through reflection.
|
/// from Rust type information obtained through reflection.
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct SchemaTypeInfo {
|
pub(crate) struct SchemaTypeInfo {
|
||||||
/// Information about the type of the schema.
|
/// Information about the type of the schema.
|
||||||
pub ty_info: TypeInformation,
|
pub ty_info: TypeInformation,
|
||||||
/// Field information for the type.
|
/// Field information for the type.
|
||||||
@ -1146,7 +1252,7 @@ impl SchemaTypeInfo {
|
|||||||
.map(TypeReferencePath::definition);
|
.map(TypeReferencePath::definition);
|
||||||
|
|
||||||
// If there is reference specified it is not need for specifying type
|
// If there is reference specified it is not need for specifying type
|
||||||
let schema_type = if ref_type.is_some() {
|
let schema_type = if ref_type.is_none() {
|
||||||
self.into()
|
self.into()
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@ -1240,15 +1346,15 @@ impl SchemaTypeInfo {
|
|||||||
pub fn to_definition(&self) -> SchemaDefinition {
|
pub fn to_definition(&self) -> SchemaDefinition {
|
||||||
let mut id: Option<TypeReferenceId> = self.ty_info.try_get_type_reference_id();
|
let mut id: Option<TypeReferenceId> = self.ty_info.try_get_type_reference_id();
|
||||||
let mut definitions: HashMap<TypeReferenceId, SchemaTypeInfo> = HashMap::new();
|
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() {
|
if let Some(custom_schema) = &self.ty_info.try_get_custom_schema() {
|
||||||
return SchemaDefinition {
|
return SchemaDefinition {
|
||||||
id,
|
id,
|
||||||
schema: custom_schema.0.clone(),
|
schema: custom_schema.0.clone(),
|
||||||
definitions,
|
definitions,
|
||||||
missing_definitions,
|
dependencies: Default::default(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
let mut dependencies: HashSet<TypeId> = HashSet::new();
|
||||||
let range = self.get_range();
|
let range = self.get_range();
|
||||||
|
|
||||||
let (type_path, short_path, crate_name, module_path) =
|
let (type_path, short_path, crate_name, module_path) =
|
||||||
@ -1303,9 +1409,9 @@ impl SchemaTypeInfo {
|
|||||||
id,
|
id,
|
||||||
schema: _,
|
schema: _,
|
||||||
definitions: field_definitions,
|
definitions: field_definitions,
|
||||||
missing_definitions: key_missing_definitions,
|
dependencies: key_dependencies,
|
||||||
} = key.to_definition();
|
} = key.to_definition();
|
||||||
missing_definitions.extend(key_missing_definitions);
|
dependencies.extend(key_dependencies);
|
||||||
if let Some(id) = id {
|
if let Some(id) = id {
|
||||||
definitions.insert(id, key.clone());
|
definitions.insert(id, key.clone());
|
||||||
definitions.extend(field_definitions);
|
definitions.extend(field_definitions);
|
||||||
@ -1317,10 +1423,10 @@ impl SchemaTypeInfo {
|
|||||||
id,
|
id,
|
||||||
schema: _,
|
schema: _,
|
||||||
definitions: field_definitions,
|
definitions: field_definitions,
|
||||||
missing_definitions: value_missing_definitions,
|
dependencies: value_dependencies,
|
||||||
} = value.to_definition();
|
} = value.to_definition();
|
||||||
|
|
||||||
missing_definitions.extend(value_missing_definitions);
|
dependencies.extend(value_dependencies);
|
||||||
if let Some(id) = id {
|
if let Some(id) = id {
|
||||||
definitions.insert(id, value.clone());
|
definitions.insert(id, value.clone());
|
||||||
definitions.extend(field_definitions);
|
definitions.extend(field_definitions);
|
||||||
@ -1415,7 +1521,7 @@ impl SchemaTypeInfo {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
definitions: HashMap::new(),
|
definitions: HashMap::new(),
|
||||||
missing_definitions,
|
dependencies: dependencies.iter().cloned().collect(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1437,18 +1543,19 @@ impl SchemaTypeInfo {
|
|||||||
if field_schema.ty_info.is_primitive_type() {
|
if field_schema.ty_info.is_primitive_type() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let SchemaDefinition {
|
dependencies.insert(field_schema.ty_info.type_id());
|
||||||
id,
|
// let SchemaDefinition {
|
||||||
schema: _,
|
// id,
|
||||||
definitions: field_definitions,
|
// schema: _,
|
||||||
missing_definitions: field_missing_definitions,
|
// definitions: field_definitions,
|
||||||
} = field_schema.to_definition();
|
// missing_definitions: field_missing_definitions,
|
||||||
missing_definitions.extend(field_missing_definitions);
|
// } = field_schema.to_definition();
|
||||||
definitions.extend(field_definitions);
|
// missing_definitions.extend(field_missing_definitions);
|
||||||
let Some(id) = id else { continue };
|
// definitions.extend(field_definitions);
|
||||||
if !definitions.contains_key(&id) {
|
// let Some(id) = id else { continue };
|
||||||
definitions.insert(id, field_schema);
|
// if !definitions.contains_key(&id) {
|
||||||
}
|
// definitions.insert(id, field_schema);
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
schema.required = fields
|
schema.required = fields
|
||||||
.fields
|
.fields
|
||||||
@ -1463,9 +1570,9 @@ impl SchemaTypeInfo {
|
|||||||
id,
|
id,
|
||||||
schema: new_schema_type,
|
schema: new_schema_type,
|
||||||
definitions: field_definitions,
|
definitions: field_definitions,
|
||||||
missing_definitions: field_missing_definitions,
|
dependencies: field_dependencies,
|
||||||
} = field_schema.to_definition();
|
} = field_schema.to_definition();
|
||||||
missing_definitions.extend(field_missing_definitions);
|
dependencies.extend(field_dependencies);
|
||||||
definitions.extend(field_definitions);
|
definitions.extend(field_definitions);
|
||||||
if let Some(id) = id {
|
if let Some(id) = id {
|
||||||
definitions.insert(id.clone(), field_schema);
|
definitions.insert(id.clone(), field_schema);
|
||||||
@ -1534,10 +1641,10 @@ impl SchemaTypeInfo {
|
|||||||
id,
|
id,
|
||||||
schema: _,
|
schema: _,
|
||||||
definitions: field_definitions,
|
definitions: field_definitions,
|
||||||
missing_definitions: field_missing_definitions,
|
dependencies: field_dependencies,
|
||||||
} = field_schema.to_definition();
|
} = field_schema.to_definition();
|
||||||
definitions.extend(field_definitions);
|
definitions.extend(field_definitions);
|
||||||
missing_definitions.extend(field_missing_definitions);
|
dependencies.extend(field_dependencies);
|
||||||
let Some(id) = id else { continue };
|
let Some(id) = id else { continue };
|
||||||
if !definitions.contains_key(&id) {
|
if !definitions.contains_key(&id) {
|
||||||
definitions.insert(id, field_schema);
|
definitions.insert(id, field_schema);
|
||||||
@ -1563,9 +1670,9 @@ impl SchemaTypeInfo {
|
|||||||
id,
|
id,
|
||||||
schema: _,
|
schema: _,
|
||||||
definitions: field_definitions,
|
definitions: field_definitions,
|
||||||
missing_definitions: field_missing_definitions,
|
dependencies: field_dependencies,
|
||||||
} = items_schema.to_definition();
|
} = items_schema.to_definition();
|
||||||
missing_definitions.extend(field_missing_definitions);
|
dependencies.extend(field_dependencies);
|
||||||
definitions.extend(field_definitions);
|
definitions.extend(field_definitions);
|
||||||
if let Some(id) = id {
|
if let Some(id) = id {
|
||||||
definitions.insert(id, items_schema);
|
definitions.insert(id, items_schema);
|
||||||
@ -1580,9 +1687,9 @@ impl SchemaTypeInfo {
|
|||||||
ty_info: TypeInformation::Type(Box::new(*generic.ty())),
|
ty_info: TypeInformation::Type(Box::new(*generic.ty())),
|
||||||
..(**schema_type_info).clone()
|
..(**schema_type_info).clone()
|
||||||
};
|
};
|
||||||
missing_definitions.push(generic.type_id());
|
if SchemaType::try_get_primitive_type_from_type_id(generic.type_id()).is_none() {
|
||||||
let definition = schema_optional.clone().to_definition();
|
dependencies.insert(generic.type_id());
|
||||||
definitions.extend(definition.definitions);
|
}
|
||||||
schema.ref_type = None;
|
schema.ref_type = None;
|
||||||
schema.schema_type = None;
|
schema.schema_type = None;
|
||||||
schema.one_of = vec![
|
schema.one_of = vec![
|
||||||
@ -1598,7 +1705,7 @@ impl SchemaTypeInfo {
|
|||||||
id,
|
id,
|
||||||
schema,
|
schema,
|
||||||
definitions,
|
definitions,
|
||||||
missing_definitions,
|
dependencies: dependencies.into_iter().collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1707,16 +1814,6 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builds a JSON schema variant from a value.
|
|
||||||
pub fn build_schema<'a, T>(value: &'a T) -> JsonSchemaVariant
|
|
||||||
where
|
|
||||||
T: 'static,
|
|
||||||
SchemaTypeInfo: From<&'a T>,
|
|
||||||
{
|
|
||||||
let schema: SchemaTypeInfo = value.into();
|
|
||||||
schema.to_definition().schema.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&UnnamedField> for SchemaFieldData {
|
impl From<&UnnamedField> for SchemaFieldData {
|
||||||
fn from(value: &UnnamedField) -> Self {
|
fn from(value: &UnnamedField) -> Self {
|
||||||
let attributes: AttributesInformation = value.custom_attributes().into();
|
let attributes: AttributesInformation = value.custom_attributes().into();
|
||||||
@ -1832,6 +1929,334 @@ where
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) trait TypeDefinitionBuilder {
|
||||||
|
/// Builds a JSON schema for a given type ID.
|
||||||
|
fn build_schema_for_type_id(
|
||||||
|
&self,
|
||||||
|
type_id: TypeId,
|
||||||
|
metadata: &SchemaTypesMetadata,
|
||||||
|
) -> Option<(Option<TypeReferenceId>, JsonSchemaBevyType)>;
|
||||||
|
/// Returns a set of type IDs that are dependencies of the given type ID.
|
||||||
|
fn get_type_dependencies(&self, type_id: TypeId) -> HashSet<TypeId>;
|
||||||
|
/// Builds a JSON schema for a given type ID with definitions.
|
||||||
|
fn build_schema_for_type_id_with_definitions(
|
||||||
|
&self,
|
||||||
|
type_id: TypeId,
|
||||||
|
metadata: &SchemaTypesMetadata,
|
||||||
|
) -> Option<JsonSchemaBevyType>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TypeDefinitionBuilder for TypeRegistry {
|
||||||
|
fn build_schema_for_type_id(
|
||||||
|
&self,
|
||||||
|
type_id: TypeId,
|
||||||
|
metadata: &SchemaTypesMetadata,
|
||||||
|
) -> Option<(Option<TypeReferenceId>, JsonSchemaBevyType)> {
|
||||||
|
let type_reg = self.get(type_id)?;
|
||||||
|
let type_info: TypeInformation = type_reg.into();
|
||||||
|
let schema_info = type_info.to_schema_type_info_with_metadata(metadata);
|
||||||
|
let mut id: Option<TypeReferenceId> = schema_info.ty_info.try_get_type_reference_id();
|
||||||
|
let mut definitions: HashMap<TypeReferenceId, SchemaTypeInfo> = HashMap::new();
|
||||||
|
if let Some(custom_schema) = &schema_info.ty_info.try_get_custom_schema() {
|
||||||
|
return Some((id, custom_schema.0.clone()));
|
||||||
|
}
|
||||||
|
let range = schema_info.get_range();
|
||||||
|
|
||||||
|
let (type_path, short_path, crate_name, module_path) =
|
||||||
|
if let Some(type_path_table) = schema_info.ty_info.try_get_type_path_table() {
|
||||||
|
(
|
||||||
|
type_path_table.path().into(),
|
||||||
|
type_path_table.short_path().into(),
|
||||||
|
type_path_table.crate_name().map(Into::into),
|
||||||
|
type_path_table.module_path().map(Into::into),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(Cow::default(), Cow::default(), None, None)
|
||||||
|
};
|
||||||
|
|
||||||
|
let not = if is_non_zero_number_type(schema_info.ty_info.type_id()) {
|
||||||
|
Some(Box::new(JsonSchemaBevyType {
|
||||||
|
const_value: Some(0.into()),
|
||||||
|
..default()
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut schema = JsonSchemaBevyType {
|
||||||
|
description: schema_info.ty_info.get_docs(),
|
||||||
|
not,
|
||||||
|
type_path,
|
||||||
|
short_path,
|
||||||
|
crate_name,
|
||||||
|
module_path,
|
||||||
|
kind: Some((&schema_info.ty_info).into()),
|
||||||
|
minimum: range.min.get_inclusive(),
|
||||||
|
maximum: range.max.get_inclusive(),
|
||||||
|
exclusive_minimum: range.min.get_exclusive(),
|
||||||
|
exclusive_maximum: range.max.get_exclusive(),
|
||||||
|
schema_type: (&schema_info).into(),
|
||||||
|
reflect_type_data: schema_info.reflect_type_data.clone().unwrap_or_default(),
|
||||||
|
..default()
|
||||||
|
};
|
||||||
|
match schema_info.internal_schema_type.clone() {
|
||||||
|
InternalSchemaType::Map { key, value } => {
|
||||||
|
let key = key.to_schema_type_info();
|
||||||
|
let value = value.to_schema_type_info();
|
||||||
|
if key.ty_info.try_get_primitive_type().is_some() {
|
||||||
|
if let Some(p) = key.ty_info.try_get_regex_for_type() {
|
||||||
|
schema.pattern_properties = [(p, value.to_ref_schema().into())].into();
|
||||||
|
schema.additional_properties = Some(JsonSchemaVariant::BoolValue(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
schema.value_type = Some(value.to_ref_schema().into());
|
||||||
|
schema.key_type = Some(key.to_ref_schema().into());
|
||||||
|
}
|
||||||
|
InternalSchemaType::Regular(_)
|
||||||
|
| InternalSchemaType::RegularType(_)
|
||||||
|
| InternalSchemaType::NoInfo => {}
|
||||||
|
InternalSchemaType::EnumHolder(variants) => {
|
||||||
|
let schema_fields: Vec<(Cow<'static, str>, SchemaDefinition)> = variants
|
||||||
|
.iter()
|
||||||
|
.map(|variant| {
|
||||||
|
(
|
||||||
|
variant.name().into(),
|
||||||
|
SchemaTypeInfo::from(variant).to_definition(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
schema.one_of = schema_fields
|
||||||
|
.iter()
|
||||||
|
.map(|(_, definition)| definition.schema.clone().into())
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
InternalSchemaType::EnumVariant(variant_info) => {
|
||||||
|
schema.kind = Some(SchemaKind::Value);
|
||||||
|
schema.schema_type = Some(SchemaTypeVariant::Single(SchemaType::Object));
|
||||||
|
let ty_info: TypeInformation = (&variant_info).into();
|
||||||
|
let field_data: Option<SchemaFieldData> = Some((&variant_info).into());
|
||||||
|
id = None;
|
||||||
|
match &variant_info {
|
||||||
|
VariantInfo::Struct(struct_variant_info) => {
|
||||||
|
let fields = get_fields_information(struct_variant_info.iter());
|
||||||
|
|
||||||
|
let schema_field = SchemaTypeInfo {
|
||||||
|
ty_info,
|
||||||
|
field_data,
|
||||||
|
internal_schema_type: InternalSchemaType::FieldsHolder(
|
||||||
|
FieldsInformation {
|
||||||
|
fields,
|
||||||
|
fields_type: FieldType::Named,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
reflect_type_data: None,
|
||||||
|
};
|
||||||
|
let definition = schema_field.to_definition();
|
||||||
|
|
||||||
|
schema.properties =
|
||||||
|
[(variant_info.name().into(), definition.schema.into())].into();
|
||||||
|
schema.required = vec![variant_info.name().into()];
|
||||||
|
}
|
||||||
|
VariantInfo::Tuple(tuple_variant_info) => {
|
||||||
|
let stored_fields = get_fields_information(tuple_variant_info.iter());
|
||||||
|
let schema_field = SchemaTypeInfo {
|
||||||
|
ty_info,
|
||||||
|
field_data: None,
|
||||||
|
internal_schema_type: InternalSchemaType::FieldsHolder(
|
||||||
|
FieldsInformation {
|
||||||
|
fields: stored_fields,
|
||||||
|
|
||||||
|
fields_type: FieldType::Unnamed,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
reflect_type_data: None,
|
||||||
|
};
|
||||||
|
let definition = schema_field.to_definition();
|
||||||
|
|
||||||
|
schema.properties =
|
||||||
|
[(variant_info.name().into(), definition.schema.into())].into();
|
||||||
|
schema.required = vec![variant_info.name().into()];
|
||||||
|
}
|
||||||
|
VariantInfo::Unit(unit_variant_info) => {
|
||||||
|
let internal_schema_type = (&ty_info).into();
|
||||||
|
let schema_field = SchemaTypeInfo {
|
||||||
|
ty_info,
|
||||||
|
field_data,
|
||||||
|
internal_schema_type,
|
||||||
|
reflect_type_data: None,
|
||||||
|
};
|
||||||
|
return Some((
|
||||||
|
None,
|
||||||
|
JsonSchemaBevyType {
|
||||||
|
const_value: Some(unit_variant_info.name().to_string().into()),
|
||||||
|
schema_type: Some(SchemaTypeVariant::Single(SchemaType::String)),
|
||||||
|
description: schema_field.get_docs(),
|
||||||
|
kind: Some(SchemaKind::Value),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InternalSchemaType::FieldsHolder(fields) => match fields.fields_type {
|
||||||
|
FieldType::Named => {
|
||||||
|
schema.additional_properties = Some(JsonSchemaVariant::BoolValue(false));
|
||||||
|
schema.schema_type = Some(SchemaTypeVariant::Single(SchemaType::Object));
|
||||||
|
let schema_fields: Vec<(Cow<'static, str>, SchemaTypeInfo)> = fields
|
||||||
|
.fields
|
||||||
|
.iter()
|
||||||
|
.map(|field| (field.field.to_name(), SchemaTypeInfo::from(field)))
|
||||||
|
.collect();
|
||||||
|
schema.properties = schema_fields
|
||||||
|
.iter()
|
||||||
|
.map(|(name, schema)| (name.clone(), schema.to_ref_schema().into()))
|
||||||
|
.collect();
|
||||||
|
schema.required = fields
|
||||||
|
.fields
|
||||||
|
.iter()
|
||||||
|
.map(|field| field.field.to_name())
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
FieldType::Unnamed if fields.fields.len() == 1 => {
|
||||||
|
let field_schema = SchemaTypeInfo::from(&fields.fields[0]);
|
||||||
|
|
||||||
|
let SchemaDefinition {
|
||||||
|
id,
|
||||||
|
schema: new_schema_type,
|
||||||
|
definitions: _,
|
||||||
|
dependencies: _,
|
||||||
|
} = field_schema.to_definition();
|
||||||
|
if let Some(id) = id {
|
||||||
|
definitions.insert(id.clone(), field_schema);
|
||||||
|
schema.ref_type = Some(TypeReferencePath::definition(id));
|
||||||
|
} else {
|
||||||
|
let (type_path, short_path, crate_name, module_path) =
|
||||||
|
if let Some(type_path_table) =
|
||||||
|
schema_info.ty_info.try_get_type_path_table()
|
||||||
|
{
|
||||||
|
(
|
||||||
|
type_path_table.path().into(),
|
||||||
|
type_path_table.short_path().into(),
|
||||||
|
type_path_table.crate_name().map(Into::into),
|
||||||
|
type_path_table.module_path().map(Into::into),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(Cow::default(), Cow::default(), None, None)
|
||||||
|
};
|
||||||
|
schema = JsonSchemaBevyType {
|
||||||
|
short_path,
|
||||||
|
type_path,
|
||||||
|
module_path,
|
||||||
|
crate_name,
|
||||||
|
kind: Some(SchemaKind::TupleStruct),
|
||||||
|
schema_type: (&schema_info).into(),
|
||||||
|
description: schema_info.get_docs(),
|
||||||
|
..new_schema_type
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s => {
|
||||||
|
let schema_fields: Vec<SchemaTypeInfo> =
|
||||||
|
fields.fields.iter().map(SchemaTypeInfo::from).collect();
|
||||||
|
schema.prefix_items = schema_fields
|
||||||
|
.iter()
|
||||||
|
.map(|field| {
|
||||||
|
let field_schema = if s == FieldType::ForceUnnamed
|
||||||
|
&& field
|
||||||
|
.field_data
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|f| f.description.is_none())
|
||||||
|
{
|
||||||
|
if let Some(field_data) = field.field_data.as_ref() {
|
||||||
|
let description = field_data.name.clone();
|
||||||
|
SchemaTypeInfo {
|
||||||
|
field_data: Some(SchemaFieldData {
|
||||||
|
description,
|
||||||
|
..field_data.clone()
|
||||||
|
}),
|
||||||
|
..field.clone()
|
||||||
|
}
|
||||||
|
.to_ref_schema()
|
||||||
|
} else {
|
||||||
|
field.to_ref_schema()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
field.to_ref_schema()
|
||||||
|
};
|
||||||
|
|
||||||
|
field_schema.into()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
schema.min_items = Some(fields.fields.len() as u64);
|
||||||
|
schema.max_items = Some(fields.fields.len() as u64);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
InternalSchemaType::Array {
|
||||||
|
element_ty,
|
||||||
|
min_size,
|
||||||
|
max_size,
|
||||||
|
} => {
|
||||||
|
id = None;
|
||||||
|
let items_schema = element_ty.to_schema_type_info();
|
||||||
|
schema.items = Some(items_schema.to_ref_schema().into());
|
||||||
|
schema.min_items = min_size;
|
||||||
|
schema.max_items = max_size;
|
||||||
|
}
|
||||||
|
InternalSchemaType::Optional {
|
||||||
|
generic,
|
||||||
|
ref schema_type_info,
|
||||||
|
} => {
|
||||||
|
let schema_optional = SchemaTypeInfo {
|
||||||
|
ty_info: TypeInformation::Type(Box::new(*generic.ty())),
|
||||||
|
..(**schema_type_info).clone()
|
||||||
|
};
|
||||||
|
schema.ref_type = None;
|
||||||
|
schema.schema_type = None;
|
||||||
|
schema.one_of = vec![
|
||||||
|
Box::new(JsonSchemaBevyType {
|
||||||
|
schema_type: Some(SchemaTypeVariant::Single(SchemaType::Null)),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
Box::new(schema_optional.to_ref_schema()),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some((id, schema))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_type_dependencies(&self, type_id: TypeId) -> HashSet<TypeId> {
|
||||||
|
let Some(type_reg) = self.get(type_id) else {
|
||||||
|
return HashSet::new();
|
||||||
|
};
|
||||||
|
let internal_schema_type: InternalSchemaType = (&TypeInformation::from(type_reg)).into();
|
||||||
|
|
||||||
|
internal_schema_type.get_dependencies(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_schema_for_type_id_with_definitions(
|
||||||
|
&self,
|
||||||
|
type_id: TypeId,
|
||||||
|
metadata: &SchemaTypesMetadata,
|
||||||
|
) -> Option<JsonSchemaBevyType> {
|
||||||
|
let Some((_, mut schema)) = self.build_schema_for_type_id(type_id, metadata) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let dependencies = self.get_type_dependencies(type_id);
|
||||||
|
eprintln!("{} -> {:#?}", schema.type_path, dependencies.len());
|
||||||
|
schema.definitions = dependencies
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|id| {
|
||||||
|
let result = self.build_schema_for_type_id(id, metadata);
|
||||||
|
let Some((Some(schema_id), schema)) = result else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
Some((schema_id, Box::new(schema)))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Some(schema)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(super) mod tests {
|
pub(super) mod tests {
|
||||||
use bevy_ecs::{component::Component, name::Name, reflect::AppTypeRegistry};
|
use bevy_ecs::{component::Component, name::Name, reflect::AppTypeRegistry};
|
||||||
@ -2173,18 +2598,28 @@ pub(super) mod tests {
|
|||||||
pub list: Vec<BaseStruct>,
|
pub list: Vec<BaseStruct>,
|
||||||
pub optional: Option<BaseStruct>,
|
pub optional: Option<BaseStruct>,
|
||||||
}
|
}
|
||||||
let type_info = TypeInformation::from(&ArrayComponent::get_type_registration())
|
|
||||||
.to_schema_type_info()
|
let atr = AppTypeRegistry::default();
|
||||||
.to_definition();
|
{
|
||||||
let type_info_second =
|
let mut register = atr.write();
|
||||||
TypeInformation::from(&ArrayComponentWithMoreVariants::get_type_registration())
|
register.register::<ArrayComponent>();
|
||||||
.to_schema_type_info()
|
register.register::<ArrayComponentWithMoreVariants>();
|
||||||
.to_definition();
|
}
|
||||||
assert_eq!(
|
let types = atr.read();
|
||||||
type_info.definitions.len(),
|
let schema = types
|
||||||
type_info_second.definitions.len()
|
.build_schema_for_type_id_with_definitions(
|
||||||
);
|
TypeId::of::<ArrayComponent>(),
|
||||||
validate::<ArrayComponentWithMoreVariants>(type_info_second.into(), &[], &[], &[]);
|
&Default::default(),
|
||||||
|
)
|
||||||
|
.expect("");
|
||||||
|
let schema_second = types
|
||||||
|
.build_schema_for_type_id_with_definitions(
|
||||||
|
TypeId::of::<ArrayComponentWithMoreVariants>(),
|
||||||
|
&Default::default(),
|
||||||
|
)
|
||||||
|
.expect("");
|
||||||
|
assert_eq!(schema.definitions.len(), schema_second.definitions.len());
|
||||||
|
validate::<ArrayComponentWithMoreVariants>(schema_second, &[], &[], &[]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -2235,12 +2670,29 @@ pub(super) mod tests {
|
|||||||
pub second: SecondStruct,
|
pub second: SecondStruct,
|
||||||
pub third: ThirdStruct,
|
pub third: ThirdStruct,
|
||||||
}
|
}
|
||||||
let type_info =
|
|
||||||
TypeInformation::from(&NestedStruct::get_type_registration()).to_schema_type_info();
|
let atr = AppTypeRegistry::default();
|
||||||
let s: JsonSchemaBevyType = type_info.to_definition().into();
|
{
|
||||||
assert_eq!(s.definitions.len(), 3);
|
let mut register = atr.write();
|
||||||
|
register.register::<NestedStruct>();
|
||||||
|
}
|
||||||
|
let types = atr.read();
|
||||||
|
let schema = types
|
||||||
|
.build_schema_for_type_id_with_definitions(
|
||||||
|
TypeId::of::<NestedStruct>(),
|
||||||
|
&Default::default(),
|
||||||
|
)
|
||||||
|
.expect("");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
schema.definitions.len(),
|
||||||
|
3,
|
||||||
|
"Expected 3 definitions, but got {}, schema: {}",
|
||||||
|
schema.definitions.len(),
|
||||||
|
serde_json::to_string_pretty(&schema).unwrap_or_default()
|
||||||
|
);
|
||||||
validate::<NestedStruct>(
|
validate::<NestedStruct>(
|
||||||
s,
|
schema,
|
||||||
&[NestedStruct {
|
&[NestedStruct {
|
||||||
other: OtherStruct { field: "s".into() },
|
other: OtherStruct { field: "s".into() },
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
Loading…
Reference in New Issue
Block a user