schema IDs using URN format, further improvements, tests for export
endpoint
This commit is contained in:
parent
16c7db42aa
commit
9ed0787d41
@ -26,8 +26,9 @@ use serde_json::{Map, Value};
|
|||||||
use crate::{
|
use crate::{
|
||||||
error_codes,
|
error_codes,
|
||||||
schemas::{
|
schemas::{
|
||||||
json_schema::{JsonSchemaBevyType, TypeRegistrySchemaReader},
|
json_schema::{JsonSchemaBevyType, SchemaMarker, TypeRegistrySchemaReader},
|
||||||
open_rpc::OpenRpcDocument,
|
open_rpc::OpenRpcDocument,
|
||||||
|
reflect_info::TypeInformation,
|
||||||
},
|
},
|
||||||
BrpError, BrpResult,
|
BrpError, BrpResult,
|
||||||
};
|
};
|
||||||
@ -362,6 +363,22 @@ pub struct BrpJsonSchemaQueryFilter {
|
|||||||
pub type_limit: JsonSchemaTypeLimit,
|
pub type_limit: JsonSchemaTypeLimit,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl BrpJsonSchemaQueryFilter {
|
||||||
|
/// 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 {
|
||||||
|
let Some(crate_name) = type_registration.type_info().type_path_table().crate_name() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
if !self.with_crates.is_empty() && !self.with_crates.iter().any(|c| crate_name.eq(c)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if !self.without_crates.is_empty() && self.without_crates.iter().any(|c| crate_name.eq(c)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Additional [`BrpJsonSchemaQueryFilter`] constraints that can be placed on a query to include or exclude
|
/// Additional [`BrpJsonSchemaQueryFilter`] constraints that can be placed on a query to include or exclude
|
||||||
/// certain definitions.
|
/// certain definitions.
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq)]
|
||||||
@ -375,6 +392,34 @@ pub struct JsonSchemaTypeLimit {
|
|||||||
pub with: Vec<String>,
|
pub with: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl JsonSchemaTypeLimit {
|
||||||
|
/// Check if the type limit is empty.
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.without.is_empty() && self.with.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if the type limit should skip a type.
|
||||||
|
pub fn should_skip_type(&self, registered_types: &[Cow<'_, str>]) -> bool {
|
||||||
|
if !self.with.is_empty()
|
||||||
|
&& !self
|
||||||
|
.with
|
||||||
|
.iter()
|
||||||
|
.any(|c| registered_types.iter().any(|cc| c.eq(cc)))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if !self.without.is_empty()
|
||||||
|
&& self
|
||||||
|
.without
|
||||||
|
.iter()
|
||||||
|
.any(|c| registered_types.iter().any(|cc| c.eq(cc)))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A response from the world to the client that specifies a single entity.
|
/// A response from the world to the client that specifies a single entity.
|
||||||
///
|
///
|
||||||
/// This is sent in response to `bevy/spawn`.
|
/// This is sent in response to `bevy/spawn`.
|
||||||
@ -1230,48 +1275,47 @@ pub fn export_registry_types(In(params): In<Option<Value>>, world: &World) -> Br
|
|||||||
let extra_info = world.resource::<crate::schemas::SchemaTypesMetadata>();
|
let extra_info = world.resource::<crate::schemas::SchemaTypesMetadata>();
|
||||||
let types = world.resource::<AppTypeRegistry>();
|
let types = world.resource::<AppTypeRegistry>();
|
||||||
let types = types.read();
|
let types = types.read();
|
||||||
let schemas = types
|
let mut schema = JsonSchemaBevyType {
|
||||||
|
schema: Some(SchemaMarker.into()),
|
||||||
|
description: Some(
|
||||||
|
"This schema represents the types registered in the Bevy application.".into(),
|
||||||
|
),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
schema.definitions = types
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|type_reg| {
|
.filter_map(|type_reg| {
|
||||||
let path_table = type_reg.type_info().type_path_table();
|
if filter.should_skip_for_crate(type_reg) {
|
||||||
if let Some(crate_name) = &path_table.crate_name() {
|
return None;
|
||||||
if !filter.with_crates.is_empty()
|
}
|
||||||
&& !filter.with_crates.iter().any(|c| crate_name.eq(c))
|
if !filter.type_limit.is_empty() {
|
||||||
{
|
let registered_types = extra_info.get_registered_reflect_types(type_reg);
|
||||||
return None;
|
if filter
|
||||||
}
|
.type_limit
|
||||||
if !filter.without_crates.is_empty()
|
.should_skip_type(registered_types.as_slice())
|
||||||
&& filter.without_crates.iter().any(|c| crate_name.eq(c))
|
|
||||||
{
|
{
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let id = type_reg.type_id();
|
let id = type_reg.type_id();
|
||||||
let schema = types.export_type_json_schema_for_id(id, extra_info)?;
|
let schema_type_info = TypeInformation::TypeRegistration(type_reg.clone())
|
||||||
|
.to_schema_type_info_with_metadata(extra_info);
|
||||||
if !filter.type_limit.with.is_empty()
|
let schema_id = schema_type_info.ty_info.try_get_type_reference_id()?;
|
||||||
&& !filter
|
let mut definition = schema_type_info.to_definition();
|
||||||
.type_limit
|
definition.schema.default_value = types.try_get_default_value_for_type_id(id);
|
||||||
.with
|
definition.schema.schema = None;
|
||||||
.iter()
|
let mut definitions = vec![(schema_id, Box::new(definition.schema))];
|
||||||
.any(|c| schema.reflect_type_data.iter().any(|cc| c.eq(cc)))
|
let extra_defs = definition.definitions.iter().map(|(id, definition)| {
|
||||||
{
|
let def = definition.to_definition();
|
||||||
return None;
|
(id.clone(), Box::new(def.schema))
|
||||||
}
|
});
|
||||||
if !filter.type_limit.without.is_empty()
|
definitions.extend(extra_defs);
|
||||||
&& filter
|
Some(definitions)
|
||||||
.type_limit
|
|
||||||
.without
|
|
||||||
.iter()
|
|
||||||
.any(|c| schema.reflect_type_data.iter().any(|cc| c.eq(cc)))
|
|
||||||
{
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
Some((type_reg.type_info().type_path().into(), schema))
|
|
||||||
})
|
})
|
||||||
.collect::<HashMap<Cow<'static, str>, JsonSchemaBevyType>>();
|
.flatten()
|
||||||
|
.collect();
|
||||||
|
|
||||||
serde_json::to_value(schemas).map_err(BrpError::internal)
|
serde_json::to_value(schema).map_err(BrpError::internal)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Immutably retrieves an entity from the [`World`], returning an error if the
|
/// Immutably retrieves an entity from the [`World`], returning an error if the
|
||||||
@ -1533,6 +1577,12 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use bevy_ecs::component::Component;
|
||||||
|
use bevy_math::Vec3;
|
||||||
|
use bevy_reflect::Reflect;
|
||||||
|
|
||||||
|
use crate::schemas::{ReflectJsonSchemaForceAsArray, SchemaTypesMetadata};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -1556,4 +1606,96 @@ mod tests {
|
|||||||
entity: Entity::from_raw_u32(0).unwrap(),
|
entity: Entity::from_raw_u32(0).unwrap(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn export_schema_test() {
|
||||||
|
#[derive(Reflect, Default, Deserialize, Serialize)]
|
||||||
|
pub struct OtherStruct {
|
||||||
|
/// FIELD DOC
|
||||||
|
pub field: String,
|
||||||
|
}
|
||||||
|
/// STRUCT DOC
|
||||||
|
#[derive(Reflect, Default, Deserialize, Serialize)]
|
||||||
|
pub struct SecondStruct {
|
||||||
|
pub field: String,
|
||||||
|
/// FIELD DOC
|
||||||
|
pub other: OtherStruct,
|
||||||
|
}
|
||||||
|
/// STRUCT DOC
|
||||||
|
#[derive(Reflect, Default, Deserialize, Serialize)]
|
||||||
|
pub struct ThirdStruct {
|
||||||
|
pub array_strings: Vec<String>,
|
||||||
|
pub array_structs: [OtherStruct; 5],
|
||||||
|
pub map_strings: HashMap<String, i32>,
|
||||||
|
}
|
||||||
|
#[derive(Reflect, Default, Deserialize, Serialize, Component)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub struct NestedStruct {
|
||||||
|
pub other: OtherStruct,
|
||||||
|
pub second: SecondStruct,
|
||||||
|
/// DOC FOR FIELD
|
||||||
|
pub third: ThirdStruct,
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut world = World::new();
|
||||||
|
let atr = AppTypeRegistry::default();
|
||||||
|
{
|
||||||
|
let mut register = atr.write();
|
||||||
|
register.register::<NestedStruct>();
|
||||||
|
register.register::<Vec3>();
|
||||||
|
register.register_type_data::<Vec3, ReflectJsonSchemaForceAsArray>();
|
||||||
|
}
|
||||||
|
world.insert_resource(atr);
|
||||||
|
world.insert_resource(SchemaTypesMetadata::default());
|
||||||
|
let response = export_registry_types_ext(BrpJsonSchemaQueryFilter::default(), &world);
|
||||||
|
|
||||||
|
assert_eq!(response.definitions.len(), 5);
|
||||||
|
let response = export_registry_types_ext(
|
||||||
|
BrpJsonSchemaQueryFilter {
|
||||||
|
without_crates: vec!["bevy_remote".to_string()],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
&world,
|
||||||
|
);
|
||||||
|
assert_eq!(response.definitions.len(), 1);
|
||||||
|
{
|
||||||
|
let first = response.definitions.iter().next().expect("Should have one");
|
||||||
|
assert_eq!(first.1.id, "urn:bevy:glam-Vec3");
|
||||||
|
}
|
||||||
|
let response = export_registry_types_ext(
|
||||||
|
BrpJsonSchemaQueryFilter {
|
||||||
|
with_crates: vec!["bevy_remote".to_string()],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
&world,
|
||||||
|
);
|
||||||
|
assert_eq!(response.definitions.len(), 4);
|
||||||
|
let response = export_registry_types_ext(
|
||||||
|
BrpJsonSchemaQueryFilter {
|
||||||
|
type_limit: JsonSchemaTypeLimit {
|
||||||
|
with: vec!["Component".to_string()],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
&world,
|
||||||
|
);
|
||||||
|
assert_eq!(response.definitions.len(), 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn export_registry_types_ext(
|
||||||
|
input: BrpJsonSchemaQueryFilter,
|
||||||
|
world: &World,
|
||||||
|
) -> JsonSchemaBevyType {
|
||||||
|
let response_json = export_registry_types(
|
||||||
|
In(Some(
|
||||||
|
serde_json::to_value(&input).expect("Failed to serialize input"),
|
||||||
|
)),
|
||||||
|
world,
|
||||||
|
)
|
||||||
|
.expect("Failed to export registry types");
|
||||||
|
|
||||||
|
serde_json::from_value::<JsonSchemaBevyType>(response_json)
|
||||||
|
.expect("Failed to deserialize response")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,7 +12,7 @@ use serde_json::Value;
|
|||||||
|
|
||||||
use crate::schemas::{
|
use crate::schemas::{
|
||||||
reflect_info::{SchemaNumber, TypeInformation, TypeReferenceId, TypeReferencePath},
|
reflect_info::{SchemaNumber, TypeInformation, TypeReferenceId, TypeReferencePath},
|
||||||
ReflectJsonSchema, SchemaTypesMetadata,
|
SchemaTypesMetadata,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Helper trait for converting `TypeRegistration` to `JsonSchemaBevyType`
|
/// Helper trait for converting `TypeRegistration` to `JsonSchemaBevyType`
|
||||||
@ -43,7 +43,7 @@ impl TypeRegistrySchemaReader for TypeRegistry {
|
|||||||
) -> Option<JsonSchemaBevyType> {
|
) -> Option<JsonSchemaBevyType> {
|
||||||
let type_reg = self.get(type_id)?;
|
let type_reg = self.get(type_id)?;
|
||||||
let mut schema: JsonSchemaBevyType = (type_reg, extra_info).try_into().ok()?;
|
let mut schema: JsonSchemaBevyType = (type_reg, extra_info).try_into().ok()?;
|
||||||
schema.schema = Some(SchemaMarker::default());
|
schema.schema = Some(SchemaMarker.into());
|
||||||
schema.default_value = self.try_get_default_value_for_type_id(type_id);
|
schema.default_value = self.try_get_default_value_for_type_id(type_id);
|
||||||
|
|
||||||
Some(schema)
|
Some(schema)
|
||||||
@ -79,11 +79,11 @@ impl TryFrom<(&TypeRegistration, &SchemaTypesMetadata)> for JsonSchemaBevyType {
|
|||||||
|
|
||||||
fn try_from(value: (&TypeRegistration, &SchemaTypesMetadata)) -> Result<Self, Self::Error> {
|
fn try_from(value: (&TypeRegistration, &SchemaTypesMetadata)) -> Result<Self, Self::Error> {
|
||||||
let (reg, metadata) = value;
|
let (reg, metadata) = value;
|
||||||
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 mut schema: JsonSchemaBevyType = TypeInformation::from(reg)
|
let mut schema: JsonSchemaBevyType = TypeInformation::from(reg)
|
||||||
.to_schema_type_info()
|
.to_schema_type_info_with_metadata(metadata)
|
||||||
.to_definition()
|
.to_definition()
|
||||||
.into();
|
.into();
|
||||||
schema.reflect_type_data = metadata.get_registered_reflect_types(reg);
|
schema.reflect_type_data = metadata.get_registered_reflect_types(reg);
|
||||||
@ -101,11 +101,21 @@ impl TryFrom<&TypeRegistration> for JsonSchemaBevyType {
|
|||||||
|
|
||||||
/// Identifies the JSON Schema version used in the schema.
|
/// Identifies the JSON Schema version used in the schema.
|
||||||
#[derive(Deserialize, Serialize, Debug, Reflect, PartialEq, Clone)]
|
#[derive(Deserialize, Serialize, Debug, Reflect, PartialEq, Clone)]
|
||||||
pub struct SchemaMarker(Cow<'static, str>);
|
pub struct SchemaMarker;
|
||||||
|
|
||||||
impl Default for SchemaMarker {
|
impl SchemaMarker {
|
||||||
fn default() -> Self {
|
const DEFAULT_SCHEMA: &'static str = "https://json-schema.org/draft/2020-12/schema";
|
||||||
Self("https://json-schema.org/draft/2020-12/schema".into())
|
}
|
||||||
|
|
||||||
|
impl From<SchemaMarker> for &'static str {
|
||||||
|
fn from(_: SchemaMarker) -> Self {
|
||||||
|
SchemaMarker::DEFAULT_SCHEMA
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<SchemaMarker> for Cow<'static, str> {
|
||||||
|
fn from(_: SchemaMarker) -> Self {
|
||||||
|
Cow::Borrowed(SchemaMarker::DEFAULT_SCHEMA)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,10 +126,15 @@ impl Default for SchemaMarker {
|
|||||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default, Reflect)]
|
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default, Reflect)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct JsonSchemaBevyType {
|
pub struct JsonSchemaBevyType {
|
||||||
|
/// JSON Schema specific field.
|
||||||
|
/// This keyword declares an identifier for the schema resource.
|
||||||
|
#[serde(skip_serializing_if = "str::is_empty", default)]
|
||||||
|
#[serde(rename = "$id")]
|
||||||
|
pub id: Cow<'static, str>,
|
||||||
/// Identifies the JSON Schema version used in the schema.
|
/// Identifies the JSON Schema version used in the schema.
|
||||||
#[serde(rename = "$schema")]
|
#[serde(rename = "$schema")]
|
||||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||||
pub schema: Option<SchemaMarker>,
|
pub schema: Option<Cow<'static, str>>,
|
||||||
/// JSON Schema specific field.
|
/// JSON Schema specific field.
|
||||||
/// This keyword is used to reference a statically identified schema.
|
/// This keyword is used to reference a statically identified schema.
|
||||||
#[serde(rename = "$ref")]
|
#[serde(rename = "$ref")]
|
||||||
@ -245,7 +260,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, JsonSchemaVariant>,
|
pub definitions: HashMap<TypeReferenceId, Box<JsonSchemaBevyType>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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.
|
||||||
@ -411,7 +426,9 @@ impl SchemaType {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use crate::schemas::open_rpc::OpenRpcDocument;
|
||||||
use crate::schemas::reflect_info::ReferenceLocation;
|
use crate::schemas::reflect_info::ReferenceLocation;
|
||||||
|
use crate::schemas::ReflectJsonSchema;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use bevy_ecs::prelude::ReflectComponent;
|
use bevy_ecs::prelude::ReflectComponent;
|
||||||
@ -587,6 +604,35 @@ mod tests {
|
|||||||
assert!(schema.one_of.len() == 3, "Should have 3 possible schemas");
|
assert!(schema.one_of.len() == 3, "Should have 3 possible schemas");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reflect_struct_with_custom_schema() {
|
||||||
|
let atr = AppTypeRegistry::default();
|
||||||
|
{
|
||||||
|
let mut register = atr.write();
|
||||||
|
register.register::<OpenRpcDocument>();
|
||||||
|
register.register_type_data::<OpenRpcDocument, ReflectJsonSchema>();
|
||||||
|
}
|
||||||
|
let type_registry = atr.read();
|
||||||
|
let schema = type_registry
|
||||||
|
.export_type_json_schema::<OpenRpcDocument>(&SchemaTypesMetadata::default())
|
||||||
|
.expect("Failed to export schema");
|
||||||
|
assert_eq!(
|
||||||
|
schema.ref_type,
|
||||||
|
Some(TypeReferencePath::new_ref(
|
||||||
|
ReferenceLocation::Url,
|
||||||
|
"raw.githubusercontent.com/open-rpc/meta-schema/master/schema.json",
|
||||||
|
))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
schema.description,
|
||||||
|
Some(
|
||||||
|
"Represents an `OpenRPC` document as defined by the `OpenRPC` specification."
|
||||||
|
.into()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert!(schema.properties.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn reflect_export_with_custom_schema() {
|
fn reflect_export_with_custom_schema() {
|
||||||
#[derive(Reflect, Component)]
|
#[derive(Reflect, Component)]
|
||||||
@ -666,6 +712,7 @@ mod tests {
|
|||||||
let schema = export_type::<Foo>();
|
let schema = export_type::<Foo>();
|
||||||
let schema_as_value = serde_json::to_value(&schema).expect("Failed to serialize schema");
|
let schema_as_value = serde_json::to_value(&schema).expect("Failed to serialize schema");
|
||||||
let mut value = json!({
|
let mut value = json!({
|
||||||
|
"$id": "urn:bevy:bevy_remote-schemas-json_schema-tests-Foo",
|
||||||
"shortPath": "Foo",
|
"shortPath": "Foo",
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"typePath": "bevy_remote::schemas::json_schema::tests::Foo",
|
"typePath": "bevy_remote::schemas::json_schema::tests::Foo",
|
||||||
|
|||||||
@ -20,7 +20,7 @@ pub mod reflect_info;
|
|||||||
|
|
||||||
/// Holds mapping of reflect [type data](TypeData) to human-readable type names,
|
/// Holds mapping of reflect [type data](TypeData) to human-readable type names,
|
||||||
/// later on used in Bevy Json Schema.
|
/// later on used in Bevy Json Schema.
|
||||||
#[derive(Debug, Resource, Reflect)]
|
#[derive(Debug, Resource, Reflect, Clone)]
|
||||||
#[reflect(Resource)]
|
#[reflect(Resource)]
|
||||||
pub struct SchemaTypesMetadata {
|
pub struct SchemaTypesMetadata {
|
||||||
/// Type Data id mapping to human-readable type names.
|
/// Type Data id mapping to human-readable type names.
|
||||||
|
|||||||
@ -24,7 +24,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
|||||||
use crate::schemas::json_schema::{
|
use crate::schemas::json_schema::{
|
||||||
JsonSchemaBevyType, JsonSchemaVariant, SchemaKind, SchemaType, SchemaTypeVariant,
|
JsonSchemaBevyType, JsonSchemaVariant, SchemaKind, SchemaType, SchemaTypeVariant,
|
||||||
};
|
};
|
||||||
use crate::schemas::ReflectJsonSchemaForceAsArray;
|
use crate::schemas::{ReflectJsonSchemaForceAsArray, SchemaTypesMetadata};
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug,
|
Debug,
|
||||||
@ -90,6 +90,8 @@ pub enum FieldType {
|
|||||||
/// Unnamed field type.
|
/// Unnamed field type.
|
||||||
#[default]
|
#[default]
|
||||||
Unnamed,
|
Unnamed,
|
||||||
|
/// Named field type that is stored as unnamed. Example: glam Vec3.
|
||||||
|
ForceUnnamed,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Information about the attributes of a field.
|
/// Information about the attributes of a field.
|
||||||
@ -109,7 +111,7 @@ impl From<&TypeInformation> for Option<FieldsInformation> {
|
|||||||
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() {
|
||||||
FieldType::Unnamed
|
FieldType::ForceUnnamed
|
||||||
} else {
|
} else {
|
||||||
FieldType::Named
|
FieldType::Named
|
||||||
},
|
},
|
||||||
@ -200,13 +202,34 @@ pub enum TypeInformation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TypeInformation {
|
impl TypeInformation {
|
||||||
/// Returns the reference type if the stored type is a reference one.
|
/// Checks for custom schema.
|
||||||
pub fn get_reference_type(&self) -> Option<TypeReferencePath> {
|
pub fn try_get_custom_schema(&self) -> Option<&super::ReflectJsonSchema> {
|
||||||
if self.is_primitive_type() {
|
if let Self::TypeRegistration(reg) = self {
|
||||||
None
|
reg.data::<super::ReflectJsonSchema>()
|
||||||
} else {
|
} else {
|
||||||
self.try_get_type_path_table()
|
None
|
||||||
.map(TypeReferencePath::definition)
|
}
|
||||||
|
}
|
||||||
|
/// Builds a `TypeReferenceId` from the type path.
|
||||||
|
pub fn try_get_type_reference_id(&self) -> Option<TypeReferenceId> {
|
||||||
|
if let Some(schema) = self.try_get_custom_schema() {
|
||||||
|
if schema.0.id.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(schema.0.id.trim().into())
|
||||||
|
}
|
||||||
|
} else if self.is_primitive_type() {
|
||||||
|
None
|
||||||
|
} else if let Some(optional) = self.try_get_optional_info() {
|
||||||
|
Some(optional.type_path().into())
|
||||||
|
} else if let Some(s) = self.try_get_type_info() {
|
||||||
|
if s.as_array().is_ok() || s.as_list().is_ok() || s.as_map().is_ok() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
self.try_get_type_path_table().map(|t| t.path().into())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.try_get_type_path_table().map(|t| t.path().into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Returns true if the stored type is a primitive one.
|
/// Returns true if the stored type is a primitive one.
|
||||||
@ -224,6 +247,25 @@ impl TypeInformation {
|
|||||||
ty_info: self,
|
ty_info: self,
|
||||||
field_data: None,
|
field_data: None,
|
||||||
stored_fields,
|
stored_fields,
|
||||||
|
reflect_type_data: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Converts the type information into a schema type information.
|
||||||
|
pub fn to_schema_type_info_with_metadata(
|
||||||
|
self,
|
||||||
|
metadata: &SchemaTypesMetadata,
|
||||||
|
) -> SchemaTypeInfo {
|
||||||
|
let stored_fields = (&self).into();
|
||||||
|
let reflect_type_data = if let Self::TypeRegistration(reg) = &self {
|
||||||
|
Some(metadata.get_registered_reflect_types(reg))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
SchemaTypeInfo {
|
||||||
|
ty_info: self,
|
||||||
|
field_data: None,
|
||||||
|
stored_fields,
|
||||||
|
reflect_type_data,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Returns the documentation of the type.
|
/// Returns the documentation of the type.
|
||||||
@ -834,13 +876,14 @@ 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) => {
|
||||||
|
let fields = get_fields_information(struct_info.iter());
|
||||||
let fields_type = if value.is_forced_as_array() {
|
let fields_type = if value.is_forced_as_array() {
|
||||||
FieldType::Unnamed
|
FieldType::ForceUnnamed
|
||||||
} else {
|
} else {
|
||||||
FieldType::Named
|
FieldType::Named
|
||||||
};
|
};
|
||||||
InternalSchemaType::FieldsHolder(FieldsInformation {
|
InternalSchemaType::FieldsHolder(FieldsInformation {
|
||||||
fields: get_fields_information(struct_info.iter()),
|
fields,
|
||||||
fields_type,
|
fields_type,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -883,7 +926,7 @@ impl From<&TypeInformation> for InternalSchemaType {
|
|||||||
key: (map_info.key_info(), &map_info.key_ty()).into(),
|
key: (map_info.key_info(), &map_info.key_ty()).into(),
|
||||||
value: (map_info.value_info(), &map_info.value_ty()).into(),
|
value: (map_info.value_info(), &map_info.value_ty()).into(),
|
||||||
},
|
},
|
||||||
TypeInfo::Opaque(t) => InternalSchemaType::Regular(t.type_id()),
|
TypeInfo::Opaque(t) => InternalSchemaType::RegularType(*t.ty()),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
match value {
|
match value {
|
||||||
@ -925,18 +968,14 @@ 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::FieldsHolder(fields) => {
|
InternalSchemaType::FieldsHolder(fields) => match fields.fields_type {
|
||||||
if fields.fields_type == FieldType::Unnamed {
|
FieldType::Named => Some(SchemaTypeVariant::Single(SchemaType::Object)),
|
||||||
if fields.fields.len() == 1 {
|
FieldType::Unnamed if fields.fields.len() == 1 => {
|
||||||
let schema: InternalSchemaType = (&fields.fields[0].type_info).into();
|
let schema: InternalSchemaType = (&fields.fields[0].type_info).into();
|
||||||
(&schema).into()
|
(&schema).into()
|
||||||
} else {
|
|
||||||
Some(SchemaTypeVariant::Single(SchemaType::Array))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Some(SchemaTypeVariant::Single(SchemaType::Object))
|
|
||||||
}
|
}
|
||||||
}
|
_ => Some(SchemaTypeVariant::Single(SchemaType::Array)),
|
||||||
|
},
|
||||||
InternalSchemaType::Optional {
|
InternalSchemaType::Optional {
|
||||||
generic,
|
generic,
|
||||||
schema_type_info: _,
|
schema_type_info: _,
|
||||||
@ -972,23 +1011,11 @@ impl From<&FieldInformation> for SchemaTypeInfo {
|
|||||||
ty_info: value.type_info.clone(),
|
ty_info: value.type_info.clone(),
|
||||||
field_data: Some(value.field.clone()),
|
field_data: Some(value.field.clone()),
|
||||||
stored_fields: (&value.type_info).into(),
|
stored_fields: (&value.type_info).into(),
|
||||||
|
reflect_type_data: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Contains comprehensive information about a type's schema representation.
|
|
||||||
/// This struct aggregates all the necessary information to generate a JSON schema
|
|
||||||
/// from Rust type information obtained through reflection.
|
|
||||||
#[derive(Clone, Debug, Default)]
|
|
||||||
pub struct SchemaTypeInfo {
|
|
||||||
/// Information about the type of the schema.
|
|
||||||
pub ty_info: TypeInformation,
|
|
||||||
/// Field information for the type.
|
|
||||||
pub field_data: Option<SchemaFieldData>,
|
|
||||||
/// Fields stored in the type.
|
|
||||||
pub stored_fields: Option<FieldsInformation>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Contains comprehensive information about a type's schema representation.
|
/// Contains comprehensive information about a type's schema representation.
|
||||||
/// 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.
|
||||||
@ -1015,6 +1042,21 @@ impl From<SchemaDefinition> for JsonSchemaBevyType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Contains comprehensive information about a type's schema representation.
|
||||||
|
/// This struct aggregates all the necessary information to generate a JSON schema
|
||||||
|
/// from Rust type information obtained through reflection.
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct SchemaTypeInfo {
|
||||||
|
/// Information about the type of the schema.
|
||||||
|
pub ty_info: TypeInformation,
|
||||||
|
/// Field information for the type.
|
||||||
|
pub field_data: Option<SchemaFieldData>,
|
||||||
|
/// Fields stored in the type.
|
||||||
|
pub stored_fields: Option<FieldsInformation>,
|
||||||
|
/// Bevy specific field, names of the types that type reflects. Mapping of the names to the data types is provided by [`SchemaTypesMetadata`].
|
||||||
|
pub reflect_type_data: Option<Vec<Cow<'static, str>>>,
|
||||||
|
}
|
||||||
|
|
||||||
impl SchemaTypeInfo {
|
impl SchemaTypeInfo {
|
||||||
/// Get the documentation for the schema type.
|
/// Get the documentation for the schema type.
|
||||||
/// If the field has a description, it is returned.
|
/// If the field has a description, it is returned.
|
||||||
@ -1052,7 +1094,12 @@ impl SchemaTypeInfo {
|
|||||||
let range = self.get_range();
|
let range = self.get_range();
|
||||||
let description = self.get_docs();
|
let description = self.get_docs();
|
||||||
let internal_type: InternalSchemaType = (self).into();
|
let internal_type: InternalSchemaType = (self).into();
|
||||||
let (ref_type, schema_type) = (self.ty_info.get_reference_type(), self.into());
|
let (ref_type, schema_type) = (
|
||||||
|
self.ty_info
|
||||||
|
.try_get_type_reference_id()
|
||||||
|
.map(TypeReferencePath::definition),
|
||||||
|
self.into(),
|
||||||
|
);
|
||||||
|
|
||||||
let mut schema = JsonSchemaBevyType {
|
let mut schema = JsonSchemaBevyType {
|
||||||
description,
|
description,
|
||||||
@ -1076,6 +1123,7 @@ impl SchemaTypeInfo {
|
|||||||
ty_info: element_ty.clone(),
|
ty_info: element_ty.clone(),
|
||||||
field_data: None,
|
field_data: None,
|
||||||
stored_fields: None,
|
stored_fields: None,
|
||||||
|
reflect_type_data: None,
|
||||||
};
|
};
|
||||||
schema.items = Some(items_schema.to_ref_schema().into());
|
schema.items = Some(items_schema.to_ref_schema().into());
|
||||||
schema.min_items = min_size;
|
schema.min_items = min_size;
|
||||||
@ -1084,10 +1132,6 @@ impl SchemaTypeInfo {
|
|||||||
InternalSchemaType::EnumHolder(_)
|
InternalSchemaType::EnumHolder(_)
|
||||||
| InternalSchemaType::EnumVariant(_)
|
| InternalSchemaType::EnumVariant(_)
|
||||||
| InternalSchemaType::FieldsHolder(_)
|
| InternalSchemaType::FieldsHolder(_)
|
||||||
| InternalSchemaType::Optional {
|
|
||||||
generic: _,
|
|
||||||
schema_type_info: _,
|
|
||||||
}
|
|
||||||
| InternalSchemaType::Map { key: _, value: _ } => {
|
| InternalSchemaType::Map { key: _, value: _ } => {
|
||||||
schema.ref_type = None;
|
schema.ref_type = None;
|
||||||
}
|
}
|
||||||
@ -1099,9 +1143,15 @@ impl SchemaTypeInfo {
|
|||||||
|
|
||||||
/// Converts the schema type information into a JSON schema definition.
|
/// Converts the schema type information into a JSON schema definition.
|
||||||
pub fn to_definition(&self) -> SchemaDefinition {
|
pub fn to_definition(&self) -> SchemaDefinition {
|
||||||
let mut id: Option<TypeReferenceId> =
|
let mut id: Option<TypeReferenceId> = self.ty_info.try_get_type_reference_id();
|
||||||
self.ty_info.try_get_type_path_table().map(Into::into);
|
|
||||||
let mut definitions: HashMap<TypeReferenceId, SchemaTypeInfo> = HashMap::new();
|
let mut definitions: HashMap<TypeReferenceId, SchemaTypeInfo> = HashMap::new();
|
||||||
|
if let Some(custom_schema) = &self.ty_info.try_get_custom_schema() {
|
||||||
|
return SchemaDefinition {
|
||||||
|
id,
|
||||||
|
schema: custom_schema.0.clone(),
|
||||||
|
definitions,
|
||||||
|
};
|
||||||
|
}
|
||||||
let range = self.ty_info.get_range();
|
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) =
|
||||||
@ -1115,7 +1165,12 @@ impl SchemaTypeInfo {
|
|||||||
} else {
|
} else {
|
||||||
(Cow::default(), Cow::default(), None, None)
|
(Cow::default(), Cow::default(), None, None)
|
||||||
};
|
};
|
||||||
|
let schema_id = id
|
||||||
|
.as_ref()
|
||||||
|
.map(|id| Cow::Owned(format!("urn:bevy:{}", id)))
|
||||||
|
.unwrap_or_default();
|
||||||
let mut schema = JsonSchemaBevyType {
|
let mut schema = JsonSchemaBevyType {
|
||||||
|
id: schema_id,
|
||||||
description: self.ty_info.get_docs(),
|
description: self.ty_info.get_docs(),
|
||||||
type_path,
|
type_path,
|
||||||
short_path,
|
short_path,
|
||||||
@ -1127,6 +1182,7 @@ impl SchemaTypeInfo {
|
|||||||
exclusive_minimum: range.min.get_exclusive(),
|
exclusive_minimum: range.min.get_exclusive(),
|
||||||
exclusive_maximum: range.max.get_exclusive(),
|
exclusive_maximum: range.max.get_exclusive(),
|
||||||
schema_type: self.into(),
|
schema_type: self.into(),
|
||||||
|
reflect_type_data: self.reflect_type_data.clone().unwrap_or_default(),
|
||||||
..default()
|
..default()
|
||||||
};
|
};
|
||||||
let internal_type: InternalSchemaType = (self).into();
|
let internal_type: InternalSchemaType = (self).into();
|
||||||
@ -1136,11 +1192,13 @@ impl SchemaTypeInfo {
|
|||||||
ty_info: key.clone(),
|
ty_info: key.clone(),
|
||||||
field_data: None,
|
field_data: None,
|
||||||
stored_fields: None,
|
stored_fields: None,
|
||||||
|
reflect_type_data: None,
|
||||||
};
|
};
|
||||||
let value: SchemaTypeInfo = SchemaTypeInfo {
|
let value: SchemaTypeInfo = SchemaTypeInfo {
|
||||||
ty_info: value.clone(),
|
ty_info: value.clone(),
|
||||||
field_data: None,
|
field_data: None,
|
||||||
stored_fields: None,
|
stored_fields: None,
|
||||||
|
reflect_type_data: None,
|
||||||
};
|
};
|
||||||
if !key.ty_info.is_primitive_type() {
|
if !key.ty_info.is_primitive_type() {
|
||||||
let SchemaDefinition {
|
let SchemaDefinition {
|
||||||
@ -1204,6 +1262,7 @@ impl SchemaTypeInfo {
|
|||||||
fields,
|
fields,
|
||||||
fields_type: FieldType::Named,
|
fields_type: FieldType::Named,
|
||||||
}),
|
}),
|
||||||
|
reflect_type_data: None,
|
||||||
};
|
};
|
||||||
let definition = schema_field.to_definition();
|
let definition = schema_field.to_definition();
|
||||||
|
|
||||||
@ -1222,6 +1281,7 @@ impl SchemaTypeInfo {
|
|||||||
|
|
||||||
fields_type: FieldType::Unnamed,
|
fields_type: FieldType::Unnamed,
|
||||||
}),
|
}),
|
||||||
|
reflect_type_data: None,
|
||||||
};
|
};
|
||||||
let definition = schema_field.to_definition();
|
let definition = schema_field.to_definition();
|
||||||
|
|
||||||
@ -1235,6 +1295,7 @@ impl SchemaTypeInfo {
|
|||||||
ty_info,
|
ty_info,
|
||||||
field_data,
|
field_data,
|
||||||
stored_fields: None,
|
stored_fields: None,
|
||||||
|
reflect_type_data: None,
|
||||||
};
|
};
|
||||||
return SchemaDefinition {
|
return SchemaDefinition {
|
||||||
id: None,
|
id: None,
|
||||||
@ -1272,11 +1333,11 @@ impl SchemaTypeInfo {
|
|||||||
schema: _,
|
schema: _,
|
||||||
definitions: field_definitions,
|
definitions: field_definitions,
|
||||||
} = field_schema.to_definition();
|
} = field_schema.to_definition();
|
||||||
|
definitions.extend(field_definitions);
|
||||||
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);
|
||||||
}
|
}
|
||||||
definitions.extend(field_definitions);
|
|
||||||
}
|
}
|
||||||
schema.required = fields
|
schema.required = fields
|
||||||
.fields
|
.fields
|
||||||
@ -1284,45 +1345,70 @@ impl SchemaTypeInfo {
|
|||||||
.map(|field| field.field.to_name())
|
.map(|field| field.field.to_name())
|
||||||
.collect();
|
.collect();
|
||||||
}
|
}
|
||||||
FieldType::Unnamed => {
|
FieldType::Unnamed if fields.fields.len() == 1 => {
|
||||||
if fields.fields.len() == 1 {
|
let field_schema = SchemaTypeInfo::from(&fields.fields[0]);
|
||||||
let field_schema = SchemaTypeInfo::from(&fields.fields[0]);
|
let SchemaDefinition {
|
||||||
|
id,
|
||||||
|
schema: new_schema_type,
|
||||||
|
definitions: field_definitions,
|
||||||
|
} = field_schema.to_definition();
|
||||||
|
definitions.extend(field_definitions);
|
||||||
|
if let Some(id) = id {
|
||||||
|
definitions.insert(id, field_schema);
|
||||||
|
}
|
||||||
|
schema = new_schema_type;
|
||||||
|
schema.schema_type = self.into();
|
||||||
|
schema.description = self.get_docs();
|
||||||
|
}
|
||||||
|
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();
|
||||||
|
for field_schema in schema_fields {
|
||||||
|
if field_schema.ty_info.is_primitive_type() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let SchemaDefinition {
|
let SchemaDefinition {
|
||||||
id,
|
id,
|
||||||
schema: new_schema_type,
|
schema: _,
|
||||||
definitions: _,
|
definitions: field_definitions,
|
||||||
} = field_schema.to_definition();
|
} = field_schema.to_definition();
|
||||||
if let Some(id) = id {
|
definitions.extend(field_definitions);
|
||||||
|
let Some(id) = id else { continue };
|
||||||
|
if !definitions.contains_key(&id) {
|
||||||
definitions.insert(id, field_schema);
|
definitions.insert(id, field_schema);
|
||||||
}
|
}
|
||||||
schema = new_schema_type;
|
|
||||||
schema.schema_type = self.into();
|
|
||||||
schema.description = self.get_docs();
|
|
||||||
} else {
|
|
||||||
let schema_fields: Vec<SchemaTypeInfo> =
|
|
||||||
fields.fields.iter().map(SchemaTypeInfo::from).collect();
|
|
||||||
schema.prefix_items = schema_fields
|
|
||||||
.iter()
|
|
||||||
.map(|field| field.to_ref_schema().into())
|
|
||||||
.collect();
|
|
||||||
for field_schema in schema_fields {
|
|
||||||
if field_schema.ty_info.is_primitive_type() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let SchemaDefinition {
|
|
||||||
id,
|
|
||||||
schema: _,
|
|
||||||
definitions: field_definitions,
|
|
||||||
} = field_schema.to_definition();
|
|
||||||
let Some(id) = id else { continue };
|
|
||||||
if !definitions.contains_key(&id) {
|
|
||||||
definitions.insert(id, field_schema);
|
|
||||||
}
|
|
||||||
definitions.extend(field_definitions);
|
|
||||||
}
|
|
||||||
schema.min_items = Some(fields.fields.len() as u64);
|
|
||||||
schema.max_items = Some(fields.fields.len() as u64);
|
|
||||||
}
|
}
|
||||||
|
schema.min_items = Some(fields.fields.len() as u64);
|
||||||
|
schema.max_items = Some(fields.fields.len() as u64);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
InternalSchemaType::Array {
|
InternalSchemaType::Array {
|
||||||
@ -1335,6 +1421,7 @@ impl SchemaTypeInfo {
|
|||||||
ty_info: element_ty.clone(),
|
ty_info: element_ty.clone(),
|
||||||
field_data: None,
|
field_data: None,
|
||||||
stored_fields: None,
|
stored_fields: None,
|
||||||
|
reflect_type_data: None,
|
||||||
};
|
};
|
||||||
schema.items = Some(items_schema.to_ref_schema().into());
|
schema.items = Some(items_schema.to_ref_schema().into());
|
||||||
schema.min_items = min_size;
|
schema.min_items = min_size;
|
||||||
@ -1346,9 +1433,9 @@ impl SchemaTypeInfo {
|
|||||||
schema: _,
|
schema: _,
|
||||||
definitions: field_definitions,
|
definitions: field_definitions,
|
||||||
} = items_schema.to_definition();
|
} = items_schema.to_definition();
|
||||||
|
definitions.extend(field_definitions);
|
||||||
if let Some(id) = id {
|
if let Some(id) = id {
|
||||||
definitions.insert(id, items_schema);
|
definitions.insert(id, items_schema);
|
||||||
definitions.extend(field_definitions);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1360,16 +1447,7 @@ 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()
|
||||||
};
|
};
|
||||||
let optional_def = schema_optional.to_definition();
|
return schema_optional.to_definition();
|
||||||
let range = schema_optional.get_range();
|
|
||||||
schema = optional_def.schema;
|
|
||||||
schema.schema_type = self.into();
|
|
||||||
schema.minimum = range.min.get_inclusive();
|
|
||||||
schema.maximum = range.max.get_inclusive();
|
|
||||||
schema.exclusive_minimum = range.min.get_exclusive();
|
|
||||||
schema.exclusive_maximum = range.max.get_exclusive();
|
|
||||||
schema.description = self.get_docs();
|
|
||||||
schema.kind = Some(SchemaKind::Optional);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SchemaDefinition {
|
SchemaDefinition {
|
||||||
@ -1479,6 +1557,7 @@ where
|
|||||||
ty_info,
|
ty_info,
|
||||||
field_data: Some(field_data),
|
field_data: Some(field_data),
|
||||||
stored_fields,
|
stored_fields,
|
||||||
|
reflect_type_data: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1762,6 +1841,38 @@ mod tests {
|
|||||||
let _: JsonSchemaBevyType = type_info.to_definition().into();
|
let _: JsonSchemaBevyType = type_info.to_definition().into();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reflect_multiple_definitions() {
|
||||||
|
#[derive(Reflect, Default, Deserialize, Serialize)]
|
||||||
|
pub struct BaseStruct {
|
||||||
|
pub base_field: i32,
|
||||||
|
pub second_field: i32,
|
||||||
|
}
|
||||||
|
#[derive(Reflect, Default, Deserialize, Serialize)]
|
||||||
|
pub struct ArrayComponent {
|
||||||
|
pub array: [BaseStruct; 3],
|
||||||
|
}
|
||||||
|
#[derive(Reflect, Default, Deserialize, Serialize)]
|
||||||
|
pub struct ArrayComponentWithMoreVariants {
|
||||||
|
pub array: [BaseStruct; 3],
|
||||||
|
pub list: Vec<BaseStruct>,
|
||||||
|
pub optional: Option<BaseStruct>,
|
||||||
|
}
|
||||||
|
let type_info = TypeInformation::from(&ArrayComponent::get_type_registration())
|
||||||
|
.to_schema_type_info()
|
||||||
|
.to_definition();
|
||||||
|
let type_info_second =
|
||||||
|
TypeInformation::from(&ArrayComponentWithMoreVariants::get_type_registration())
|
||||||
|
.to_schema_type_info()
|
||||||
|
.to_definition();
|
||||||
|
assert_eq!(
|
||||||
|
type_info.definitions.len(),
|
||||||
|
type_info_second.definitions.len()
|
||||||
|
);
|
||||||
|
// let schema: JsonSchemaBevyType = type_info_second.into();
|
||||||
|
// eprintln!("{}", serde_json::to_string_pretty(&schema).expect(""));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn reflect_struct_with_hashmap() {
|
fn reflect_struct_with_hashmap() {
|
||||||
#[derive(Reflect, Default, Deserialize, Serialize)]
|
#[derive(Reflect, Default, Deserialize, Serialize)]
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user