JSON Schema reforge
This commit is contained in:
parent
7beca6fd0e
commit
da2336aa45
@ -9,7 +9,8 @@ license = "MIT OR Apache-2.0"
|
||||
keywords = ["bevy"]
|
||||
|
||||
[features]
|
||||
default = ["http", "bevy_asset"]
|
||||
default = ["http", "bevy_asset", "documentation"]
|
||||
documentation = ["bevy_reflect/documentation"]
|
||||
http = ["dep:async-io", "dep:smol-hyper"]
|
||||
bevy_asset = ["dep:bevy_asset"]
|
||||
|
||||
|
@ -3,14 +3,16 @@
|
||||
use alloc::borrow::Cow;
|
||||
use bevy_platform::collections::HashMap;
|
||||
use bevy_reflect::{
|
||||
GetTypeRegistration, NamedField, OpaqueInfo, TypeInfo, TypeRegistration, TypeRegistry,
|
||||
VariantInfo,
|
||||
GetTypeRegistration, NamedField, OpaqueInfo, Reflect, TypeInfo, TypeRegistration, TypeRegistry,
|
||||
};
|
||||
use core::any::TypeId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, Map, Value};
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use crate::schemas::SchemaTypesMetadata;
|
||||
use crate::schemas::{
|
||||
reflect_info::{SchemaInfoReflect, SchemaNumber},
|
||||
SchemaTypesMetadata,
|
||||
};
|
||||
|
||||
/// Helper trait for converting `TypeRegistration` to `JsonSchemaBevyType`
|
||||
pub trait TypeRegistrySchemaReader {
|
||||
@ -51,134 +53,80 @@ pub fn export_type(
|
||||
impl From<(&TypeRegistration, &SchemaTypesMetadata)> for JsonSchemaBevyType {
|
||||
fn from(value: (&TypeRegistration, &SchemaTypesMetadata)) -> Self {
|
||||
let (reg, metadata) = value;
|
||||
let t = reg.type_info();
|
||||
let binding = t.type_path_table();
|
||||
let type_info = reg.type_info();
|
||||
let base_schema = type_info.build_schema();
|
||||
|
||||
let short_path = binding.short_path();
|
||||
let type_path = binding.path();
|
||||
let mut typed_schema = JsonSchemaBevyType {
|
||||
reflect_types: metadata.get_registered_reflect_types(reg),
|
||||
short_path: short_path.to_owned(),
|
||||
type_path: type_path.to_owned(),
|
||||
crate_name: binding.crate_name().map(str::to_owned),
|
||||
module_path: binding.module_path().map(str::to_owned),
|
||||
..Default::default()
|
||||
let JsonSchemaVariant::Schema(mut typed_schema) = base_schema else {
|
||||
return JsonSchemaBevyType::default();
|
||||
};
|
||||
match t {
|
||||
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.ty().ref_type()))
|
||||
.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 = SchemaType::Object;
|
||||
typed_schema.kind = SchemaKind::Struct;
|
||||
// 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;
|
||||
|
||||
let simple = info
|
||||
typed_schema.one_of = info
|
||||
.iter()
|
||||
.all(|variant| matches!(variant, VariantInfo::Unit(_)));
|
||||
if simple {
|
||||
typed_schema.schema_type = SchemaType::String;
|
||||
typed_schema.one_of = info
|
||||
.iter()
|
||||
.map(|variant| match variant {
|
||||
VariantInfo::Unit(v) => v.name().into(),
|
||||
_ => unreachable!(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
} else {
|
||||
typed_schema.schema_type = SchemaType::Object;
|
||||
typed_schema.one_of = info
|
||||
.iter()
|
||||
.map(|variant| match variant {
|
||||
VariantInfo::Struct(v) => json!({
|
||||
"type": "object",
|
||||
"kind": "Struct",
|
||||
"typePath": format!("{}::{}", type_path, v.name()),
|
||||
"shortPath": v.name(),
|
||||
"properties": v
|
||||
.iter()
|
||||
.map(|field| (field.name().to_owned(), field.ref_type()))
|
||||
.collect::<Map<_, _>>(),
|
||||
"additionalProperties": false,
|
||||
"required": v
|
||||
.iter()
|
||||
.filter(|field| !field.type_path().starts_with("core::option::Option"))
|
||||
.map(NamedField::name)
|
||||
.collect::<Vec<_>>(),
|
||||
}),
|
||||
VariantInfo::Tuple(v) => json!({
|
||||
"type": "array",
|
||||
"kind": "Tuple",
|
||||
"typePath": format!("{}::{}", type_path, v.name()),
|
||||
"shortPath": v.name(),
|
||||
"prefixItems": v
|
||||
.iter()
|
||||
.map(SchemaJsonReference::ref_type)
|
||||
.collect::<Vec<_>>(),
|
||||
"items": false,
|
||||
}),
|
||||
VariantInfo::Unit(v) => json!({
|
||||
"typePath": format!("{}::{}", type_path, v.name()),
|
||||
"shortPath": v.name(),
|
||||
}),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
}
|
||||
.map(|variant| variant.build_schema())
|
||||
.collect::<Vec<_>>();
|
||||
}
|
||||
TypeInfo::TupleStruct(info) => {
|
||||
typed_schema.schema_type = SchemaType::Array;
|
||||
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());
|
||||
// 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 = SchemaType::Array;
|
||||
typed_schema.schema_type = Some(SchemaTypeVariant::Single(SchemaType::Array));
|
||||
typed_schema.kind = SchemaKind::List;
|
||||
typed_schema.items = info.item_ty().ref_type().into();
|
||||
// typed_schema.items = info.item_ty().ref_type().into();
|
||||
}
|
||||
TypeInfo::Array(info) => {
|
||||
typed_schema.schema_type = SchemaType::Array;
|
||||
typed_schema.schema_type = Some(SchemaTypeVariant::Single(SchemaType::Array));
|
||||
typed_schema.kind = SchemaKind::Array;
|
||||
typed_schema.items = info.item_ty().ref_type().into();
|
||||
// typed_schema.items = info.item_ty().ref_type().into();
|
||||
}
|
||||
TypeInfo::Map(info) => {
|
||||
typed_schema.schema_type = SchemaType::Object;
|
||||
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 = SchemaType::Array;
|
||||
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());
|
||||
// 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 = SchemaType::Set;
|
||||
typed_schema.schema_type = Some(SchemaTypeVariant::Single(SchemaType::Array));
|
||||
typed_schema.kind = SchemaKind::Set;
|
||||
typed_schema.items = info.value_ty().ref_type().into();
|
||||
// typed_schema.items = info.value_ty().ref_type().into();
|
||||
}
|
||||
TypeInfo::Opaque(info) => {
|
||||
typed_schema.schema_type = info.map_json_type();
|
||||
typed_schema.schema_type = Some(SchemaTypeVariant::Single(SchemaType::Array));
|
||||
typed_schema.kind = SchemaKind::Value;
|
||||
}
|
||||
};
|
||||
typed_schema
|
||||
*typed_schema
|
||||
}
|
||||
}
|
||||
|
||||
@ -215,8 +163,9 @@ pub struct JsonSchemaBevyType {
|
||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||
pub value_type: Option<Value>,
|
||||
/// 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")]
|
||||
pub schema_type: SchemaType,
|
||||
pub schema_type: Option<SchemaTypeVariant>,
|
||||
/// The behavior of this keyword depends on the presence and annotation results of "properties"
|
||||
/// and "patternProperties" within the same schema object.
|
||||
/// Validation with "additionalProperties" applies only to the child
|
||||
@ -227,13 +176,13 @@ pub struct JsonSchemaBevyType {
|
||||
/// within this keyword's value, the child instance for that name successfully validates
|
||||
/// against the corresponding schema.
|
||||
#[serde(skip_serializing_if = "HashMap::is_empty", default)]
|
||||
pub properties: HashMap<String, Value>,
|
||||
pub properties: HashMap<String, JsonSchemaVariant>,
|
||||
/// An object instance is valid against this keyword if every item in the array is the name of a property in the instance.
|
||||
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
||||
pub required: Vec<String>,
|
||||
/// An instance validates successfully against this keyword if it validates successfully against exactly one schema defined by this keyword's value.
|
||||
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
||||
pub one_of: Vec<Value>,
|
||||
pub one_of: Vec<JsonSchemaVariant>,
|
||||
/// Validation succeeds if each element of the instance validates against the schema at the same position, if any. This keyword does not constrain the length of the array. If the array is longer than this keyword's value, this keyword validates only the prefix of matching length.
|
||||
///
|
||||
/// This keyword produces an annotation value which is the largest index to which this keyword
|
||||
@ -241,7 +190,7 @@ pub struct JsonSchemaBevyType {
|
||||
/// index of the instance, such as is produced by the "items" keyword.
|
||||
/// This annotation affects the behavior of "items" and "unevaluatedItems".
|
||||
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
||||
pub prefix_items: Vec<Value>,
|
||||
pub prefix_items: Vec<JsonSchemaVariant>,
|
||||
/// This keyword applies its subschema to all instance elements at indexes greater
|
||||
/// than the length of the "prefixItems" array in the same schema object,
|
||||
/// as reported by the annotation result of that "prefixItems" keyword.
|
||||
@ -252,7 +201,40 @@ pub struct JsonSchemaBevyType {
|
||||
/// it produces an annotation result of boolean true, indicating that all remaining
|
||||
/// array elements have been evaluated against this keyword's subschema.
|
||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||
pub items: Option<Value>,
|
||||
pub items: Option<JsonSchemaVariant>,
|
||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||
pub minimum: Option<SchemaNumber>,
|
||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||
pub maximum: Option<SchemaNumber>,
|
||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||
pub exclusive_minimum: Option<SchemaNumber>,
|
||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||
pub exclusive_maximum: Option<SchemaNumber>,
|
||||
/// Type description
|
||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Reflect)]
|
||||
#[serde(untagged)]
|
||||
pub enum JsonSchemaVariant {
|
||||
BoolValue(bool),
|
||||
Const {
|
||||
#[reflect(ignore)]
|
||||
#[serde(rename = "const")]
|
||||
value: Value,
|
||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||
description: Option<String>,
|
||||
},
|
||||
Schema(#[reflect(ignore)] Box<JsonSchemaBevyType>),
|
||||
}
|
||||
impl JsonSchemaVariant {
|
||||
pub fn const_value(serializable: impl Serialize, description: Option<String>) -> Self {
|
||||
Self::Const {
|
||||
value: serde_json::to_value(serializable).unwrap_or_default(),
|
||||
description,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Kind of json schema, maps [`TypeInfo`] type
|
||||
@ -277,41 +259,93 @@ pub enum SchemaKind {
|
||||
Set,
|
||||
/// Single value, eg. primitive types
|
||||
Value,
|
||||
/// Opaque type
|
||||
Opaque,
|
||||
/// Optional type
|
||||
Optional,
|
||||
}
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Reflect, Eq, PartialOrd, Ord)]
|
||||
#[serde(untagged)]
|
||||
pub enum SchemaTypeVariant {
|
||||
Single(SchemaType),
|
||||
Multiple(Vec<SchemaType>),
|
||||
}
|
||||
|
||||
/// Type of json schema
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
|
||||
/// More [here](https://json-schema.org/draft-07/draft-handrews-json-schema-01#rfc.section.4.2.1)
|
||||
#[derive(
|
||||
Debug, Serialize, Deserialize, Clone, PartialEq, Reflect, Default, Eq, PartialOrd, Ord,
|
||||
)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum SchemaType {
|
||||
/// Represents a string value.
|
||||
/// A string of Unicode code points, from the JSON "string" production.
|
||||
String,
|
||||
|
||||
/// Represents a floating-point number.
|
||||
Float,
|
||||
/// An arbitrary-precision, base-10 decimal number value, from the JSON "number" production.
|
||||
Number,
|
||||
|
||||
/// Represents an unsigned integer.
|
||||
Uint,
|
||||
/// Represents both a signed and unsigned integer.
|
||||
Integer,
|
||||
|
||||
/// Represents a signed integer.
|
||||
Int,
|
||||
|
||||
/// Represents an object with key-value pairs.
|
||||
/// An unordered set of properties mapping a string to an instance, from the JSON "object" production.
|
||||
Object,
|
||||
|
||||
/// Represents an array of values.
|
||||
/// An ordered list of instances, from the JSON "array" production.
|
||||
Array,
|
||||
|
||||
/// Represents a boolean value (true or false).
|
||||
/// A "true" or "false" value, from the JSON "true" or "false" productions.
|
||||
Boolean,
|
||||
|
||||
/// Represents a set of unique values.
|
||||
Set,
|
||||
|
||||
/// Represents a null value.
|
||||
/// A JSON "null" production.
|
||||
#[default]
|
||||
Null,
|
||||
}
|
||||
|
||||
impl From<TypeId> for SchemaType {
|
||||
fn from(value: TypeId) -> Self {
|
||||
if value.eq(&TypeId::of::<bool>()) {
|
||||
Self::Boolean
|
||||
} else if value.eq(&TypeId::of::<f32>()) || value.eq(&TypeId::of::<f64>()) {
|
||||
Self::Number
|
||||
} else if value.eq(&TypeId::of::<u8>())
|
||||
|| value.eq(&TypeId::of::<u16>())
|
||||
|| value.eq(&TypeId::of::<u32>())
|
||||
|| value.eq(&TypeId::of::<u64>())
|
||||
|| value.eq(&TypeId::of::<u128>())
|
||||
|| value.eq(&TypeId::of::<usize>())
|
||||
{
|
||||
Self::Integer
|
||||
} else if value.eq(&TypeId::of::<i8>())
|
||||
|| value.eq(&TypeId::of::<i16>())
|
||||
|| value.eq(&TypeId::of::<i32>())
|
||||
|| value.eq(&TypeId::of::<i64>())
|
||||
|| value.eq(&TypeId::of::<i128>())
|
||||
|| value.eq(&TypeId::of::<isize>())
|
||||
{
|
||||
Self::Integer
|
||||
} else if value.eq(&TypeId::of::<str>())
|
||||
|| value.eq(&TypeId::of::<char>())
|
||||
|| value.eq(&TypeId::of::<String>())
|
||||
{
|
||||
Self::String
|
||||
} else {
|
||||
Self::Object
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SchemaType {
|
||||
/// Returns the primitive type corresponding to the given type ID, if it exists.
|
||||
pub fn try_get_primitive_type_from_type_id(type_id: TypeId) -> Option<Self> {
|
||||
let schema_type: SchemaType = type_id.into();
|
||||
if schema_type.eq(&Self::Object) {
|
||||
None
|
||||
} else {
|
||||
Some(schema_type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper trait for generating json schema reference
|
||||
trait SchemaJsonReference {
|
||||
/// Reference to another type in schema.
|
||||
@ -328,9 +362,9 @@ pub trait SchemaJsonType {
|
||||
fn map_json_type(&self) -> SchemaType {
|
||||
match self.get_type_path() {
|
||||
"bool" => SchemaType::Boolean,
|
||||
"u8" | "u16" | "u32" | "u64" | "u128" | "usize" => SchemaType::Uint,
|
||||
"i8" | "i16" | "i32" | "i64" | "i128" | "isize" => SchemaType::Int,
|
||||
"f32" | "f64" => SchemaType::Float,
|
||||
"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,
|
||||
}
|
||||
@ -380,7 +414,8 @@ mod tests {
|
||||
#[reflect(Resource, Default, Serialize, Deserialize)]
|
||||
struct Foo {
|
||||
a: f32,
|
||||
b: Option<f32>,
|
||||
#[reflect(@10..=15i16)]
|
||||
b: Option<i16>,
|
||||
}
|
||||
|
||||
let atr = AppTypeRegistry::default();
|
||||
@ -393,8 +428,17 @@ mod tests {
|
||||
.get(TypeId::of::<Foo>())
|
||||
.expect("SHOULD BE REGISTERED")
|
||||
.clone();
|
||||
// for field in foo_registration
|
||||
// .type_info()
|
||||
// .as_struct()
|
||||
// .expect("msg")
|
||||
// .iter()
|
||||
// {
|
||||
// eprintln!("{}: {:#?}", field.name(), field.build_schema_type_info());
|
||||
// }
|
||||
let (_, schema) = export_type(&foo_registration, &SchemaTypesMetadata::default());
|
||||
|
||||
eprintln!("{}", serde_json::to_string_pretty(&schema).expect("msg"));
|
||||
assert!(
|
||||
!schema.reflect_types.contains(&"Component".to_owned()),
|
||||
"Should not be a component"
|
||||
@ -409,10 +453,6 @@ mod tests {
|
||||
schema.required.contains(&"a".to_owned()),
|
||||
"Field a should be required"
|
||||
);
|
||||
assert!(
|
||||
!schema.required.contains(&"b".to_owned()),
|
||||
"Field b should not be required"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -422,6 +462,7 @@ mod tests {
|
||||
enum EnumComponent {
|
||||
ValueOne(i32),
|
||||
ValueTwo {
|
||||
#[reflect(@111..5555i32)]
|
||||
test: i32,
|
||||
},
|
||||
#[default]
|
||||
@ -439,6 +480,7 @@ mod tests {
|
||||
.expect("SHOULD BE REGISTERED")
|
||||
.clone();
|
||||
let (_, schema) = export_type(&foo_registration, &SchemaTypesMetadata::default());
|
||||
eprintln!("{}", serde_json::to_string_pretty(&schema).expect("msg"));
|
||||
assert!(
|
||||
schema.reflect_types.contains(&"Component".to_owned()),
|
||||
"Should be a component"
|
||||
@ -576,7 +618,8 @@ mod tests {
|
||||
#[derive(Reflect, Resource, Default, Deserialize, Serialize)]
|
||||
#[reflect(Resource, Default)]
|
||||
struct Foo {
|
||||
a: f32,
|
||||
/// Test doc
|
||||
a: u16,
|
||||
}
|
||||
|
||||
let atr = AppTypeRegistry::default();
|
||||
@ -591,6 +634,7 @@ mod tests {
|
||||
.clone();
|
||||
let (_, schema) = export_type(&foo_registration, &SchemaTypesMetadata::default());
|
||||
let schema_as_value = serde_json::to_value(&schema).expect("Should serialize");
|
||||
eprintln!("{:#?}", &schema_as_value);
|
||||
let value = json!({
|
||||
"shortPath": "Foo",
|
||||
"typePath": "bevy_remote::schemas::json_schema::tests::Foo",
|
||||
@ -605,9 +649,14 @@ mod tests {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"a": {
|
||||
"type": {
|
||||
"$ref": "#/$defs/f32"
|
||||
}
|
||||
"kind": "Value",
|
||||
"maximum": 65535,
|
||||
"minimum": 0,
|
||||
"type": "integer",
|
||||
"description": "Test doc",
|
||||
"shortPath": "",
|
||||
"typePath": "",
|
||||
|
||||
},
|
||||
},
|
||||
"required": [
|
||||
|
@ -12,6 +12,7 @@ use core::any::TypeId;
|
||||
|
||||
pub mod json_schema;
|
||||
pub mod open_rpc;
|
||||
pub mod reflect_info;
|
||||
|
||||
/// Holds mapping of reflect [type data](TypeData) to strings,
|
||||
/// later on used in Bevy Json Schema.
|
||||
|
833
crates/bevy_remote/src/schemas/reflect_info.rs
Normal file
833
crates/bevy_remote/src/schemas/reflect_info.rs
Normal file
@ -0,0 +1,833 @@
|
||||
//! Module containing information about reflected types.
|
||||
use bevy_reflect::{
|
||||
GenericInfo, NamedField, Reflect, StructVariantInfo, TypeInfo, UnnamedField, VariantInfo,
|
||||
};
|
||||
use core::any::TypeId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
any::Any,
|
||||
f32::consts::PI,
|
||||
fmt::Debug,
|
||||
ops::{Bound, RangeBounds},
|
||||
};
|
||||
|
||||
use crate::schemas::json_schema::{
|
||||
JsonSchemaBevyType, JsonSchemaVariant, SchemaKind, SchemaType, SchemaTypeVariant,
|
||||
};
|
||||
|
||||
/// Enum representing a number in schema.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Reflect)]
|
||||
#[serde(untagged)]
|
||||
pub enum SchemaNumber {
|
||||
/// Integer value.
|
||||
Int(i128),
|
||||
/// Always finite.
|
||||
Float(f64),
|
||||
}
|
||||
|
||||
impl From<f32> for SchemaNumber {
|
||||
fn from(value: f32) -> Self {
|
||||
SchemaNumber::Float(value as f64)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f64> for SchemaNumber {
|
||||
fn from(value: f64) -> Self {
|
||||
SchemaNumber::Float(value)
|
||||
}
|
||||
}
|
||||
impl From<u8> for SchemaNumber {
|
||||
fn from(value: u8) -> Self {
|
||||
SchemaNumber::Int(value as i128)
|
||||
}
|
||||
}
|
||||
impl From<u16> for SchemaNumber {
|
||||
fn from(value: u16) -> Self {
|
||||
SchemaNumber::Int(value as i128)
|
||||
}
|
||||
}
|
||||
impl From<u32> for SchemaNumber {
|
||||
fn from(value: u32) -> Self {
|
||||
SchemaNumber::Int(value as i128)
|
||||
}
|
||||
}
|
||||
impl From<u64> for SchemaNumber {
|
||||
fn from(value: u64) -> Self {
|
||||
SchemaNumber::Int(value as i128)
|
||||
}
|
||||
}
|
||||
impl From<usize> for SchemaNumber {
|
||||
fn from(value: usize) -> Self {
|
||||
SchemaNumber::Int(value as i128)
|
||||
}
|
||||
}
|
||||
impl From<i8> for SchemaNumber {
|
||||
fn from(value: i8) -> Self {
|
||||
SchemaNumber::Int(value as i128)
|
||||
}
|
||||
}
|
||||
impl From<i16> for SchemaNumber {
|
||||
fn from(value: i16) -> Self {
|
||||
SchemaNumber::Int(value as i128)
|
||||
}
|
||||
}
|
||||
impl From<i32> for SchemaNumber {
|
||||
fn from(value: i32) -> Self {
|
||||
SchemaNumber::Int(value as i128)
|
||||
}
|
||||
}
|
||||
impl From<i64> for SchemaNumber {
|
||||
fn from(value: i64) -> Self {
|
||||
SchemaNumber::Int(value as i128)
|
||||
}
|
||||
}
|
||||
impl From<isize> for SchemaNumber {
|
||||
fn from(value: isize) -> Self {
|
||||
SchemaNumber::Int(value as i128)
|
||||
}
|
||||
}
|
||||
|
||||
#[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>,
|
||||
}
|
||||
|
||||
impl MinMaxValues {
|
||||
pub fn from_reflect<T, Y>(reflect_val: &dyn Reflect) -> Option<MinMaxValues>
|
||||
where
|
||||
T: 'static + RangeBounds<Y>,
|
||||
Y: 'static + Into<SchemaNumber> + Copy + Debug,
|
||||
{
|
||||
let range = reflect_val.downcast_ref::<T>()?;
|
||||
|
||||
Some(Self::from_range((
|
||||
range.start_bound(),
|
||||
range.end_bound(),
|
||||
TypeId::of::<Y>(),
|
||||
)))
|
||||
}
|
||||
|
||||
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 (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),
|
||||
};
|
||||
|
||||
Self {
|
||||
min,
|
||||
min_exclusive,
|
||||
max,
|
||||
max_exclusive,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TypeId> for MinMaxValues {
|
||||
fn from(value: TypeId) -> Self {
|
||||
let mut min: Option<SchemaNumber> = None;
|
||||
let mut max: Option<SchemaNumber> = None;
|
||||
if value.eq(&TypeId::of::<u8>()) {
|
||||
min = Some(0.into());
|
||||
max = Some(u8::MAX.into());
|
||||
} else if value.eq(&TypeId::of::<u16>()) {
|
||||
min = Some(0.into());
|
||||
max = Some(u16::MAX.into());
|
||||
} else if value.eq(&TypeId::of::<u32>()) {
|
||||
min = Some(0.into());
|
||||
max = Some(u32::MAX.into());
|
||||
} else if value.eq(&TypeId::of::<u64>()) {
|
||||
min = Some(0.into());
|
||||
} else if value.eq(&TypeId::of::<u128>()) {
|
||||
min = Some(0.into());
|
||||
} else if value.eq(&TypeId::of::<usize>()) {
|
||||
min = Some(0.into());
|
||||
} else if value.eq(&TypeId::of::<i8>()) {
|
||||
min = Some(i8::MIN.into());
|
||||
max = Some(i8::MAX.into());
|
||||
} else if value.eq(&TypeId::of::<i16>()) {
|
||||
min = Some(i16::MIN.into());
|
||||
max = Some(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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub enum InternalSchemaType {
|
||||
Primitive {
|
||||
range: MinMaxValues,
|
||||
schema_type: SchemaType,
|
||||
},
|
||||
EnumVariant(VariantInfo),
|
||||
NamedFieldsHolder(Vec<NamedField>),
|
||||
UnnamedFieldsHolder(Vec<UnnamedField>),
|
||||
Optional {
|
||||
generic: GenericInfo,
|
||||
range: MinMaxValues,
|
||||
schema_type: SchemaType,
|
||||
},
|
||||
#[default]
|
||||
Regular,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct SchemaTypeInfo {
|
||||
pub internal_type: InternalSchemaType,
|
||||
pub documentation: Option<String>,
|
||||
pub kind: SchemaKind,
|
||||
pub type_info: Option<TypeInfo>,
|
||||
}
|
||||
|
||||
impl Into<JsonSchemaVariant> for SchemaTypeInfo {
|
||||
fn into(self) -> JsonSchemaVariant {
|
||||
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::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::EnumVariant(variant_info) => match &variant_info {
|
||||
VariantInfo::Struct(struct_variant_info) => {
|
||||
let internal_type = InternalSchemaType::NamedFieldsHolder(
|
||||
struct_variant_info.iter().cloned().collect(),
|
||||
);
|
||||
let schema = SchemaTypeInfo {
|
||||
internal_type,
|
||||
documentation: variant_info.to_description(),
|
||||
kind: SchemaKind::Struct,
|
||||
type_info: None,
|
||||
};
|
||||
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()
|
||||
}))
|
||||
}
|
||||
VariantInfo::Tuple(tuple_variant_info) => {
|
||||
let internal_type = InternalSchemaType::UnnamedFieldsHolder(
|
||||
tuple_variant_info.iter().cloned().collect(),
|
||||
);
|
||||
let schema = SchemaTypeInfo {
|
||||
internal_type,
|
||||
documentation: variant_info.to_description(),
|
||||
kind: SchemaKind::Tuple,
|
||||
type_info: None,
|
||||
};
|
||||
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()
|
||||
}))
|
||||
}
|
||||
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()
|
||||
}))
|
||||
}
|
||||
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)
|
||||
} else {
|
||||
s
|
||||
}
|
||||
} 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()
|
||||
}))
|
||||
}
|
||||
}
|
||||
InternalSchemaType::Optional {
|
||||
generic,
|
||||
range,
|
||||
schema_type,
|
||||
} => {
|
||||
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)
|
||||
} else {
|
||||
schema_variant
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait that builds the type information based on the reflected data.
|
||||
pub trait SchemaInfoReflect {
|
||||
fn try_get_optional_info(&self) -> Option<GenericInfo> {
|
||||
let type_info = self.try_get_type_info()?;
|
||||
let TypeInfo::Enum(enum_info) = type_info else {
|
||||
return None;
|
||||
};
|
||||
if let Some(generic) = enum_info.generics().first() {
|
||||
if enum_info.contains_variant("Some")
|
||||
&& enum_info.contains_variant("None")
|
||||
&& enum_info.variant_len() == 2
|
||||
{
|
||||
return Some(generic.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
/// Returns the type information of the schema.
|
||||
fn try_get_type_info(&self) -> Option<TypeInfo>;
|
||||
/// Returns the Bevy kind of the schema.
|
||||
fn get_kind(&self) -> SchemaKind {
|
||||
SchemaKind::Value
|
||||
}
|
||||
/// Builds the type information based on the reflected data.
|
||||
fn build_schema(&self) -> JsonSchemaVariant {
|
||||
self.build_schema_type_info().into()
|
||||
}
|
||||
/// Builds the type information based on the reflected data.
|
||||
fn build_schema_type_info(&self) -> SchemaTypeInfo {
|
||||
let internal_type = self.build_internal_type();
|
||||
SchemaTypeInfo {
|
||||
type_info: self.try_get_type_info(),
|
||||
internal_type,
|
||||
documentation: self.to_description(),
|
||||
kind: self.get_kind(),
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
if let Some(type_info) = self.try_get_type_info() {
|
||||
match type_info {
|
||||
TypeInfo::Struct(struct_info) => {
|
||||
return InternalSchemaType::NamedFieldsHolder(
|
||||
struct_info.iter().cloned().collect(),
|
||||
);
|
||||
}
|
||||
TypeInfo::TupleStruct(tuple_struct_info) => {
|
||||
return InternalSchemaType::UnnamedFieldsHolder(
|
||||
tuple_struct_info.iter().cloned().collect(),
|
||||
);
|
||||
}
|
||||
TypeInfo::Tuple(tuple_info) => {
|
||||
return InternalSchemaType::UnnamedFieldsHolder(
|
||||
tuple_info.iter().cloned().collect(),
|
||||
);
|
||||
}
|
||||
// TypeInfo::Enum(enum_info) => {}
|
||||
|
||||
// TypeInfo::List(list_info) => todo!(),
|
||||
// TypeInfo::Array(array_info) => todo!(),
|
||||
// TypeInfo::Map(map_info) => todo!(),
|
||||
// TypeInfo::Set(set_info) => todo!(),
|
||||
//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds the description based on the reflected data.
|
||||
fn to_description(&self) -> Option<String> {
|
||||
self.get_docs()
|
||||
.map(|s| s.trim().replace("\n", "").to_string())
|
||||
}
|
||||
|
||||
/// Returns the documentation of the reflected data.
|
||||
fn get_docs(&self) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Get the underlaying TypeId
|
||||
fn get_type(&self) -> TypeId;
|
||||
|
||||
/// Try to get the attribute by id
|
||||
fn try_get_attribute_by_id(&self, _id: ::core::any::TypeId) -> Option<&dyn Reflect> {
|
||||
None
|
||||
}
|
||||
|
||||
fn min_max_from_attribute<T, Y>(&self) -> Option<MinMaxValues>
|
||||
where
|
||||
T: 'static + RangeBounds<Y>,
|
||||
Y: 'static + Into<SchemaNumber> + Copy + Debug,
|
||||
{
|
||||
self.try_get_attribute_by_id(TypeId::of::<T>())
|
||||
.and_then(|reflect_value| MinMaxValues::from_reflect::<T, Y>(reflect_value))
|
||||
}
|
||||
|
||||
fn min_max_from_attribute_for_type<T>(&self) -> Option<MinMaxValues>
|
||||
where
|
||||
T: 'static + Into<SchemaNumber> + Copy + Debug,
|
||||
{
|
||||
let s = self.min_max_from_attribute::<core::ops::RangeInclusive<T>, T>();
|
||||
if s.is_some() {
|
||||
return s;
|
||||
}
|
||||
let s = self.min_max_from_attribute::<core::ops::Range<T>, T>();
|
||||
if s.is_some() {
|
||||
return s;
|
||||
}
|
||||
let s = self.min_max_from_attribute::<core::ops::RangeTo<T>, T>();
|
||||
if s.is_some() {
|
||||
return s;
|
||||
}
|
||||
let s = self.min_max_from_attribute::<core::ops::RangeToInclusive<T>, T>();
|
||||
if s.is_some() {
|
||||
return s;
|
||||
}
|
||||
let s = self.min_max_from_attribute::<core::ops::RangeFrom<T>, T>();
|
||||
if s.is_some() {
|
||||
return s;
|
||||
}
|
||||
let s = self.min_max_from_attribute::<core::ops::RangeFull, T>();
|
||||
if s.is_some() {
|
||||
return s;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn get_range_by_id(&self) -> MinMaxValues {
|
||||
let t = match self.try_get_optional_info() {
|
||||
Some(info) => info.ty().id(),
|
||||
None => self.get_type(),
|
||||
};
|
||||
let result = if t.eq(&TypeId::of::<u8>()) {
|
||||
self.min_max_from_attribute_for_type::<u8>()
|
||||
} else if t.eq(&TypeId::of::<i8>()) {
|
||||
self.min_max_from_attribute_for_type::<i8>()
|
||||
} else if t.eq(&TypeId::of::<u16>()) {
|
||||
self.min_max_from_attribute_for_type::<u16>()
|
||||
} else if t.eq(&TypeId::of::<usize>()) {
|
||||
self.min_max_from_attribute_for_type::<usize>()
|
||||
} else if t.eq(&TypeId::of::<isize>()) {
|
||||
self.min_max_from_attribute_for_type::<isize>()
|
||||
} else if t.eq(&TypeId::of::<i16>()) {
|
||||
self.min_max_from_attribute_for_type::<i16>()
|
||||
} else if t.eq(&TypeId::of::<u32>()) {
|
||||
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::<u64>()) {
|
||||
self.min_max_from_attribute_for_type::<u64>()
|
||||
} else if t.eq(&TypeId::of::<i64>()) {
|
||||
self.min_max_from_attribute_for_type::<i64>()
|
||||
} else if t.eq(&TypeId::of::<f32>()) {
|
||||
self.min_max_from_attribute_for_type::<f32>()
|
||||
} else if t.eq(&TypeId::of::<f64>()) {
|
||||
self.min_max_from_attribute_for_type::<f64>()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
result.unwrap_or(t.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl SchemaInfoReflect for UnnamedField {
|
||||
fn try_get_type_info(&self) -> Option<TypeInfo> {
|
||||
self.type_info().and_then(|info| Some(info.clone()))
|
||||
}
|
||||
#[cfg(feature = "documentation")]
|
||||
fn get_docs(&self) -> Option<&str> {
|
||||
self.docs()
|
||||
}
|
||||
fn get_type(&self) -> TypeId {
|
||||
self.type_id()
|
||||
}
|
||||
|
||||
fn try_get_attribute_by_id(&self, id: ::core::any::TypeId) -> Option<&dyn Reflect> {
|
||||
self.get_attribute_by_id(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl SchemaInfoReflect for NamedField {
|
||||
fn try_get_type_info(&self) -> Option<TypeInfo> {
|
||||
self.type_info().and_then(|info| Some(info.clone()))
|
||||
}
|
||||
#[cfg(feature = "documentation")]
|
||||
fn get_docs(&self) -> Option<&str> {
|
||||
self.docs()
|
||||
}
|
||||
fn get_type(&self) -> TypeId {
|
||||
self.type_id()
|
||||
}
|
||||
|
||||
fn try_get_attribute_by_id(&self, id: ::core::any::TypeId) -> Option<&dyn Reflect> {
|
||||
self.get_attribute_by_id(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl SchemaInfoReflect for VariantInfo {
|
||||
fn try_get_type_info(&self) -> Option<TypeInfo> {
|
||||
None
|
||||
}
|
||||
#[cfg(feature = "documentation")]
|
||||
fn get_docs(&self) -> Option<&str> {
|
||||
match self {
|
||||
VariantInfo::Unit(info) => info.docs(),
|
||||
VariantInfo::Tuple(info) => info.docs(),
|
||||
VariantInfo::Struct(info) => info.docs(),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_internal_type(&self) -> InternalSchemaType {
|
||||
InternalSchemaType::EnumVariant(self.clone())
|
||||
}
|
||||
|
||||
fn get_type(&self) -> TypeId {
|
||||
self.type_id()
|
||||
}
|
||||
|
||||
fn try_get_attribute_by_id(&self, id: ::core::any::TypeId) -> Option<&dyn Reflect> {
|
||||
self.get_attribute_by_id(id)
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
fn get_type(&self) -> TypeId {
|
||||
if let Some(generic) = self.generics().first() {
|
||||
generic.type_id()
|
||||
} else {
|
||||
self.type_id()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SchemaInfoReflect for TypeId {
|
||||
fn try_get_type_info(&self) -> Option<TypeInfo> {
|
||||
None
|
||||
}
|
||||
fn get_type(&self) -> TypeId {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use bevy_reflect::GetTypeRegistration;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[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);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom_range_test() {
|
||||
#[derive(Reflect, Default, Deserialize, Serialize)]
|
||||
pub struct StructTest {
|
||||
/// Test documentation
|
||||
#[reflect(@10..=13_i32)]
|
||||
no_value: i32,
|
||||
}
|
||||
let struct_info = StructTest::get_type_registration()
|
||||
.type_info()
|
||||
.as_struct()
|
||||
.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);
|
||||
assert_eq!(
|
||||
type_info.documentation,
|
||||
Some("Test documentation".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom_range_test_usize() {
|
||||
#[derive(Reflect, Default, Deserialize, Serialize)]
|
||||
pub struct StructTest {
|
||||
/// Test documentation
|
||||
#[reflect(@..13_usize)]
|
||||
no_value: usize,
|
||||
}
|
||||
eprintln!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(
|
||||
&StructTest::get_type_registration()
|
||||
.type_info()
|
||||
.build_schema()
|
||||
)
|
||||
.expect("Should not happened")
|
||||
);
|
||||
let struct_info = StructTest::get_type_registration()
|
||||
.type_info()
|
||||
.as_struct()
|
||||
.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);
|
||||
assert_eq!(
|
||||
type_info.documentation,
|
||||
Some("Test documentation".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom_tuple_test_usize() {
|
||||
#[derive(Reflect, Default, Deserialize, Serialize)]
|
||||
pub struct TupleTest(
|
||||
/// Test documentation
|
||||
#[reflect(@..13_usize)]
|
||||
pub usize,
|
||||
);
|
||||
eprintln!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(
|
||||
&TupleTest::get_type_registration()
|
||||
.type_info()
|
||||
.build_schema()
|
||||
)
|
||||
.expect("SD")
|
||||
);
|
||||
let struct_info = TupleTest::get_type_registration()
|
||||
.type_info()
|
||||
.as_tuple_struct()
|
||||
.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);
|
||||
assert_eq!(
|
||||
type_info.documentation,
|
||||
Some("Test documentation".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom_enum_test() {
|
||||
#[derive(Reflect, Default, Deserialize, Serialize)]
|
||||
pub enum EnumTest {
|
||||
/// Variant documentation
|
||||
#[default]
|
||||
Variant1,
|
||||
Variant2 {
|
||||
field1: String,
|
||||
field2: u32,
|
||||
},
|
||||
Variant3(isize, usize),
|
||||
Variant4(usize),
|
||||
}
|
||||
eprintln!(
|
||||
"{:#?}",
|
||||
EnumTest::get_type_registration().type_info().build_schema()
|
||||
);
|
||||
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()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user