Reflect info and stuff
This commit is contained in:
parent
648bd3d796
commit
2b20e8dae8
@ -2,12 +2,10 @@
|
||||
//! It tries to follow this standard: <https://json-schema.org/specification>
|
||||
use alloc::borrow::Cow;
|
||||
use bevy_platform::collections::HashMap;
|
||||
use bevy_reflect::{
|
||||
GetTypeRegistration, NamedField, OpaqueInfo, Reflect, TypeInfo, TypeRegistration, TypeRegistry,
|
||||
};
|
||||
use bevy_reflect::{GetTypeRegistration, Reflect, TypeRegistration, TypeRegistry};
|
||||
use core::any::TypeId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, Value};
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::schemas::{
|
||||
reflect_info::{SchemaInfoReflect, SchemaNumber},
|
||||
@ -60,72 +58,7 @@ impl From<(&TypeRegistration, &SchemaTypesMetadata)> for JsonSchemaBevyType {
|
||||
return JsonSchemaBevyType::default();
|
||||
};
|
||||
typed_schema.reflect_types = metadata.get_registered_reflect_types(reg);
|
||||
match type_info {
|
||||
TypeInfo::Struct(info) => {
|
||||
// typed_schema.properties = info
|
||||
// .iter()
|
||||
// .map(|field| (field.name().to_owned(), field.build_schema()))
|
||||
// .collect::<HashMap<_, _>>();
|
||||
// typed_schema.required = info
|
||||
// .iter()
|
||||
// .filter(|field| !field.type_path().starts_with("core::option::Option"))
|
||||
// .map(|f| f.name().to_owned())
|
||||
// .collect::<Vec<_>>();
|
||||
// typed_schema.additional_properties = Some(false);
|
||||
// typed_schema.schema_type = Some(SchemaType::Object);
|
||||
// typed_schema.kind = SchemaKind::Struct;
|
||||
}
|
||||
TypeInfo::Enum(info) => {
|
||||
typed_schema.kind = SchemaKind::Enum;
|
||||
typed_schema.one_of = info
|
||||
.iter()
|
||||
.map(|variant| variant.build_schema())
|
||||
.collect::<Vec<_>>();
|
||||
}
|
||||
TypeInfo::TupleStruct(info) => {
|
||||
typed_schema.schema_type = Some(SchemaTypeVariant::Single(SchemaType::Array));
|
||||
typed_schema.kind = SchemaKind::TupleStruct;
|
||||
// typed_schema.prefix_items = info
|
||||
// .iter()
|
||||
// .map(SchemaJsonReference::ref_type)
|
||||
// .collect::<Vec<_>>();
|
||||
// typed_schema.items = Some(false.into());
|
||||
}
|
||||
TypeInfo::List(info) => {
|
||||
typed_schema.schema_type = Some(SchemaTypeVariant::Single(SchemaType::Array));
|
||||
typed_schema.kind = SchemaKind::List;
|
||||
// typed_schema.items = info.item_ty().ref_type().into();
|
||||
}
|
||||
TypeInfo::Array(info) => {
|
||||
typed_schema.schema_type = Some(SchemaTypeVariant::Single(SchemaType::Array));
|
||||
typed_schema.kind = SchemaKind::Array;
|
||||
// typed_schema.items = info.item_ty().ref_type().into();
|
||||
}
|
||||
TypeInfo::Map(info) => {
|
||||
typed_schema.schema_type = Some(SchemaTypeVariant::Single(SchemaType::Array));
|
||||
typed_schema.kind = SchemaKind::Map;
|
||||
typed_schema.key_type = info.key_ty().ref_type().into();
|
||||
typed_schema.value_type = info.value_ty().ref_type().into();
|
||||
}
|
||||
TypeInfo::Tuple(info) => {
|
||||
typed_schema.schema_type = Some(SchemaTypeVariant::Single(SchemaType::Array));
|
||||
typed_schema.kind = SchemaKind::Tuple;
|
||||
// typed_schema.prefix_items = info
|
||||
// .iter()
|
||||
// .map(SchemaJsonReference::ref_type)
|
||||
// .collect::<Vec<_>>();
|
||||
// typed_schema.items = Some(false.into());
|
||||
}
|
||||
TypeInfo::Set(info) => {
|
||||
typed_schema.schema_type = Some(SchemaTypeVariant::Single(SchemaType::Array));
|
||||
typed_schema.kind = SchemaKind::Set;
|
||||
// typed_schema.items = info.value_ty().ref_type().into();
|
||||
}
|
||||
TypeInfo::Opaque(info) => {
|
||||
typed_schema.schema_type = Some(SchemaTypeVariant::Single(SchemaType::Array));
|
||||
typed_schema.kind = SchemaKind::Value;
|
||||
}
|
||||
};
|
||||
|
||||
*typed_schema
|
||||
}
|
||||
}
|
||||
@ -138,8 +71,10 @@ impl From<(&TypeRegistration, &SchemaTypesMetadata)> for JsonSchemaBevyType {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct JsonSchemaBevyType {
|
||||
/// Bevy specific field, short path of the type.
|
||||
#[serde(skip_serializing_if = "String::is_empty", default)]
|
||||
pub short_path: String,
|
||||
/// Bevy specific field, full path of the type.
|
||||
#[serde(skip_serializing_if = "String::is_empty", default)]
|
||||
pub type_path: String,
|
||||
/// Bevy specific field, path of the module that type is part of.
|
||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||
@ -156,12 +91,12 @@ pub struct JsonSchemaBevyType {
|
||||
///
|
||||
/// It contains type info of key of the Map.
|
||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||
pub key_type: Option<Value>,
|
||||
pub key_type: Option<JsonSchemaVariant>,
|
||||
/// Bevy specific field, provided when [`SchemaKind`] `kind` field is equal to [`SchemaKind::Map`].
|
||||
///
|
||||
/// It contains type info of value of the Map.
|
||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||
pub value_type: Option<Value>,
|
||||
pub value_type: Option<JsonSchemaVariant>,
|
||||
/// The type keyword is fundamental to JSON Schema. It specifies the data type for a schema.
|
||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||
#[serde(rename = "type")]
|
||||
@ -171,7 +106,7 @@ pub struct JsonSchemaBevyType {
|
||||
/// Validation with "additionalProperties" applies only to the child
|
||||
/// values of instance names that do not appear in the annotation results of either "properties" or "patternProperties".
|
||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||
pub additional_properties: Option<bool>,
|
||||
pub additional_properties: Option<JsonSchemaVariant>,
|
||||
/// Validation succeeds if, for each name that appears in both the instance and as a name
|
||||
/// within this keyword's value, the child instance for that name successfully validates
|
||||
/// against the corresponding schema.
|
||||
@ -202,12 +137,39 @@ pub struct JsonSchemaBevyType {
|
||||
/// array elements have been evaluated against this keyword's subschema.
|
||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||
pub items: Option<JsonSchemaVariant>,
|
||||
/// The value of this keyword MUST be a non-negative integer.
|
||||
/// An array instance is valid against "minItems" if its size is greater than,
|
||||
/// or equal to, the value of this keyword.
|
||||
/// Omitting this keyword has the same behavior as a value of 0.
|
||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||
pub min_items: Option<u64>,
|
||||
/// The value of this keyword MUST be a non-negative integer.
|
||||
/// An array instance is valid against "maxItems" if its size is less than,
|
||||
/// or equal to, the value of this keyword.
|
||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||
pub max_items: Option<u64>,
|
||||
/// The value of "minimum" MUST be a number,
|
||||
/// representing an inclusive lower limit for a numeric instance.
|
||||
/// If the instance is a number, then this keyword validates only
|
||||
/// if the instance is greater than or exactly equal to "minimum".
|
||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||
pub minimum: Option<SchemaNumber>,
|
||||
/// The value of "maximum" MUST be a number,
|
||||
/// representing an inclusive upper limit for a numeric instance.
|
||||
/// If the instance is a number, then this keyword validates only
|
||||
/// if the instance is less than or exactly equal to "maximum".
|
||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||
pub maximum: Option<SchemaNumber>,
|
||||
/// The value of "exclusiveMinimum" MUST be a number,
|
||||
/// representing an exclusive lower limit for a numeric instance.
|
||||
/// If the instance is a number, then this keyword validates only
|
||||
/// if the instance is greater than "exclusiveMinimum".
|
||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||
pub exclusive_minimum: Option<SchemaNumber>,
|
||||
/// The value of "exclusiveMaximum" MUST be a number,
|
||||
/// representing an exclusive upper limit for a numeric instance.
|
||||
/// If the instance is a number, then this keyword validates only
|
||||
/// if the instance is less than "exclusiveMaximum".
|
||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||
pub exclusive_maximum: Option<SchemaNumber>,
|
||||
/// Type description
|
||||
@ -215,20 +177,52 @@ pub struct JsonSchemaBevyType {
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
/// Represents different types of JSON Schema values that can be used in schema definitions.
|
||||
///
|
||||
/// This enum supports the various ways a JSON Schema property can be defined,
|
||||
/// including boolean values, constant values, and complex schema objects.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Reflect)]
|
||||
#[serde(untagged)]
|
||||
pub enum JsonSchemaVariant {
|
||||
/// A simple boolean value used in schema definitions.
|
||||
///
|
||||
/// This is commonly used for properties like `additionalProperties` where
|
||||
/// a boolean true/false indicates whether additional properties are allowed.
|
||||
BoolValue(bool),
|
||||
/// A constant value with an optional description.
|
||||
///
|
||||
/// This variant represents a JSON Schema `const` keyword, which specifies
|
||||
/// that a value must be exactly equal to the given constant value.
|
||||
Const {
|
||||
/// The constant value that must be matched exactly.
|
||||
#[reflect(ignore)]
|
||||
#[serde(rename = "const")]
|
||||
value: Value,
|
||||
/// Optional human-readable description of the constant value.
|
||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||
description: Option<String>,
|
||||
},
|
||||
/// A full JSON Schema definition.
|
||||
///
|
||||
/// This variant contains a complete schema object that defines the structure,
|
||||
/// validation rules, and metadata for a particular type or property.
|
||||
Schema(#[reflect(ignore)] Box<JsonSchemaBevyType>),
|
||||
}
|
||||
|
||||
impl JsonSchemaVariant {
|
||||
/// Creates a new constant value variant from any serializable type.
|
||||
///
|
||||
/// This is a convenience constructor that serializes the provided value
|
||||
/// to JSON and wraps it in the `Const` variant with an optional description.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `serializable` - Any value that implements `Serialize`
|
||||
/// * `description` - Optional description for the constant value
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A new `JsonSchemaVariant::Const` with the serialized value
|
||||
pub fn const_value(serializable: impl Serialize, description: Option<String>) -> Self {
|
||||
Self::Const {
|
||||
value: serde_json::to_value(serializable).unwrap_or_default(),
|
||||
@ -264,10 +258,20 @@ pub enum SchemaKind {
|
||||
/// Optional type
|
||||
Optional,
|
||||
}
|
||||
/// Represents the possible type variants for a JSON Schema.
|
||||
///
|
||||
/// In JSON Schema, the `type` keyword can either specify a single type
|
||||
/// or an array of types to allow multiple valid types for a property.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Reflect, Eq, PartialOrd, Ord)]
|
||||
#[serde(untagged)]
|
||||
pub enum SchemaTypeVariant {
|
||||
/// A single schema type (e.g., "string", "number", "object").
|
||||
/// This is the most common case where a property has exactly one valid type.
|
||||
Single(SchemaType),
|
||||
/// Multiple schema types allowed for the same property.
|
||||
/// This variant is used when a property can accept multiple types,
|
||||
/// such as allowing both "string" and "number" for the same field.
|
||||
/// In Rust case it most often means it is a Option type.
|
||||
Multiple(Vec<SchemaType>),
|
||||
}
|
||||
|
||||
@ -346,58 +350,6 @@ impl SchemaType {
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper trait for generating json schema reference
|
||||
trait SchemaJsonReference {
|
||||
/// Reference to another type in schema.
|
||||
/// The value `$ref` is a URI-reference that is resolved against the schema.
|
||||
fn ref_type(self) -> Value;
|
||||
}
|
||||
|
||||
/// Helper trait for mapping bevy type path into json schema type
|
||||
pub trait SchemaJsonType {
|
||||
/// Bevy Reflect type path
|
||||
fn get_type_path(&self) -> &'static str;
|
||||
|
||||
/// JSON Schema type keyword from Bevy reflect type path into
|
||||
fn map_json_type(&self) -> SchemaType {
|
||||
match self.get_type_path() {
|
||||
"bool" => SchemaType::Boolean,
|
||||
"u8" | "u16" | "u32" | "u64" | "u128" | "usize" => SchemaType::Integer,
|
||||
"i8" | "i16" | "i32" | "i64" | "i128" | "isize" => SchemaType::Integer,
|
||||
"f32" | "f64" => SchemaType::Number,
|
||||
"char" | "str" | "alloc::string::String" => SchemaType::String,
|
||||
_ => SchemaType::Object,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SchemaJsonType for OpaqueInfo {
|
||||
fn get_type_path(&self) -> &'static str {
|
||||
self.type_path()
|
||||
}
|
||||
}
|
||||
|
||||
impl SchemaJsonReference for &bevy_reflect::Type {
|
||||
fn ref_type(self) -> Value {
|
||||
let path = self.path();
|
||||
json!({"type": json!({ "$ref": format!("#/$defs/{path}") })})
|
||||
}
|
||||
}
|
||||
|
||||
impl SchemaJsonReference for &bevy_reflect::UnnamedField {
|
||||
fn ref_type(self) -> Value {
|
||||
let path = self.type_path();
|
||||
json!({"type": json!({ "$ref": format!("#/$defs/{path}") })})
|
||||
}
|
||||
}
|
||||
|
||||
impl SchemaJsonReference for &NamedField {
|
||||
fn ref_type(self) -> Value {
|
||||
let type_path = self.type_path();
|
||||
json!({"type": json!({ "$ref": format!("#/$defs/{type_path}") }), "typePath": self.name()})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@ -407,6 +359,7 @@ mod tests {
|
||||
use bevy_ecs::{component::Component, reflect::AppTypeRegistry, resource::Resource};
|
||||
use bevy_reflect::prelude::ReflectDefault;
|
||||
use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize};
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn reflect_export_struct() {
|
||||
@ -654,8 +607,8 @@ mod tests {
|
||||
"minimum": 0,
|
||||
"type": "integer",
|
||||
"description": "Test doc",
|
||||
"shortPath": "",
|
||||
"typePath": "",
|
||||
"shortPath": "u16",
|
||||
"typePath": "u16",
|
||||
|
||||
},
|
||||
},
|
||||
|
@ -14,12 +14,12 @@ pub mod json_schema;
|
||||
pub mod open_rpc;
|
||||
pub mod reflect_info;
|
||||
|
||||
/// Holds mapping of reflect [type data](TypeData) to strings,
|
||||
/// Holds mapping of reflect [type data](TypeData) to human-readable type names,
|
||||
/// later on used in Bevy Json Schema.
|
||||
#[derive(Debug, Resource, Reflect)]
|
||||
#[reflect(Resource)]
|
||||
pub struct SchemaTypesMetadata {
|
||||
/// Type Data id mapping to strings.
|
||||
/// Type Data id mapping to human-readable type names.
|
||||
pub type_data_map: HashMap<TypeId, String>,
|
||||
}
|
||||
|
||||
@ -42,7 +42,7 @@ impl Default for SchemaTypesMetadata {
|
||||
}
|
||||
|
||||
impl SchemaTypesMetadata {
|
||||
/// Map `TypeId` of `TypeData` to string
|
||||
/// Map `TypeId` of `TypeData` to a human-readable type name
|
||||
pub fn map_type_data<T: TypeData>(&mut self, name: impl Into<String>) {
|
||||
self.type_data_map.insert(TypeId::of::<T>(), name.into());
|
||||
}
|
||||
@ -55,12 +55,12 @@ impl SchemaTypesMetadata {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Checks if slice contains string value that matches checked `TypeData`
|
||||
/// Checks if slice contains a type name that matches the checked `TypeData`
|
||||
pub fn has_type_data<T: TypeData>(&self, types_string_slice: &[String]) -> bool {
|
||||
self.has_type_data_by_id(TypeId::of::<T>(), types_string_slice)
|
||||
}
|
||||
|
||||
/// Checks if slice contains string value that matches checked `TypeData` by id.
|
||||
/// Checks if slice contains a type name that matches the checked `TypeData` by id.
|
||||
pub fn has_type_data_by_id(&self, id: TypeId, types_string_slice: &[String]) -> bool {
|
||||
self.type_data_map
|
||||
.get(&id)
|
||||
|
@ -1,12 +1,9 @@
|
||||
//! Module containing information about reflected types.
|
||||
use bevy_reflect::{
|
||||
GenericInfo, NamedField, Reflect, StructVariantInfo, TypeInfo, UnnamedField, VariantInfo,
|
||||
};
|
||||
use bevy_reflect::{GenericInfo, NamedField, Reflect, TypeInfo, UnnamedField, VariantInfo};
|
||||
use core::any::TypeId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
any::Any,
|
||||
f32::consts::PI,
|
||||
fmt::Debug,
|
||||
ops::{Bound, RangeBounds},
|
||||
};
|
||||
@ -16,12 +13,12 @@ use crate::schemas::json_schema::{
|
||||
};
|
||||
|
||||
/// Enum representing a number in schema.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Reflect)]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Reflect, PartialOrd)]
|
||||
#[serde(untagged)]
|
||||
pub enum SchemaNumber {
|
||||
/// Integer value.
|
||||
Int(i128),
|
||||
/// Always finite.
|
||||
/// Floating-point value.
|
||||
Float(f64),
|
||||
}
|
||||
|
||||
@ -86,16 +83,74 @@ impl From<isize> for SchemaNumber {
|
||||
SchemaNumber::Int(value as i128)
|
||||
}
|
||||
}
|
||||
/// Represents a bound value that can be either inclusive or exclusive.
|
||||
/// Used to define range constraints for numeric types in JSON schema.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Copy, PartialEq, Reflect)]
|
||||
pub enum BoundValue {
|
||||
/// An inclusive bound that includes the specified value in the range.
|
||||
Inclusive(SchemaNumber),
|
||||
/// An exclusive bound that excludes the specified value from the range.
|
||||
Exclusive(SchemaNumber),
|
||||
}
|
||||
|
||||
impl BoundValue {
|
||||
/// Returns the value if this is an inclusive bound, otherwise returns None.
|
||||
fn get_inclusive(&self) -> Option<SchemaNumber> {
|
||||
match self {
|
||||
BoundValue::Inclusive(v) => Some(*v),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
/// Returns the value if this is an exclusive bound, otherwise returns None.
|
||||
fn get_exclusive(&self) -> Option<SchemaNumber> {
|
||||
match self {
|
||||
BoundValue::Exclusive(v) => Some(*v),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents minimum and maximum value constraints for numeric types.
|
||||
/// Used to define valid ranges for schema validation.
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, Copy, PartialEq, Reflect)]
|
||||
pub struct MinMaxValues {
|
||||
pub min: Option<SchemaNumber>,
|
||||
pub min_exclusive: Option<SchemaNumber>,
|
||||
pub max: Option<SchemaNumber>,
|
||||
pub max_exclusive: Option<SchemaNumber>,
|
||||
/// The minimum bound value, if any.
|
||||
pub min: Option<BoundValue>,
|
||||
/// The maximum bound value, if any.
|
||||
pub max: Option<BoundValue>,
|
||||
}
|
||||
|
||||
impl MinMaxValues {
|
||||
/// Checks if a given value falls within the defined range constraints.
|
||||
/// Returns true if the value is within bounds, false otherwise.
|
||||
pub fn in_range(&self, value: SchemaNumber) -> bool {
|
||||
if let Some(min) = self.min {
|
||||
if let Some(min_value) = min.get_inclusive() {
|
||||
if value < min_value {
|
||||
return false;
|
||||
}
|
||||
} else if let Some(min_value) = min.get_exclusive() {
|
||||
if value <= min_value {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(max) = self.max {
|
||||
if let Some(max_value) = max.get_inclusive() {
|
||||
if value > max_value {
|
||||
return false;
|
||||
}
|
||||
} else if let Some(max_value) = max.get_exclusive() {
|
||||
if value >= max_value {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
/// Creates MinMaxValues from a reflected range type.
|
||||
/// Attempts to downcast the reflected value to the specified range type T
|
||||
/// and extract its bounds.
|
||||
pub fn from_reflect<T, Y>(reflect_val: &dyn Reflect) -> Option<MinMaxValues>
|
||||
where
|
||||
T: 'static + RangeBounds<Y>,
|
||||
@ -110,299 +165,333 @@ impl MinMaxValues {
|
||||
)))
|
||||
}
|
||||
|
||||
/// Creates MinMaxValues from range bounds and a type identifier.
|
||||
/// Takes a tuple containing start bound, end bound, and TypeId to construct
|
||||
/// the appropriate range constraints.
|
||||
pub fn from_range<T>(value: (Bound<&T>, Bound<&T>, TypeId)) -> MinMaxValues
|
||||
where
|
||||
T: 'static + Into<SchemaNumber> + Copy + Debug,
|
||||
{
|
||||
let base: MinMaxValues = value.2.into();
|
||||
let (min, min_exclusive) = match value.0 {
|
||||
Bound::Included(v) => (Some((*v).into()), None),
|
||||
Bound::Excluded(v) => (None, Some((*v).into())),
|
||||
Bound::Unbounded => (base.min, None),
|
||||
let min = match value.0 {
|
||||
Bound::Included(v) => Some(BoundValue::Inclusive((*v).into())),
|
||||
Bound::Excluded(v) => Some(BoundValue::Exclusive((*v).into())),
|
||||
Bound::Unbounded => base.min,
|
||||
};
|
||||
let (max, max_exclusive) = match value.1 {
|
||||
Bound::Included(v) => (Some((*v).into()), None),
|
||||
Bound::Excluded(v) => (None, Some((*v).into())),
|
||||
Bound::Unbounded => (base.max, None),
|
||||
let max = match value.1 {
|
||||
Bound::Included(v) => Some(BoundValue::Inclusive((*v).into())),
|
||||
Bound::Excluded(v) => Some(BoundValue::Exclusive((*v).into())),
|
||||
Bound::Unbounded => base.max,
|
||||
};
|
||||
|
||||
Self {
|
||||
min,
|
||||
min_exclusive,
|
||||
max,
|
||||
max_exclusive,
|
||||
}
|
||||
Self { min, max }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TypeId> for MinMaxValues {
|
||||
fn from(value: TypeId) -> Self {
|
||||
let mut min: Option<SchemaNumber> = None;
|
||||
let mut max: Option<SchemaNumber> = None;
|
||||
let mut min: Option<BoundValue> = None;
|
||||
let mut max: Option<BoundValue> = None;
|
||||
if value.eq(&TypeId::of::<u8>()) {
|
||||
min = Some(0.into());
|
||||
max = Some(u8::MAX.into());
|
||||
min = Some(BoundValue::Inclusive(0.into()));
|
||||
max = Some(BoundValue::Inclusive(u8::MAX.into()));
|
||||
} else if value.eq(&TypeId::of::<u16>()) {
|
||||
min = Some(0.into());
|
||||
max = Some(u16::MAX.into());
|
||||
min = Some(BoundValue::Inclusive(0.into()));
|
||||
max = Some(BoundValue::Inclusive(u16::MAX.into()));
|
||||
} else if value.eq(&TypeId::of::<u32>()) {
|
||||
min = Some(0.into());
|
||||
max = Some(u32::MAX.into());
|
||||
min = Some(BoundValue::Inclusive(0.into()));
|
||||
max = Some(BoundValue::Inclusive(u32::MAX.into()));
|
||||
} else if value.eq(&TypeId::of::<u64>()) {
|
||||
min = Some(0.into());
|
||||
min = Some(BoundValue::Inclusive(0.into()));
|
||||
} else if value.eq(&TypeId::of::<u128>()) {
|
||||
min = Some(0.into());
|
||||
min = Some(BoundValue::Inclusive(0.into()));
|
||||
} else if value.eq(&TypeId::of::<usize>()) {
|
||||
min = Some(0.into());
|
||||
min = Some(BoundValue::Inclusive(0.into()));
|
||||
} else if value.eq(&TypeId::of::<i8>()) {
|
||||
min = Some(i8::MIN.into());
|
||||
max = Some(i8::MAX.into());
|
||||
min = Some(BoundValue::Inclusive(i8::MIN.into()));
|
||||
max = Some(BoundValue::Inclusive(i8::MAX.into()));
|
||||
} else if value.eq(&TypeId::of::<i16>()) {
|
||||
min = Some(i16::MIN.into());
|
||||
max = Some(i16::MAX.into());
|
||||
min = Some(BoundValue::Inclusive(i16::MIN.into()));
|
||||
max = Some(BoundValue::Inclusive(i16::MAX.into()));
|
||||
} else if value.eq(&TypeId::of::<i32>()) {
|
||||
min = Some(i32::MIN.into());
|
||||
max = Some(i32::MAX.into());
|
||||
}
|
||||
MinMaxValues {
|
||||
min,
|
||||
max,
|
||||
min_exclusive: None,
|
||||
max_exclusive: None,
|
||||
min = Some(BoundValue::Inclusive(i32::MIN.into()));
|
||||
max = Some(BoundValue::Inclusive(i32::MAX.into()));
|
||||
}
|
||||
MinMaxValues { min, max }
|
||||
}
|
||||
}
|
||||
|
||||
/// Enum representing the internal schema type information for different Rust types.
|
||||
/// This enum categorizes how different types should be represented in JSON schema.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub enum InternalSchemaType {
|
||||
Primitive {
|
||||
range: MinMaxValues,
|
||||
schema_type: SchemaType,
|
||||
/// Represents array-like types (Vec, arrays, lists, sets).
|
||||
Array {
|
||||
/// The TypeId of the element type contained in the array.
|
||||
element_type: TypeId,
|
||||
/// Optional type information for the element type.
|
||||
element_type_info: Option<TypeInfo>,
|
||||
/// Minimum number of elements allowed in the array.
|
||||
min_size: Option<u64>,
|
||||
/// Maximum number of elements allowed in the array.
|
||||
max_size: Option<u64>,
|
||||
},
|
||||
/// Holds all variants of an enum type.
|
||||
EnumHolder(Vec<VariantInfo>),
|
||||
/// Represents a single enum variant.
|
||||
EnumVariant(VariantInfo),
|
||||
/// Holds named fields for struct types.
|
||||
NamedFieldsHolder(Vec<NamedField>),
|
||||
/// Holds unnamed fields for tuple and tuple struct types.
|
||||
UnnamedFieldsHolder(Vec<UnnamedField>),
|
||||
/// Represents an Optional type (e.g., Option<T>).
|
||||
Optional {
|
||||
/// Generic information about the wrapped type T in Option<T>.
|
||||
generic: GenericInfo,
|
||||
range: MinMaxValues,
|
||||
schema_type: SchemaType,
|
||||
},
|
||||
/// Represents a Map type (e.g., HashMap<K, V>).
|
||||
Map {
|
||||
/// The TypeId of the key type contained in the map.
|
||||
key_type: TypeId,
|
||||
/// Optional type information for the key type.
|
||||
key_type_info: Option<TypeInfo>,
|
||||
/// The TypeId of the value type contained in the map.
|
||||
value_type: TypeId,
|
||||
/// Optional type information for the value type.
|
||||
value_type_info: Option<TypeInfo>,
|
||||
},
|
||||
/// Default variant for regular primitive types and other simple types.
|
||||
#[default]
|
||||
Regular,
|
||||
}
|
||||
|
||||
impl From<&SchemaTypeInfo> for Option<SchemaTypeVariant> {
|
||||
fn from(value: &SchemaTypeInfo) -> Self {
|
||||
match &value.internal_type {
|
||||
InternalSchemaType::Map { .. } => Some(SchemaTypeVariant::Single(SchemaType::Object)),
|
||||
InternalSchemaType::Array { .. } => Some(SchemaTypeVariant::Single(SchemaType::Array)),
|
||||
InternalSchemaType::EnumHolder { .. } => None,
|
||||
InternalSchemaType::EnumVariant(variant) => match variant {
|
||||
VariantInfo::Unit(_) => Some(SchemaTypeVariant::Single(SchemaType::String)),
|
||||
_ => Some(SchemaTypeVariant::Single(SchemaType::Object)),
|
||||
},
|
||||
InternalSchemaType::NamedFieldsHolder { .. } => {
|
||||
Some(SchemaTypeVariant::Single(SchemaType::Object))
|
||||
}
|
||||
InternalSchemaType::UnnamedFieldsHolder(unnamed_fields) => {
|
||||
if unnamed_fields.len() == 1 {
|
||||
(&unnamed_fields[0].build_schema_type_info()).into()
|
||||
} else {
|
||||
Some(SchemaTypeVariant::Single(SchemaType::Array))
|
||||
}
|
||||
}
|
||||
InternalSchemaType::Optional { generic } => {
|
||||
let schema_type = if let Some(SchemaTypeVariant::Single(gen_schema)) =
|
||||
(&generic.ty().id().build_schema_type_info()).into()
|
||||
{
|
||||
gen_schema
|
||||
} else {
|
||||
SchemaType::Object
|
||||
};
|
||||
Some(SchemaTypeVariant::Multiple(vec![
|
||||
schema_type,
|
||||
SchemaType::Null,
|
||||
]))
|
||||
}
|
||||
InternalSchemaType::Regular => match &value.type_id {
|
||||
Some(s) => Some(SchemaTypeVariant::Single(s.clone().into())),
|
||||
_ => 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 {
|
||||
/// The internal categorization of the schema type.
|
||||
pub internal_type: InternalSchemaType,
|
||||
/// Optional documentation string extracted from the type.
|
||||
pub documentation: Option<String>,
|
||||
/// The kind of schema (struct, enum, value, etc.).
|
||||
pub kind: SchemaKind,
|
||||
/// Optional Bevy reflection type information.
|
||||
pub type_info: Option<TypeInfo>,
|
||||
/// Optional TypeId for the type.
|
||||
pub type_id: Option<TypeId>,
|
||||
/// Numeric range constraints for the type.
|
||||
pub range: MinMaxValues,
|
||||
}
|
||||
|
||||
impl Into<JsonSchemaVariant> for SchemaTypeInfo {
|
||||
fn into(self) -> JsonSchemaVariant {
|
||||
let schema_type: Option<SchemaTypeVariant> = (&self).into();
|
||||
let mut schema = JsonSchemaBevyType {
|
||||
schema_type: schema_type.clone(),
|
||||
kind: self.kind.clone(),
|
||||
description: self.documentation.clone(),
|
||||
type_path: self
|
||||
.type_info
|
||||
.as_ref()
|
||||
.and_then(|s| Some(s.type_path_table().path().to_owned()))
|
||||
.unwrap_or_default(),
|
||||
short_path: self
|
||||
.type_info
|
||||
.as_ref()
|
||||
.and_then(|s| Some(s.type_path_table().short_path().to_owned()))
|
||||
.unwrap_or_default(),
|
||||
crate_name: self
|
||||
.type_info
|
||||
.as_ref()
|
||||
.and_then(|s| s.type_path_table().crate_name().map(str::to_owned)),
|
||||
module_path: self
|
||||
.type_info
|
||||
.as_ref()
|
||||
.and_then(|s| s.type_path_table().module_path().map(str::to_owned)),
|
||||
minimum: self.range.min.as_ref().and_then(|r| r.get_inclusive()),
|
||||
maximum: self.range.max.as_ref().and_then(|r| r.get_inclusive()),
|
||||
exclusive_minimum: self.range.min.as_ref().and_then(|r| r.get_exclusive()),
|
||||
exclusive_maximum: self.range.max.as_ref().and_then(|r| r.get_exclusive()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
match self.internal_type {
|
||||
InternalSchemaType::Primitive { range, schema_type } => {
|
||||
JsonSchemaVariant::Schema(Box::new(JsonSchemaBevyType {
|
||||
kind: self.kind,
|
||||
schema_type: Some(SchemaTypeVariant::Single(schema_type)),
|
||||
minimum: range.min,
|
||||
maximum: range.max,
|
||||
exclusive_minimum: range.min_exclusive,
|
||||
exclusive_maximum: range.max_exclusive,
|
||||
description: self.documentation,
|
||||
..Default::default()
|
||||
}))
|
||||
InternalSchemaType::Map {
|
||||
key_type,
|
||||
key_type_info,
|
||||
value_type,
|
||||
value_type_info,
|
||||
} => {
|
||||
schema.additional_properties = match &value_type_info {
|
||||
Some(info) => Some(info.build_schema()),
|
||||
None => Some(value_type.build_schema()),
|
||||
};
|
||||
schema.value_type = match &value_type_info {
|
||||
Some(info) => Some(info.build_schema()),
|
||||
None => Some(value_type.build_schema()),
|
||||
};
|
||||
schema.key_type = match &key_type_info {
|
||||
Some(info) => Some(info.build_schema()),
|
||||
None => Some(key_type.build_schema()),
|
||||
};
|
||||
}
|
||||
InternalSchemaType::Regular => {
|
||||
JsonSchemaVariant::Schema(Box::new(JsonSchemaBevyType {
|
||||
type_path: self
|
||||
.type_info
|
||||
.as_ref()
|
||||
.and_then(|s| Some(s.type_path_table().path().to_owned()))
|
||||
.unwrap_or_default(),
|
||||
short_path: self
|
||||
.type_info
|
||||
.as_ref()
|
||||
.and_then(|s| Some(s.type_path_table().short_path().to_owned()))
|
||||
.unwrap_or_default(),
|
||||
crate_name: self
|
||||
.type_info
|
||||
.as_ref()
|
||||
.and_then(|s| s.type_path_table().crate_name().map(str::to_owned)),
|
||||
module_path: self
|
||||
.type_info
|
||||
.as_ref()
|
||||
.and_then(|s| s.type_path_table().module_path().map(str::to_owned)),
|
||||
description: self.documentation,
|
||||
kind: self.kind,
|
||||
..Default::default()
|
||||
}))
|
||||
InternalSchemaType::Regular => {}
|
||||
InternalSchemaType::EnumHolder(variants) => {
|
||||
schema.one_of = variants.iter().map(|v| v.build_schema()).collect();
|
||||
}
|
||||
InternalSchemaType::EnumVariant(variant_info) => match &variant_info {
|
||||
VariantInfo::Struct(struct_variant_info) => {
|
||||
schema.kind = SchemaKind::Value;
|
||||
schema.schema_type = Some(SchemaTypeVariant::Single(SchemaType::Object));
|
||||
let internal_type = InternalSchemaType::NamedFieldsHolder(
|
||||
struct_variant_info.iter().cloned().collect(),
|
||||
);
|
||||
let schema = SchemaTypeInfo {
|
||||
let schema_field = SchemaTypeInfo {
|
||||
internal_type,
|
||||
documentation: variant_info.to_description(),
|
||||
kind: SchemaKind::Struct,
|
||||
type_info: None,
|
||||
type_id: Some(struct_variant_info.type_id()),
|
||||
range: MinMaxValues::default(),
|
||||
};
|
||||
JsonSchemaVariant::Schema(Box::new(JsonSchemaBevyType {
|
||||
description: self.documentation,
|
||||
kind: SchemaKind::Value,
|
||||
schema_type: Some(SchemaTypeVariant::Single(SchemaType::Object)),
|
||||
properties: [(variant_info.name().to_string(), schema.into())].into(),
|
||||
required: vec![variant_info.name().to_string()],
|
||||
..Default::default()
|
||||
}))
|
||||
|
||||
schema.properties =
|
||||
[(variant_info.name().to_string(), schema_field.into())].into();
|
||||
schema.required = vec![variant_info.name().to_string()];
|
||||
}
|
||||
VariantInfo::Tuple(tuple_variant_info) => {
|
||||
schema.kind = SchemaKind::Value;
|
||||
schema.schema_type = Some(SchemaTypeVariant::Single(SchemaType::Object));
|
||||
let internal_type = InternalSchemaType::UnnamedFieldsHolder(
|
||||
tuple_variant_info.iter().cloned().collect(),
|
||||
);
|
||||
let schema = SchemaTypeInfo {
|
||||
let schema_field = SchemaTypeInfo {
|
||||
internal_type,
|
||||
documentation: variant_info.to_description(),
|
||||
kind: SchemaKind::Tuple,
|
||||
type_info: None,
|
||||
type_id: Some(tuple_variant_info.type_id()),
|
||||
range: MinMaxValues::default(),
|
||||
};
|
||||
JsonSchemaVariant::Schema(Box::new(JsonSchemaBevyType {
|
||||
description: self.documentation,
|
||||
kind: SchemaKind::Value,
|
||||
schema_type: Some(SchemaTypeVariant::Single(SchemaType::Object)),
|
||||
properties: [(variant_info.name().to_string(), schema.into())].into(),
|
||||
required: vec![variant_info.name().to_string()],
|
||||
..Default::default()
|
||||
}))
|
||||
schema.properties =
|
||||
[(variant_info.name().to_string(), schema_field.into())].into();
|
||||
schema.required = vec![variant_info.name().to_string()];
|
||||
}
|
||||
VariantInfo::Unit(unit_variant_info) => {
|
||||
return JsonSchemaVariant::const_value(
|
||||
unit_variant_info.name().to_string(),
|
||||
schema.description.clone(),
|
||||
);
|
||||
}
|
||||
VariantInfo::Unit(unit_variant_info) => JsonSchemaVariant::const_value(
|
||||
unit_variant_info.name().to_string(),
|
||||
self.documentation,
|
||||
),
|
||||
},
|
||||
InternalSchemaType::NamedFieldsHolder(named_fields) => {
|
||||
JsonSchemaVariant::Schema(Box::new(JsonSchemaBevyType {
|
||||
kind: self.kind,
|
||||
schema_type: Some(SchemaTypeVariant::Single(SchemaType::Object)),
|
||||
description: self.documentation,
|
||||
additional_properties: Some(false),
|
||||
properties: named_fields
|
||||
.iter()
|
||||
.map(|field| (field.name().to_string(), field.build_schema()))
|
||||
.collect(),
|
||||
required: named_fields
|
||||
.iter()
|
||||
.map(|field| field.name().to_string())
|
||||
.collect(),
|
||||
type_path: self
|
||||
.type_info
|
||||
.as_ref()
|
||||
.and_then(|s| Some(s.type_path_table().path().to_owned()))
|
||||
.unwrap_or_default(),
|
||||
short_path: self
|
||||
.type_info
|
||||
.as_ref()
|
||||
.and_then(|s| Some(s.type_path_table().short_path().to_owned()))
|
||||
.unwrap_or_default(),
|
||||
crate_name: self
|
||||
.type_info
|
||||
.as_ref()
|
||||
.and_then(|s| s.type_path_table().crate_name().map(str::to_owned)),
|
||||
module_path: self
|
||||
.type_info
|
||||
.as_ref()
|
||||
.and_then(|s| s.type_path_table().module_path().map(str::to_owned)),
|
||||
..Default::default()
|
||||
}))
|
||||
schema.additional_properties = Some(JsonSchemaVariant::BoolValue(false));
|
||||
schema.schema_type = Some(SchemaTypeVariant::Single(SchemaType::Object));
|
||||
schema.properties = named_fields
|
||||
.iter()
|
||||
.map(|field| (field.name().to_string(), field.build_schema()))
|
||||
.collect();
|
||||
schema.required = named_fields
|
||||
.iter()
|
||||
.map(|field| field.name().to_string())
|
||||
.collect();
|
||||
}
|
||||
InternalSchemaType::UnnamedFieldsHolder(unnamed_fields) => {
|
||||
if unnamed_fields.len() == 1 {
|
||||
let s = unnamed_fields[0].build_schema();
|
||||
if let JsonSchemaVariant::Schema(mut schema) = s {
|
||||
schema.kind = self.kind;
|
||||
schema.description = self.documentation;
|
||||
|
||||
schema.type_path = self
|
||||
.type_info
|
||||
.as_ref()
|
||||
.and_then(|s| Some(s.type_path_table().path().to_owned()))
|
||||
.unwrap_or_default();
|
||||
schema.short_path = self
|
||||
.type_info
|
||||
.as_ref()
|
||||
.and_then(|s| Some(s.type_path_table().short_path().to_owned()))
|
||||
.unwrap_or_default();
|
||||
schema.crate_name = self
|
||||
.type_info
|
||||
.as_ref()
|
||||
.and_then(|s| s.type_path_table().crate_name().map(str::to_owned));
|
||||
schema.module_path = self
|
||||
.type_info
|
||||
.as_ref()
|
||||
.and_then(|s| s.type_path_table().module_path().map(str::to_owned));
|
||||
JsonSchemaVariant::Schema(schema)
|
||||
let new_schema = unnamed_fields[0].build_schema();
|
||||
if let JsonSchemaVariant::Schema(new_schema_type) = new_schema {
|
||||
schema = *new_schema_type;
|
||||
schema.schema_type = schema_type.clone();
|
||||
schema.description = self.documentation.clone();
|
||||
} else {
|
||||
s
|
||||
return new_schema;
|
||||
}
|
||||
} else {
|
||||
JsonSchemaVariant::Schema(Box::new(JsonSchemaBevyType {
|
||||
description: self.documentation,
|
||||
kind: self.kind,
|
||||
additional_properties: Some(false),
|
||||
prefix_items: unnamed_fields.iter().map(|s| s.build_schema()).collect(),
|
||||
type_path: self
|
||||
.type_info
|
||||
.as_ref()
|
||||
.and_then(|s| Some(s.type_path_table().path().to_owned()))
|
||||
.unwrap_or_default(),
|
||||
short_path: self
|
||||
.type_info
|
||||
.as_ref()
|
||||
.and_then(|s| Some(s.type_path_table().short_path().to_owned()))
|
||||
.unwrap_or_default(),
|
||||
crate_name: self
|
||||
.type_info
|
||||
.as_ref()
|
||||
.and_then(|s| s.type_path_table().crate_name().map(str::to_owned)),
|
||||
module_path: self
|
||||
.type_info
|
||||
.as_ref()
|
||||
.and_then(|s| s.type_path_table().module_path().map(str::to_owned)),
|
||||
..Default::default()
|
||||
}))
|
||||
schema.prefix_items = unnamed_fields.iter().map(|s| s.build_schema()).collect();
|
||||
schema.min_items = Some(unnamed_fields.len() as u64);
|
||||
schema.max_items = Some(unnamed_fields.len() as u64);
|
||||
}
|
||||
}
|
||||
InternalSchemaType::Optional {
|
||||
generic,
|
||||
range,
|
||||
schema_type,
|
||||
InternalSchemaType::Array {
|
||||
element_type,
|
||||
element_type_info,
|
||||
min_size,
|
||||
max_size,
|
||||
} => {
|
||||
let items_schema = match element_type_info {
|
||||
None => element_type.build_schema(),
|
||||
Some(info) => info.build_schema(),
|
||||
};
|
||||
schema.items = Some(items_schema);
|
||||
schema.min_items = min_size;
|
||||
schema.max_items = max_size;
|
||||
}
|
||||
InternalSchemaType::Optional { generic } => {
|
||||
let schema_variant = generic.ty().id().build_schema();
|
||||
if let JsonSchemaVariant::Schema(mut value) = schema_variant {
|
||||
value.minimum = range.min;
|
||||
value.maximum = range.max;
|
||||
value.exclusive_minimum = range.min_exclusive;
|
||||
value.exclusive_maximum = range.max_exclusive;
|
||||
value.description = self.documentation;
|
||||
value.kind = SchemaKind::Optional;
|
||||
value.schema_type = Some(SchemaTypeVariant::Multiple(vec![
|
||||
schema_type,
|
||||
SchemaType::Null,
|
||||
]));
|
||||
JsonSchemaVariant::Schema(value)
|
||||
if let JsonSchemaVariant::Schema(value) = schema_variant {
|
||||
schema = *value;
|
||||
schema.schema_type = schema_type.clone();
|
||||
schema.minimum = self.range.min.as_ref().and_then(|r| r.get_inclusive());
|
||||
schema.maximum = self.range.max.as_ref().and_then(|r| r.get_inclusive());
|
||||
schema.exclusive_minimum =
|
||||
self.range.min.as_ref().and_then(|r| r.get_exclusive());
|
||||
schema.exclusive_maximum =
|
||||
self.range.max.as_ref().and_then(|r| r.get_exclusive());
|
||||
schema.description = self.documentation;
|
||||
schema.kind = SchemaKind::Optional;
|
||||
} else {
|
||||
schema_variant
|
||||
return schema_variant;
|
||||
}
|
||||
}
|
||||
}
|
||||
JsonSchemaVariant::Schema(Box::new(schema))
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait that builds the type information based on the reflected data.
|
||||
pub trait SchemaInfoReflect {
|
||||
/// Returns the optional type information of the schema.
|
||||
fn try_get_optional_info(&self) -> Option<GenericInfo> {
|
||||
let type_info = self.try_get_type_info()?;
|
||||
let TypeInfo::Enum(enum_info) = type_info else {
|
||||
@ -422,6 +511,28 @@ pub trait SchemaInfoReflect {
|
||||
fn try_get_type_info(&self) -> Option<TypeInfo>;
|
||||
/// Returns the Bevy kind of the schema.
|
||||
fn get_kind(&self) -> SchemaKind {
|
||||
if self.try_get_optional_info().is_some() {
|
||||
return SchemaKind::Optional;
|
||||
};
|
||||
if SchemaType::try_get_primitive_type_from_type_id(self.get_type()).is_some() {
|
||||
return SchemaKind::Value;
|
||||
}
|
||||
match self.try_get_type_info() {
|
||||
Some(type_info) => {
|
||||
return match type_info {
|
||||
TypeInfo::Struct(_) => SchemaKind::Struct,
|
||||
TypeInfo::TupleStruct(_) => SchemaKind::TupleStruct,
|
||||
TypeInfo::Tuple(_) => SchemaKind::Tuple,
|
||||
TypeInfo::List(_) => SchemaKind::List,
|
||||
TypeInfo::Array(_) => SchemaKind::Array,
|
||||
TypeInfo::Map(_) => SchemaKind::Map,
|
||||
TypeInfo::Set(_) => SchemaKind::Set,
|
||||
TypeInfo::Enum(_) => SchemaKind::Enum,
|
||||
TypeInfo::Opaque(_) => SchemaKind::Opaque,
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
SchemaKind::Value
|
||||
}
|
||||
/// Builds the type information based on the reflected data.
|
||||
@ -436,18 +547,15 @@ pub trait SchemaInfoReflect {
|
||||
internal_type,
|
||||
documentation: self.to_description(),
|
||||
kind: self.get_kind(),
|
||||
range: self.get_range_by_id(),
|
||||
type_id: Some(self.get_type()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds the internal type information based on the reflected data.
|
||||
fn build_internal_type(&self) -> InternalSchemaType {
|
||||
if let Some(generic) = self.try_get_optional_info() {
|
||||
let range = self.get_range_by_id();
|
||||
let schema_type: SchemaType = generic.ty().id().into();
|
||||
return InternalSchemaType::Optional {
|
||||
generic,
|
||||
range,
|
||||
schema_type,
|
||||
};
|
||||
return InternalSchemaType::Optional { generic };
|
||||
}
|
||||
if let Some(type_info) = self.try_get_type_info() {
|
||||
match type_info {
|
||||
@ -466,27 +574,48 @@ pub trait SchemaInfoReflect {
|
||||
tuple_info.iter().cloned().collect(),
|
||||
);
|
||||
}
|
||||
// TypeInfo::Enum(enum_info) => {}
|
||||
TypeInfo::Enum(enum_info) => {
|
||||
return InternalSchemaType::EnumHolder(enum_info.iter().cloned().collect());
|
||||
}
|
||||
|
||||
// TypeInfo::List(list_info) => todo!(),
|
||||
// TypeInfo::Array(array_info) => todo!(),
|
||||
// TypeInfo::Map(map_info) => todo!(),
|
||||
// TypeInfo::Set(set_info) => todo!(),
|
||||
TypeInfo::List(list_info) => {
|
||||
return InternalSchemaType::Array {
|
||||
element_type: list_info.item_ty().id(),
|
||||
element_type_info: list_info.item_info().cloned(),
|
||||
min_size: None,
|
||||
max_size: None,
|
||||
}
|
||||
}
|
||||
TypeInfo::Set(set_info) => {
|
||||
return InternalSchemaType::Array {
|
||||
element_type: set_info.value_ty().id(),
|
||||
element_type_info: None,
|
||||
min_size: None,
|
||||
max_size: None,
|
||||
}
|
||||
}
|
||||
TypeInfo::Array(array_info) => {
|
||||
return InternalSchemaType::Array {
|
||||
element_type: array_info.item_ty().id(),
|
||||
element_type_info: array_info.item_info().cloned(),
|
||||
min_size: Some(array_info.capacity() as u64),
|
||||
max_size: Some(array_info.capacity() as u64),
|
||||
}
|
||||
}
|
||||
TypeInfo::Map(map_info) => {
|
||||
return InternalSchemaType::Map {
|
||||
key_type: map_info.key_ty().id(),
|
||||
key_type_info: map_info.key_info().cloned(),
|
||||
value_type: map_info.value_ty().id(),
|
||||
value_type_info: map_info.value_info().cloned(),
|
||||
};
|
||||
}
|
||||
//
|
||||
// TypeInfo::Opaque(opaque_info) => todo!(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
let primitive_type = SchemaType::try_get_primitive_type_from_type_id(self.get_type());
|
||||
if let Some(s) = primitive_type {
|
||||
InternalSchemaType::Primitive {
|
||||
schema_type: s,
|
||||
range: self.get_range_by_id(),
|
||||
}
|
||||
} else {
|
||||
InternalSchemaType::Regular
|
||||
}
|
||||
InternalSchemaType::Regular
|
||||
}
|
||||
|
||||
/// Builds the description based on the reflected data.
|
||||
@ -508,6 +637,9 @@ pub trait SchemaInfoReflect {
|
||||
None
|
||||
}
|
||||
|
||||
/// Creates MinMaxValues from a reflected range type.
|
||||
/// Attempts to downcast the reflected value to the specified range type T
|
||||
/// and extract its bounds.
|
||||
fn min_max_from_attribute<T, Y>(&self) -> Option<MinMaxValues>
|
||||
where
|
||||
T: 'static + RangeBounds<Y>,
|
||||
@ -517,6 +649,9 @@ pub trait SchemaInfoReflect {
|
||||
.and_then(|reflect_value| MinMaxValues::from_reflect::<T, Y>(reflect_value))
|
||||
}
|
||||
|
||||
/// Creates MinMaxValues from a reflected range type.
|
||||
/// Attempts to downcast the reflected value to the specified range type T
|
||||
/// and extract its bounds.
|
||||
fn min_max_from_attribute_for_type<T>(&self) -> Option<MinMaxValues>
|
||||
where
|
||||
T: 'static + Into<SchemaNumber> + Copy + Debug,
|
||||
@ -548,6 +683,9 @@ pub trait SchemaInfoReflect {
|
||||
None
|
||||
}
|
||||
|
||||
/// Creates MinMaxValues from a reflected range type.
|
||||
/// Attempts to downcast the reflected value to the specified range type T
|
||||
/// and extract its bounds.
|
||||
fn get_range_by_id(&self) -> MinMaxValues {
|
||||
let t = match self.try_get_optional_info() {
|
||||
Some(info) => info.ty().id(),
|
||||
@ -569,6 +707,8 @@ pub trait SchemaInfoReflect {
|
||||
self.min_max_from_attribute_for_type::<u32>()
|
||||
} else if t.eq(&TypeId::of::<i32>()) {
|
||||
self.min_max_from_attribute_for_type::<i32>()
|
||||
} else if t.eq(&TypeId::of::<i64>()) {
|
||||
self.min_max_from_attribute_for_type::<i64>()
|
||||
} else if t.eq(&TypeId::of::<u64>()) {
|
||||
self.min_max_from_attribute_for_type::<u64>()
|
||||
} else if t.eq(&TypeId::of::<i64>()) {
|
||||
@ -648,19 +788,6 @@ impl SchemaInfoReflect for TypeInfo {
|
||||
fn try_get_type_info(&self) -> Option<TypeInfo> {
|
||||
Some(self.clone())
|
||||
}
|
||||
fn get_kind(&self) -> SchemaKind {
|
||||
match self {
|
||||
TypeInfo::Struct(_) => SchemaKind::Struct,
|
||||
TypeInfo::TupleStruct(_) => SchemaKind::TupleStruct,
|
||||
TypeInfo::Tuple(_) => SchemaKind::Tuple,
|
||||
TypeInfo::List(_) => SchemaKind::List,
|
||||
TypeInfo::Array(_) => SchemaKind::Array,
|
||||
TypeInfo::Map(_) => SchemaKind::Map,
|
||||
TypeInfo::Set(_) => SchemaKind::Set,
|
||||
TypeInfo::Enum(_) => SchemaKind::Enum,
|
||||
TypeInfo::Opaque(_) => SchemaKind::Opaque,
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "documentation")]
|
||||
fn get_docs(&self) -> Option<&str> {
|
||||
self.docs()
|
||||
@ -685,6 +812,7 @@ impl SchemaInfoReflect for TypeId {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use bevy_platform::collections::HashMap;
|
||||
use bevy_reflect::GetTypeRegistration;
|
||||
|
||||
use super::*;
|
||||
@ -692,12 +820,16 @@ mod tests {
|
||||
#[test]
|
||||
fn integer_test() {
|
||||
let type_info = TypeId::of::<u16>().build_schema_type_info();
|
||||
let InternalSchemaType::Primitive { range, schema_type } = type_info.internal_type else {
|
||||
return;
|
||||
};
|
||||
assert_eq!(range.min, Some(0.into()));
|
||||
assert_eq!(range.max, Some(u16::MAX.into()));
|
||||
assert_eq!(schema_type, SchemaType::Integer);
|
||||
let schema_type: Option<SchemaTypeVariant> = (&type_info).into();
|
||||
assert_eq!(type_info.range.min, Some(BoundValue::Inclusive(0.into())));
|
||||
assert_eq!(
|
||||
type_info.range.max,
|
||||
Some(BoundValue::Inclusive(u16::MAX.into()))
|
||||
);
|
||||
assert_eq!(
|
||||
schema_type,
|
||||
Some(SchemaTypeVariant::Single(SchemaType::Integer))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -714,12 +846,13 @@ mod tests {
|
||||
.expect("Should not fail");
|
||||
let field_info = struct_info.field("no_value").expect("Should not fail");
|
||||
let type_info = field_info.build_schema_type_info();
|
||||
let InternalSchemaType::Primitive { range, schema_type } = type_info.internal_type else {
|
||||
return;
|
||||
};
|
||||
assert_eq!(range.min, Some(10.into()));
|
||||
assert_eq!(range.max, Some(13.into()));
|
||||
assert_eq!(schema_type, SchemaType::Integer);
|
||||
let schema_type: Option<SchemaTypeVariant> = (&type_info).into();
|
||||
assert_eq!(type_info.range.min, Some(BoundValue::Inclusive(10.into())));
|
||||
assert_eq!(type_info.range.max, Some(BoundValue::Inclusive(13.into())));
|
||||
assert_eq!(
|
||||
schema_type,
|
||||
Some(SchemaTypeVariant::Single(SchemaType::Integer))
|
||||
);
|
||||
assert_eq!(
|
||||
type_info.documentation,
|
||||
Some("Test documentation".to_string())
|
||||
@ -749,14 +882,17 @@ mod tests {
|
||||
.expect("Should not fail");
|
||||
let field_info = struct_info.field("no_value").expect("Should not fail");
|
||||
let type_info = field_info.build_schema_type_info();
|
||||
let InternalSchemaType::Primitive { range, schema_type } = type_info.internal_type else {
|
||||
return;
|
||||
};
|
||||
eprintln!("Range: {:#?}", range);
|
||||
assert_eq!(range.min, Some(0.into()));
|
||||
assert_eq!(range.max, None);
|
||||
assert_eq!(range.max_exclusive, Some(13.into()));
|
||||
assert_eq!(schema_type, SchemaType::Integer);
|
||||
let schema_type: Option<SchemaTypeVariant> = (&type_info).into();
|
||||
assert!(!type_info.range.in_range((-1).into()));
|
||||
assert!(type_info.range.in_range(0.into()));
|
||||
assert!(type_info.range.in_range(12.into()));
|
||||
assert!(!type_info.range.in_range(13.into()));
|
||||
assert_eq!(type_info.range.min, Some(BoundValue::Inclusive(0.into())));
|
||||
assert_eq!(type_info.range.max, Some(BoundValue::Exclusive(13.into())));
|
||||
assert_eq!(
|
||||
schema_type,
|
||||
Some(SchemaTypeVariant::Single(SchemaType::Integer))
|
||||
);
|
||||
assert_eq!(
|
||||
type_info.documentation,
|
||||
Some("Test documentation".to_string())
|
||||
@ -786,13 +922,13 @@ mod tests {
|
||||
.expect("Should not fail");
|
||||
let field_info = struct_info.iter().next().expect("Should not fail");
|
||||
let type_info = field_info.build_schema_type_info();
|
||||
let InternalSchemaType::Primitive { range, schema_type } = type_info.internal_type else {
|
||||
return;
|
||||
};
|
||||
assert_eq!(range.min, Some(0.into()));
|
||||
assert_eq!(range.max, None);
|
||||
assert_eq!(range.max_exclusive, Some(13.into()));
|
||||
assert_eq!(schema_type, SchemaType::Integer);
|
||||
let schema_type: Option<SchemaTypeVariant> = (&type_info).into();
|
||||
assert_eq!(type_info.range.min, Some(BoundValue::Inclusive(0.into())));
|
||||
assert_eq!(type_info.range.max, Some(BoundValue::Exclusive(13.into())));
|
||||
assert_eq!(
|
||||
schema_type,
|
||||
Some(SchemaTypeVariant::Single(SchemaType::Integer))
|
||||
);
|
||||
assert_eq!(
|
||||
type_info.documentation,
|
||||
Some("Test documentation".to_string())
|
||||
@ -814,20 +950,71 @@ mod tests {
|
||||
Variant4(usize),
|
||||
}
|
||||
eprintln!(
|
||||
"{:#?}",
|
||||
EnumTest::get_type_registration().type_info().build_schema()
|
||||
"{}",
|
||||
serde_json::to_string_pretty(
|
||||
&EnumTest::get_type_registration().type_info().build_schema()
|
||||
)
|
||||
.expect("")
|
||||
);
|
||||
let enum_info = EnumTest::get_type_registration()
|
||||
.type_info()
|
||||
.as_enum()
|
||||
.expect("Should not fail");
|
||||
for field in enum_info.iter() {
|
||||
let type_info = field.build_schema();
|
||||
eprintln!(
|
||||
"{}: {}",
|
||||
field.name(),
|
||||
serde_json::to_string_pretty(&type_info).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reflect_struct_with_array() {
|
||||
#[derive(Reflect, Default, Deserialize, Serialize)]
|
||||
pub struct ArrayComponent {
|
||||
pub arry: [i32; 3],
|
||||
}
|
||||
eprintln!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(
|
||||
&ArrayComponent::get_type_registration()
|
||||
.type_info()
|
||||
.build_schema()
|
||||
)
|
||||
.expect("")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reflect_struct_with_hashmap() {
|
||||
#[derive(Reflect, Default, Deserialize, Serialize)]
|
||||
pub struct HashMapStruct {
|
||||
pub map: HashMap<i32, Option<i32>>,
|
||||
}
|
||||
assert!(serde_json::from_str::<HashMapStruct>(
|
||||
"{\"map\": {\"0\": 1, \"1\": 41, \"2\": null}}"
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
eprintln!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(
|
||||
&HashMapStruct::get_type_registration()
|
||||
.type_info()
|
||||
.build_schema()
|
||||
)
|
||||
.expect("")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reflect_nested_struct() {
|
||||
#[derive(Reflect, Default, Deserialize, Serialize)]
|
||||
pub struct OtherStruct {
|
||||
pub field: String,
|
||||
}
|
||||
#[derive(Reflect, Default, Deserialize, Serialize)]
|
||||
pub struct NestedStruct {
|
||||
pub other: OtherStruct,
|
||||
}
|
||||
eprintln!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(
|
||||
&NestedStruct::get_type_registration()
|
||||
.type_info()
|
||||
.build_schema()
|
||||
)
|
||||
.expect("")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user