Start populating schema definitons field

This commit is contained in:
Piotr Siuszko 2025-07-03 09:46:10 +02:00
parent 27366c9f4f
commit 5ea21b4705
2 changed files with 242 additions and 209 deletions

View File

@ -11,7 +11,7 @@ use serde::{Deserialize, Serialize};
use serde_json::Value; use serde_json::Value;
use crate::schemas::{ use crate::schemas::{
reflect_info::{SchemaNumber, TypeReferenceId, TypeReferencePath}, reflect_info::{SchemaNumber, TypeInformation, TypeReferenceId, TypeReferencePath},
ReflectJsonSchema, SchemaTypesMetadata, ReflectJsonSchema, SchemaTypesMetadata,
}; };
@ -82,13 +82,12 @@ impl TryFrom<(&TypeRegistration, &SchemaTypesMetadata)> for JsonSchemaBevyType {
if let Some(s) = reg.data::<ReflectJsonSchema>() { if let Some(s) = reg.data::<ReflectJsonSchema>() {
return Ok(s.0.clone()); return Ok(s.0.clone());
} }
let base_schema = super::reflect_info::build_schema(reg); let (_, mut typed_schema) = TypeInformation::from(reg)
.to_schema_type_info()
.to_definition();
let JsonSchemaVariant::Schema(mut typed_schema) = base_schema else {
return Err(InvalidJsonSchema::InvalidType);
};
typed_schema.reflect_type_data = metadata.get_registered_reflect_types(reg); typed_schema.reflect_type_data = metadata.get_registered_reflect_types(reg);
Ok(*typed_schema) Ok(typed_schema)
} }
} }
@ -142,7 +141,8 @@ pub struct JsonSchemaBevyType {
#[serde(skip_serializing_if = "Vec::is_empty", default)] #[serde(skip_serializing_if = "Vec::is_empty", default)]
pub reflect_type_data: Vec<Cow<'static, str>>, pub reflect_type_data: Vec<Cow<'static, str>>,
/// Bevy specific field, [`TypeInfo`] type mapping. /// Bevy specific field, [`TypeInfo`] type mapping.
pub kind: SchemaKind, #[serde(skip_serializing_if = "Option::is_none", default)]
pub kind: Option<SchemaKind>,
/// JSON Schema specific field. /// JSON Schema specific field.
/// This keyword is used to reference a constant value. /// This keyword is used to reference a constant value.
#[serde(rename = "const")] #[serde(rename = "const")]
@ -245,7 +245,7 @@ pub struct JsonSchemaBevyType {
#[serde(skip_serializing_if = "HashMap::is_empty", default)] #[serde(skip_serializing_if = "HashMap::is_empty", default)]
#[reflect(ignore)] #[reflect(ignore)]
#[serde(rename = "$defs")] #[serde(rename = "$defs")]
pub definitions: HashMap<TypeReferenceId, Box<JsonSchemaVariant>>, pub definitions: HashMap<TypeReferenceId, JsonSchemaVariant>,
} }
/// 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.
@ -267,6 +267,12 @@ pub enum JsonSchemaVariant {
Schema(#[reflect(ignore)] Box<JsonSchemaBevyType>), Schema(#[reflect(ignore)] Box<JsonSchemaBevyType>),
} }
impl From<JsonSchemaBevyType> for JsonSchemaVariant {
fn from(value: JsonSchemaBevyType) -> Self {
JsonSchemaVariant::Schema(Box::new(value))
}
}
/// Kind of json schema, maps [`TypeInfo`] type /// Kind of json schema, maps [`TypeInfo`] type
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default, Reflect)] #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default, Reflect)]
pub enum SchemaKind { pub enum SchemaKind {
@ -372,9 +378,7 @@ impl From<TypeId> for SchemaType {
|| value.eq(&TypeId::of::<u64>()) || value.eq(&TypeId::of::<u64>())
|| value.eq(&TypeId::of::<u128>()) || value.eq(&TypeId::of::<u128>())
|| value.eq(&TypeId::of::<usize>()) || value.eq(&TypeId::of::<usize>())
{ || value.eq(&TypeId::of::<i8>())
Self::Integer
} else if value.eq(&TypeId::of::<i8>())
|| value.eq(&TypeId::of::<i16>()) || value.eq(&TypeId::of::<i16>())
|| value.eq(&TypeId::of::<i32>()) || value.eq(&TypeId::of::<i32>())
|| value.eq(&TypeId::of::<i64>()) || value.eq(&TypeId::of::<i64>())
@ -679,12 +683,9 @@ mod tests {
"additionalProperties": false, "additionalProperties": false,
"properties": { "properties": {
"a": { "a": {
"kind": "Value",
"maximum": 65535, "maximum": 65535,
"minimum": 0, "minimum": 0,
"type": "integer", "type": "integer"
"shortPath": "u16",
"typePath": "u16",
}, },
}, },
"required": [ "required": [

View File

@ -99,6 +99,12 @@ impl From<&Type> for TypeReferenceId {
TypeReferenceId(t.path().replace("::", "-").into()) TypeReferenceId(t.path().replace("::", "-").into())
} }
} }
impl From<&TypePathTable> for TypeReferenceId {
fn from(t: &TypePathTable) -> Self {
TypeReferenceId(t.path().replace("::", "-").into())
}
}
impl From<&str> for TypeReferenceId { impl From<&str> for TypeReferenceId {
fn from(t: &str) -> Self { fn from(t: &str) -> Self {
TypeReferenceId(t.replace("::", "-").into()) TypeReferenceId(t.replace("::", "-").into())
@ -126,14 +132,18 @@ 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(#[deref] pub Vec<FieldInformation>, FieldType); pub struct FieldsInformation {
/// Fields information.
#[deref]
fields: Vec<FieldInformation>,
/// Field type information.
fields_type: FieldType,
}
impl From<&TypeInformation> for Option<FieldsInformation> { impl From<&TypeInformation> for Option<FieldsInformation> {
fn from(value: &TypeInformation) -> Self { fn from(value: &TypeInformation) -> Self {
let Some(info) = value.try_get_type_info() else { let info = value.try_get_type_info()?;
return None; let (fields, fields_type) = match info {
};
let (array, field_type) = match info {
TypeInfo::Struct(struct_info) => ( TypeInfo::Struct(struct_info) => (
get_fields_information(struct_info.iter()), get_fields_information(struct_info.iter()),
if value.is_forced_as_array() { if value.is_forced_as_array() {
@ -150,14 +160,17 @@ impl From<&TypeInformation> for Option<FieldsInformation> {
get_fields_information(tuple_info.iter()), get_fields_information(tuple_info.iter()),
FieldType::Unnamed, FieldType::Unnamed,
), ),
TypeInfo::List(_) => return None, TypeInfo::List(_)
TypeInfo::Array(_) => return None, | TypeInfo::Array(_)
TypeInfo::Map(_) => return None, | TypeInfo::Map(_)
TypeInfo::Set(_) => return None, | TypeInfo::Set(_)
TypeInfo::Enum(_) => return None, | TypeInfo::Enum(_)
TypeInfo::Opaque(_) => return None, | TypeInfo::Opaque(_) => return None,
}; };
Some(FieldsInformation(array, field_type)) Some(FieldsInformation {
fields,
fields_type,
})
} }
} }
@ -225,6 +238,32 @@ pub enum TypeInformation {
} }
impl TypeInformation { impl TypeInformation {
/// Returns the reference type if the stored type is a reference one.
pub fn get_reference_type(&self) -> Option<TypeReferencePath> {
if self.is_primitive_type() {
None
} else {
self.try_get_type_path_table()
.map(TypeReferencePath::definition)
}
}
/// Returns true if the stored type is a primitive one.
pub fn is_primitive_type(&self) -> bool {
self.try_get_primitive_type().is_some()
}
/// Returns the primitive type if the stored type is a primitive one.
pub fn try_get_primitive_type(&self) -> Option<SchemaType> {
SchemaType::try_get_primitive_type_from_type_id(self.type_id())
}
/// Converts the type information into a schema type information.
pub fn to_schema_type_info(self) -> SchemaTypeInfo {
let stored_fields = (&self).into();
SchemaTypeInfo {
ty_info: self,
field_data: None,
stored_fields,
}
}
/// Returns the documentation of the type. /// Returns the documentation of the type.
#[cfg(feature = "documentation")] #[cfg(feature = "documentation")]
pub fn get_docs(&self) -> Option<Cow<'static, str>> { pub fn get_docs(&self) -> Option<Cow<'static, str>> {
@ -234,8 +273,7 @@ impl TypeInformation {
} }
TypeInformation::TypeInfo(type_info) => type_info.docs(), TypeInformation::TypeInfo(type_info) => type_info.docs(),
TypeInformation::VariantInfo(variant_info) => variant_info.docs(), TypeInformation::VariantInfo(variant_info) => variant_info.docs(),
TypeInformation::Type(_) => None, _ => None,
TypeInformation::TypeId(_) => None,
}; };
docs.map(|docs| docs.trim().replace("\n", "").into()) docs.map(|docs| docs.trim().replace("\n", "").into())
@ -256,15 +294,23 @@ 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 {
TypeInformation::Type(_) => false,
TypeInformation::TypeId(_) | TypeInformation::VariantInfo(_) => false,
TypeInformation::TypeInfo(_) => false,
TypeInformation::TypeRegistration(type_registration) => type_registration TypeInformation::TypeRegistration(type_registration) => type_registration
.data::<ReflectJsonSchemaForceAsArray>() .data::<ReflectJsonSchemaForceAsArray>()
.is_some(), .is_some(),
_ => false,
} }
} }
@ -321,9 +367,7 @@ impl TypeInformation {
} }
/// Try to get the optional type information from the enum information. /// Try to get the optional type information from the enum information.
pub fn try_get_optional_from_info(enum_info: &EnumInfo) -> Option<&GenericInfo> { pub fn try_get_optional_from_info(enum_info: &EnumInfo) -> Option<&GenericInfo> {
let Some(generic) = enum_info.generics().first() else { let generic = enum_info.generics().first()?;
return None;
};
if enum_info.variant_len() != 2 if enum_info.variant_len() != 2
|| !enum_info.contains_variant("Some") || !enum_info.contains_variant("Some")
|| !enum_info.contains_variant("None") || !enum_info.contains_variant("None")
@ -340,6 +384,7 @@ impl Default for TypeInformation {
Self::TypeId(TypeId::of::<()>()) Self::TypeId(TypeId::of::<()>())
} }
} }
impl From<&TypeInformation> for SchemaKind { impl From<&TypeInformation> for SchemaKind {
fn from(value: &TypeInformation) -> Self { fn from(value: &TypeInformation) -> Self {
let type_info = value.try_get_type_info(); let type_info = value.try_get_type_info();
@ -374,11 +419,6 @@ impl From<&TypeInformation> for SchemaKind {
} }
} }
} }
impl From<&TypePathTable> for TypeReferenceId {
fn from(value: &TypePathTable) -> Self {
value.path().into()
}
}
impl TryFrom<&TypeInformation> for TypeReferenceId { impl TryFrom<&TypeInformation> for TypeReferenceId {
type Error = (); type Error = ();
@ -803,10 +843,8 @@ pub enum InternalSchemaType {
EnumHolder(Vec<VariantInfo>), EnumHolder(Vec<VariantInfo>),
/// Represents a single enum variant. /// Represents a single enum variant.
EnumVariant(VariantInfo), EnumVariant(VariantInfo),
/// Holds named fields for struct types. /// Holds named fields for struct, tuple, and tuple struct types.
NamedFieldsHolder(FieldsInformation), FieldsHolder(FieldsInformation),
/// Holds unnamed fields for tuple and tuple struct types.
UnnamedFieldsHolder(FieldsInformation),
/// Represents an Optional type (e.g., Option<T>). /// Represents an Optional type (e.g., Option<T>).
Optional { Optional {
/// Generic information about the wrapped type T in Option<T>. /// Generic information about the wrapped type T in Option<T>.
@ -834,24 +872,26 @@ impl From<&TypeInformation> for InternalSchemaType {
if let Some(type_info) = value.try_get_type_info() { if let Some(type_info) = value.try_get_type_info() {
match type_info { match type_info {
TypeInfo::Struct(struct_info) => { TypeInfo::Struct(struct_info) => {
if value.is_forced_as_array() { let fields_type = if value.is_forced_as_array() {
InternalSchemaType::UnnamedFieldsHolder(FieldsInformation( FieldType::Unnamed
get_fields_information(struct_info.iter()),
FieldType::Unnamed,
))
} else { } else {
InternalSchemaType::NamedFieldsHolder(FieldsInformation( FieldType::Named
get_fields_information(struct_info.iter()), };
FieldType::Named, InternalSchemaType::FieldsHolder(FieldsInformation {
)) fields: get_fields_information(struct_info.iter()),
} fields_type,
})
} }
TypeInfo::TupleStruct(info) => InternalSchemaType::UnnamedFieldsHolder( TypeInfo::TupleStruct(info) => {
FieldsInformation(get_fields_information(info.iter()), FieldType::Unnamed), InternalSchemaType::FieldsHolder(FieldsInformation {
), fields: get_fields_information(info.iter()),
TypeInfo::Tuple(info) => InternalSchemaType::UnnamedFieldsHolder( fields_type: FieldType::Unnamed,
FieldsInformation(get_fields_information(info.iter()), FieldType::Unnamed), })
), }
TypeInfo::Tuple(info) => InternalSchemaType::FieldsHolder(FieldsInformation {
fields: get_fields_information(info.iter()),
fields_type: FieldType::Unnamed,
}),
TypeInfo::Enum(enum_info) => { TypeInfo::Enum(enum_info) => {
match TypeInformation::try_get_optional_from_info(enum_info) { match TypeInformation::try_get_optional_from_info(enum_info) {
Some(e) => InternalSchemaType::Optional { Some(e) => InternalSchemaType::Optional {
@ -898,12 +938,10 @@ impl From<&TypeInformation> for InternalSchemaType {
impl From<&SchemaTypeInfo> for InternalSchemaType { impl From<&SchemaTypeInfo> for InternalSchemaType {
fn from(value: &SchemaTypeInfo) -> Self { fn from(value: &SchemaTypeInfo) -> Self {
if let Some(s) = &value.stored_fields { if let Some(s) = &value.stored_fields {
if s.1 == FieldType::Named { InternalSchemaType::FieldsHolder(s.clone())
return InternalSchemaType::NamedFieldsHolder(s.clone()); } else {
} (&value.ty_info).into()
return InternalSchemaType::UnnamedFieldsHolder(s.clone());
} }
(&value.ty_info).into()
} }
} }
@ -911,7 +949,6 @@ impl From<&InternalSchemaType> for Option<SchemaTypeVariant> {
fn from(value: &InternalSchemaType) -> Self { fn from(value: &InternalSchemaType) -> Self {
match value { match value {
InternalSchemaType::Array { .. } => Some(SchemaTypeVariant::Single(SchemaType::Array)), InternalSchemaType::Array { .. } => Some(SchemaTypeVariant::Single(SchemaType::Array)),
InternalSchemaType::EnumHolder(_) => None,
InternalSchemaType::EnumVariant(variant) => match variant { InternalSchemaType::EnumVariant(variant) => match variant {
VariantInfo::Tuple(t) => { VariantInfo::Tuple(t) => {
if t.field_len() == 1 { if t.field_len() == 1 {
@ -926,15 +963,16 @@ impl From<&InternalSchemaType> for Option<SchemaTypeVariant> {
VariantInfo::Struct(_) => Some(SchemaTypeVariant::Single(SchemaType::Object)), VariantInfo::Struct(_) => Some(SchemaTypeVariant::Single(SchemaType::Object)),
VariantInfo::Unit(_) => Some(SchemaTypeVariant::Single(SchemaType::String)), VariantInfo::Unit(_) => Some(SchemaTypeVariant::Single(SchemaType::String)),
}, },
InternalSchemaType::NamedFieldsHolder { .. } => { InternalSchemaType::FieldsHolder(fields) => {
Some(SchemaTypeVariant::Single(SchemaType::Object)) if fields.fields_type == FieldType::Unnamed {
} if fields.fields.len() == 1 {
InternalSchemaType::UnnamedFieldsHolder(unnamed_fields) => { let schema: InternalSchemaType = (&fields.fields[0].type_info).into();
if unnamed_fields.len() == 1 { (&schema).into()
let schema: InternalSchemaType = (&unnamed_fields[0].type_info).into(); } else {
(&schema).into() Some(SchemaTypeVariant::Single(SchemaType::Array))
}
} else { } else {
Some(SchemaTypeVariant::Single(SchemaType::Array)) Some(SchemaTypeVariant::Single(SchemaType::Object))
} }
} }
InternalSchemaType::Optional { InternalSchemaType::Optional {
@ -954,7 +992,7 @@ impl From<&InternalSchemaType> for Option<SchemaTypeVariant> {
Some(SchemaTypeVariant::Single((*type_id).into())) Some(SchemaTypeVariant::Single((*type_id).into()))
} }
InternalSchemaType::RegularType(ty) => Some(SchemaTypeVariant::Single(ty.id().into())), InternalSchemaType::RegularType(ty) => Some(SchemaTypeVariant::Single(ty.id().into())),
InternalSchemaType::NoInfo => None, InternalSchemaType::NoInfo | InternalSchemaType::EnumHolder(_) => None,
} }
} }
} }
@ -1024,26 +1062,28 @@ impl SchemaTypeInfo {
/// Converts the schema type information into a JSON schema reference. /// Converts the schema type information into a JSON schema reference.
pub fn to_ref_schema(&self) -> JsonSchemaBevyType { pub fn to_ref_schema(&self) -> JsonSchemaBevyType {
let range = self.get_range(); let range = self.get_range();
let description = self.get_docs();
let (ref_type, schema_type) = (self.ty_info.get_reference_type(), self.into());
JsonSchemaBevyType { JsonSchemaBevyType {
description: self.get_docs(), description,
minimum: range.min.get_inclusive(), minimum: range.min.get_inclusive(),
maximum: range.max.get_inclusive(), maximum: range.max.get_inclusive(),
exclusive_minimum: range.min.get_exclusive(), exclusive_minimum: range.min.get_exclusive(),
exclusive_maximum: range.max.get_exclusive(), exclusive_maximum: range.max.get_exclusive(),
ref_type: self ref_type,
.ty_info kind: None,
.try_get_type_path_table() schema_type,
.map(TypeReferencePath::definition),
..default() ..default()
} }
} }
}
impl From<SchemaTypeInfo> for JsonSchemaVariant { /// Converts the schema type information into a JSON schema definition.
fn from(val: SchemaTypeInfo) -> Self { pub fn to_definition(&self) -> (Option<TypeReferenceId>, JsonSchemaBevyType) {
let schema_type: Option<SchemaTypeVariant> = (&val).into(); let id: Option<TypeReferenceId> = self.ty_info.try_get_type_path_table().map(Into::into);
let range = self.ty_info.get_range();
let (type_path, short_path, crate_name, module_path) = let (type_path, short_path, crate_name, module_path) =
if let Some(type_path_table) = val.ty_info.try_get_type_path_table() { if let Some(type_path_table) = self.ty_info.try_get_type_path_table() {
( (
type_path_table.path().into(), type_path_table.path().into(),
type_path_table.short_path().into(), type_path_table.short_path().into(),
@ -1053,24 +1093,21 @@ impl From<SchemaTypeInfo> for JsonSchemaVariant {
} else { } else {
(Cow::default(), Cow::default(), None, None) (Cow::default(), Cow::default(), None, None)
}; };
let kind: SchemaKind = (&val.ty_info).into();
let range = val.get_range();
let mut schema = JsonSchemaBevyType { let mut schema = JsonSchemaBevyType {
schema_type: schema_type.clone(), description: self.ty_info.get_docs(),
kind,
description: val.get_docs(),
type_path, type_path,
short_path, short_path,
crate_name, crate_name,
module_path, module_path,
kind: Some((&self.ty_info).into()),
minimum: range.min.get_inclusive(), minimum: range.min.get_inclusive(),
maximum: range.max.get_inclusive(), maximum: range.max.get_inclusive(),
exclusive_minimum: range.min.get_exclusive(), exclusive_minimum: range.min.get_exclusive(),
exclusive_maximum: range.max.get_exclusive(), exclusive_maximum: range.max.get_exclusive(),
..Default::default() schema_type: self.into(),
..default()
}; };
let internal_type: InternalSchemaType = (&val).into(); let internal_type: InternalSchemaType = (self).into();
match internal_type { match internal_type {
InternalSchemaType::Map { key, value } => { InternalSchemaType::Map { key, value } => {
let key: SchemaTypeInfo = SchemaTypeInfo { let key: SchemaTypeInfo = SchemaTypeInfo {
@ -1083,9 +1120,9 @@ impl From<SchemaTypeInfo> for JsonSchemaVariant {
field_data: None, field_data: None,
stored_fields: None, stored_fields: None,
}; };
schema.additional_properties = Some(key.clone().into()); schema.additional_properties = Some(key.clone().to_definition().1.into());
schema.value_type = Some(value.into()); schema.value_type = Some(value.to_definition().1.into());
schema.key_type = Some(key.into()); schema.key_type = Some(key.to_definition().1.into());
} }
InternalSchemaType::Regular(_) InternalSchemaType::Regular(_)
| InternalSchemaType::RegularType(_) | InternalSchemaType::RegularType(_)
@ -1094,23 +1131,29 @@ impl From<SchemaTypeInfo> for JsonSchemaVariant {
schema.one_of = variants.iter().map(build_schema).collect(); schema.one_of = variants.iter().map(build_schema).collect();
} }
InternalSchemaType::EnumVariant(variant_info) => { InternalSchemaType::EnumVariant(variant_info) => {
schema.kind = SchemaKind::Value; schema.kind = Some(SchemaKind::Value);
schema.schema_type = Some(SchemaTypeVariant::Single(SchemaType::Object)); schema.schema_type = Some(SchemaTypeVariant::Single(SchemaType::Object));
let ty_info: TypeInformation = (&variant_info).into(); let ty_info: TypeInformation = (&variant_info).into();
let field_data: Option<SchemaFieldData> = Some((&variant_info).into()); let field_data: Option<SchemaFieldData> = Some((&variant_info).into());
match &variant_info { match &variant_info {
VariantInfo::Struct(struct_variant_info) => { VariantInfo::Struct(struct_variant_info) => {
let stored_fields = get_fields_information(struct_variant_info.iter()); let fields = get_fields_information(struct_variant_info.iter());
let schema_field = SchemaTypeInfo { let schema_field = SchemaTypeInfo {
ty_info, ty_info,
field_data, field_data,
stored_fields: Some(FieldsInformation(stored_fields, FieldType::Named)), stored_fields: Some(FieldsInformation {
fields,
fields_type: FieldType::Named,
}),
}; };
schema.properties = schema.properties = [(
[(variant_info.name().into(), schema_field.into())].into(); variant_info.name().into(),
schema_field.to_definition().1.into(),
)]
.into();
schema.required = vec![variant_info.name().into()]; schema.required = vec![variant_info.name().into()];
} }
VariantInfo::Tuple(tuple_variant_info) => { VariantInfo::Tuple(tuple_variant_info) => {
@ -1118,13 +1161,18 @@ impl From<SchemaTypeInfo> for JsonSchemaVariant {
let schema_field = SchemaTypeInfo { let schema_field = SchemaTypeInfo {
ty_info, ty_info,
field_data: None, field_data: None,
stored_fields: Some(FieldsInformation( stored_fields: Some(FieldsInformation {
stored_fields, fields: stored_fields,
FieldType::Unnamed,
)), fields_type: FieldType::Unnamed,
}),
}; };
schema.properties =
[(variant_info.name().into(), schema_field.into())].into(); schema.properties = [(
variant_info.name().into(),
schema_field.to_definition().1.into(),
)]
.into();
schema.required = vec![variant_info.name().into()]; schema.required = vec![variant_info.name().into()];
} }
VariantInfo::Unit(unit_variant_info) => { VariantInfo::Unit(unit_variant_info) => {
@ -1133,48 +1181,66 @@ impl From<SchemaTypeInfo> for JsonSchemaVariant {
field_data, field_data,
stored_fields: None, stored_fields: None,
}; };
return JsonSchemaVariant::Schema(Box::new(JsonSchemaBevyType { return (
const_value: Some(unit_variant_info.name().to_string().into()), None,
schema_type: Some(SchemaTypeVariant::Single(SchemaType::String)), JsonSchemaBevyType {
description: schema_field.get_docs(), const_value: Some(unit_variant_info.name().to_string().into()),
kind: SchemaKind::Value, schema_type: Some(SchemaTypeVariant::Single(SchemaType::String)),
..Default::default() description: schema_field.get_docs(),
})); kind: Some(SchemaKind::Value),
..Default::default()
},
);
} }
} }
} }
InternalSchemaType::NamedFieldsHolder(named_fields) => { InternalSchemaType::FieldsHolder(fields) => match fields.fields_type {
schema.additional_properties = Some(JsonSchemaVariant::BoolValue(false)); FieldType::Named => {
schema.schema_type = Some(SchemaTypeVariant::Single(SchemaType::Object)); schema.additional_properties = Some(JsonSchemaVariant::BoolValue(false));
schema.schema_type = Some(SchemaTypeVariant::Single(SchemaType::Object));
schema.properties = named_fields let schema_fields: Vec<(Cow<'static, str>, SchemaTypeInfo)> = fields
.iter() .fields
.map(|field| (field.field.to_name(), SchemaTypeInfo::from(field).into()))
.collect();
schema.required = named_fields
.iter()
.map(|field| field.field.to_name())
.collect();
}
InternalSchemaType::UnnamedFieldsHolder(unnamed_fields) => {
if unnamed_fields.len() == 1 {
let new_schema = SchemaTypeInfo::from(&unnamed_fields[0]).into();
if let JsonSchemaVariant::Schema(new_schema_type) = new_schema {
schema = *new_schema_type;
schema.schema_type = schema_type.clone();
schema.description = val.get_docs();
} else {
return new_schema;
}
} else {
schema.prefix_items = unnamed_fields
.iter() .iter()
.map(|field| SchemaTypeInfo::from(field).into()) .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();
for (_, field_schema) in schema_fields {
if field_schema.ty_info.is_primitive_type() {
continue;
}
let (id, definition) = field_schema.to_definition();
let Some(id) = id else { continue };
if !schema.definitions.contains_key(&id) {
schema.definitions.insert(id, definition.into());
}
}
schema.required = fields
.fields
.iter()
.map(|field| field.field.to_name())
.collect(); .collect();
schema.min_items = Some(unnamed_fields.len() as u64);
schema.max_items = Some(unnamed_fields.len() as u64);
} }
} FieldType::Unnamed => {
if fields.fields.len() == 1 {
let (_, new_schema_type) =
SchemaTypeInfo::from(&fields.fields[0]).to_definition();
schema = new_schema_type;
schema.schema_type = self.into();
schema.description = self.get_docs();
} else {
schema.prefix_items = fields
.fields
.iter()
.map(|field| SchemaTypeInfo::from(field).to_definition().1.into())
.collect();
schema.min_items = Some(fields.fields.len() as u64);
schema.max_items = Some(fields.fields.len() as u64);
}
}
},
InternalSchemaType::Array { InternalSchemaType::Array {
element_ty, element_ty,
min_size, min_size,
@ -1185,7 +1251,7 @@ impl From<SchemaTypeInfo> for JsonSchemaVariant {
field_data: None, field_data: None,
stored_fields: None, stored_fields: None,
}; };
schema.items = Some(items_schema.into()); schema.items = Some(items_schema.to_definition().1.into());
schema.min_items = min_size; schema.min_items = min_size;
schema.max_items = max_size; schema.max_items = max_size;
} }
@ -1193,23 +1259,22 @@ impl From<SchemaTypeInfo> for JsonSchemaVariant {
generic: _, generic: _,
ref schema_type_info, ref schema_type_info,
} => { } => {
let schema_variant: JsonSchemaVariant = schema_type_info.as_ref().clone().into(); let schema_variant: JsonSchemaVariant =
schema_type_info.as_ref().clone().to_definition().1.into();
if let JsonSchemaVariant::Schema(value) = schema_variant { if let JsonSchemaVariant::Schema(value) = schema_variant {
let range = val.get_range(); let range = self.get_range();
schema = *value; schema = *value;
schema.schema_type = schema_type.clone(); schema.schema_type = self.into();
schema.minimum = range.min.get_inclusive(); schema.minimum = range.min.get_inclusive();
schema.maximum = range.max.get_inclusive(); schema.maximum = range.max.get_inclusive();
schema.exclusive_minimum = range.min.get_exclusive(); schema.exclusive_minimum = range.min.get_exclusive();
schema.exclusive_maximum = range.max.get_exclusive(); schema.exclusive_maximum = range.max.get_exclusive();
schema.description = val.get_docs(); schema.description = self.get_docs();
schema.kind = SchemaKind::Optional; schema.kind = Some(SchemaKind::Optional);
} else {
return schema_variant;
} }
} }
} }
JsonSchemaVariant::Schema(Box::new(schema)) (id, schema)
} }
} }
@ -1302,38 +1367,28 @@ impl<'a, T> From<&'a T> for SchemaTypeInfo
where where
T: 'static, T: 'static,
TypeInformation: From<&'a T>, TypeInformation: From<&'a T>,
SchemaFieldData: TryFrom<&'a T>, SchemaFieldData: From<&'a T>,
{ {
fn from(value: &'a T) -> Self { fn from(value: &'a T) -> Self {
let ty_info: TypeInformation = value.into(); let ty_info: TypeInformation = value.into();
let field_data: Option<SchemaFieldData> = value.try_into().ok(); let field_data: SchemaFieldData = value.into();
let stored_fields = (&ty_info).into(); let stored_fields = (&ty_info).into();
SchemaTypeInfo { SchemaTypeInfo {
ty_info, ty_info,
field_data, field_data: Some(field_data),
stored_fields, stored_fields,
} }
} }
} }
/// Builds a [`SchemaTypeInfo`] from a value.
pub fn build_schema_type_info<'a, T>(value: &'a T) -> SchemaTypeInfo
where
T: 'static,
TypeInformation: From<&'a T>,
SchemaFieldData: TryFrom<&'a T>,
{
value.into()
}
/// Builds a JSON schema variant from a value. /// Builds a JSON schema variant from a value.
pub fn build_schema<'a, T>(value: &'a T) -> JsonSchemaVariant pub fn build_schema<'a, T>(value: &'a T) -> JsonSchemaVariant
where where
T: 'static, T: 'static,
TypeInformation: From<&'a T>, SchemaTypeInfo: From<&'a T>,
SchemaFieldData: TryFrom<&'a T>,
{ {
build_schema_type_info(value).into() let schema: SchemaTypeInfo = value.into();
schema.to_definition().1.into()
} }
impl From<&UnnamedField> for SchemaFieldData { impl From<&UnnamedField> for SchemaFieldData {
@ -1411,20 +1466,6 @@ impl From<&TypeInfo> for TypeInformation {
TypeInformation::TypeInfo(Box::new(value.clone())) TypeInformation::TypeInfo(Box::new(value.clone()))
} }
} }
impl TryFrom<&TypeInfo> for SchemaFieldData {
type Error = ();
fn try_from(_: &TypeInfo) -> Result<Self, Self::Error> {
Err(())
}
}
impl TryFrom<&TypeRegistration> for SchemaFieldData {
type Error = ();
fn try_from(_: &TypeRegistration) -> Result<Self, Self::Error> {
Err(())
}
}
impl From<&TypeRegistration> for TypeInformation { impl From<&TypeRegistration> for TypeInformation {
fn from(value: &TypeRegistration) -> Self { fn from(value: &TypeRegistration) -> Self {
@ -1437,15 +1478,6 @@ impl From<Type> for TypeInformation {
TypeInformation::Type(Box::new(value)) TypeInformation::Type(Box::new(value))
} }
} }
impl TryFrom<&Type> for SchemaFieldData {
type Error = ();
fn try_from(_: &Type) -> Result<Self, Self::Error> {
Err(())
}
}
impl From<&TypeId> for TypeInformation { impl From<&TypeId> for TypeInformation {
fn from(value: &TypeId) -> Self { fn from(value: &TypeId) -> Self {
TypeInformation::TypeId(*value) TypeInformation::TypeId(*value)
@ -1457,14 +1489,6 @@ impl From<TypeId> for TypeInformation {
} }
} }
impl TryFrom<&TypeId> for SchemaFieldData {
type Error = ();
fn try_from(_: &TypeId) -> Result<Self, Self::Error> {
Err(())
}
}
fn get_fields_information<'a, 'b, T>(iterator: Iter<'a, T>) -> Vec<FieldInformation> fn get_fields_information<'a, 'b, T>(iterator: Iter<'a, T>) -> Vec<FieldInformation>
where where
SchemaFieldData: From<&'a T>, SchemaFieldData: From<&'a T>,
@ -1491,7 +1515,7 @@ mod tests {
#[test] #[test]
fn integer_test() { fn integer_test() {
let type_info = build_schema_type_info(&TypeId::of::<u16>()); let type_info = TypeInformation::from(&TypeId::of::<u16>()).to_schema_type_info();
let schema_type: Option<SchemaTypeVariant> = (&type_info).into(); let schema_type: Option<SchemaTypeVariant> = (&type_info).into();
assert_eq!( assert_eq!(
type_info.get_range().min, type_info.get_range().min,
@ -1520,7 +1544,7 @@ mod tests {
.as_struct() .as_struct()
.expect("Should not fail"); .expect("Should not fail");
let field_info = struct_info.field("no_value").expect("Should not fail"); let field_info = struct_info.field("no_value").expect("Should not fail");
let type_info = build_schema_type_info(field_info); let type_info = SchemaTypeInfo::from(field_info);
let schema_type: Option<SchemaTypeVariant> = (&type_info).into(); let schema_type: Option<SchemaTypeVariant> = (&type_info).into();
let range = type_info.get_range(); let range = type_info.get_range();
assert_eq!(range.min, Some(BoundValue::Inclusive(10.into()))); assert_eq!(range.min, Some(BoundValue::Inclusive(10.into())));
@ -1545,7 +1569,7 @@ mod tests {
.as_struct() .as_struct()
.expect("Should not fail"); .expect("Should not fail");
let field_info = struct_info.field("no_value").expect("Should not fail"); let field_info = struct_info.field("no_value").expect("Should not fail");
let type_info = build_schema_type_info(field_info); let type_info = SchemaTypeInfo::from(field_info);
let schema_type: Option<SchemaTypeVariant> = (&type_info).into(); let schema_type: Option<SchemaTypeVariant> = (&type_info).into();
let range = type_info.get_range(); let range = type_info.get_range();
assert!(!range.in_range((-1).into())); assert!(!range.in_range((-1).into()));
@ -1574,12 +1598,13 @@ mod tests {
register.register_type_data::<bevy_math::Vec3, ReflectJsonSchemaForceAsArray>(); register.register_type_data::<bevy_math::Vec3, ReflectJsonSchemaForceAsArray>();
} }
let type_registry = atr.read(); let type_registry = atr.read();
let declaration = build_schema_type_info( let declaration = TypeInformation::from(
type_registry type_registry
.get(TypeId::of::<bevy_math::Vec3>()) .get(TypeId::of::<bevy_math::Vec3>())
.expect(""), .expect(""),
); )
let _: JsonSchemaVariant = declaration.clone().into(); .to_schema_type_info();
let _: JsonSchemaVariant = declaration.to_definition().1.into();
} }
#[test] #[test]
@ -1596,7 +1621,7 @@ mod tests {
.as_tuple_struct() .as_tuple_struct()
.expect("Should not fail"); .expect("Should not fail");
let field_info = struct_info.iter().next().expect("Should not fail"); let field_info = struct_info.iter().next().expect("Should not fail");
let type_info = build_schema_type_info(field_info); let type_info = SchemaTypeInfo::from(field_info);
let schema_type: Option<SchemaTypeVariant> = (&type_info).into(); let schema_type: Option<SchemaTypeVariant> = (&type_info).into();
let range = type_info.get_range(); let range = type_info.get_range();
assert_eq!(range.min, Some(BoundValue::Inclusive(0.into()))); assert_eq!(range.min, Some(BoundValue::Inclusive(0.into())));
@ -1630,7 +1655,9 @@ mod tests {
pub struct ArrayComponent { pub struct ArrayComponent {
pub array: [i32; 3], pub array: [i32; 3],
} }
let _ = build_schema(&ArrayComponent::get_type_registration()); let type_info =
TypeInformation::from(&ArrayComponent::get_type_registration()).to_schema_type_info();
let _: JsonSchemaVariant = type_info.to_definition().1.into();
} }
#[test] #[test]
@ -1643,7 +1670,9 @@ mod tests {
"{\"map\": {\"0\": 1, \"1\": 41, \"2\": null}}" "{\"map\": {\"0\": 1, \"1\": 41, \"2\": null}}"
) )
.is_ok()); .is_ok());
let _ = build_schema(&HashMapStruct::get_type_registration()); let type_info =
TypeInformation::from(&HashMapStruct::get_type_registration()).to_schema_type_info();
let _: JsonSchemaVariant = type_info.to_definition().1.into();
} }
#[test] #[test]
@ -1669,6 +1698,9 @@ mod tests {
pub second: SecondStruct, pub second: SecondStruct,
pub third: ThirdStruct, pub third: ThirdStruct,
} }
let _ = build_schema(&NestedStruct::get_type_registration()); let type_info =
TypeInformation::from(&NestedStruct::get_type_registration()).to_schema_type_info();
let _s: JsonSchemaVariant = type_info.to_definition().1.into();
// eprintln!("{}", serde_json::to_string_pretty(&s).expect("msg"));
} }
} }