Cleanup and refactor

This commit is contained in:
Piotr Siuszko 2025-07-01 21:57:10 +02:00
parent 2f03beb2d0
commit 27366c9f4f
6 changed files with 1211 additions and 541 deletions

View File

@ -9,10 +9,11 @@ license = "MIT OR Apache-2.0"
keywords = ["bevy"] keywords = ["bevy"]
[features] [features]
default = ["http", "bevy_asset", "documentation"] default = ["http", "bevy_asset", "documentation", "bevy_math"]
documentation = ["bevy_reflect/documentation"] documentation = ["bevy_reflect/documentation"]
http = ["dep:async-io", "dep:smol-hyper"] http = ["dep:async-io", "dep:smol-hyper"]
bevy_asset = ["dep:bevy_asset"] bevy_asset = ["dep:bevy_asset"]
bevy_math = ["dep:bevy_math", "bevy_reflect/glam"]
[dependencies] [dependencies]
# bevy # bevy
@ -31,6 +32,7 @@ bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-fea
"serialize", "serialize",
] } ] }
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev", optional = true } bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev", optional = true }
bevy_math = { path = "../bevy_math", version = "0.17.0-dev", optional = true }
# other # other
anyhow = "1" anyhow = "1"

View File

@ -518,7 +518,8 @@ impl Default for RemotePlugin {
impl Plugin for RemotePlugin { impl Plugin for RemotePlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.register_type::<schemas::open_rpc::OpenRpcDocument>().register_type_data::<schemas::open_rpc::OpenRpcDocument, schemas::ReflectJsonSchema>(); app.register_type::<schemas::open_rpc::OpenRpcDocument>()
.register_type_data::<schemas::open_rpc::OpenRpcDocument, schemas::ReflectJsonSchema>();
let mut remote_methods = RemoteMethods::new(); let mut remote_methods = RemoteMethods::new();

View File

@ -11,7 +11,7 @@ use serde::{Deserialize, Serialize};
use serde_json::Value; use serde_json::Value;
use crate::schemas::{ use crate::schemas::{
reflect_info::{SchemaInfoReflect, SchemaNumber}, reflect_info::{SchemaNumber, TypeReferenceId, TypeReferencePath},
ReflectJsonSchema, SchemaTypesMetadata, ReflectJsonSchema, SchemaTypesMetadata,
}; };
@ -82,8 +82,7 @@ impl TryFrom<(&TypeRegistration, &SchemaTypesMetadata)> for JsonSchemaBevyType {
if let Some(s) = reg.data::<ReflectJsonSchema>() { if let Some(s) = reg.data::<ReflectJsonSchema>() {
return Ok(s.0.clone()); return Ok(s.0.clone());
} }
let type_info = reg.type_info(); let base_schema = super::reflect_info::build_schema(reg);
let base_schema = type_info.build_schema();
let JsonSchemaVariant::Schema(mut typed_schema) = base_schema else { let JsonSchemaVariant::Schema(mut typed_schema) = base_schema else {
return Err(InvalidJsonSchema::InvalidType); return Err(InvalidJsonSchema::InvalidType);
@ -126,7 +125,7 @@ pub struct JsonSchemaBevyType {
/// This keyword is used to reference a statically identified schema. /// This keyword is used to reference a statically identified schema.
#[serde(rename = "$ref")] #[serde(rename = "$ref")]
#[serde(skip_serializing_if = "Option::is_none", default)] #[serde(skip_serializing_if = "Option::is_none", default)]
pub ref_type: Option<Cow<'static, str>>, pub ref_type: Option<TypeReferencePath>,
/// Bevy specific field, short path of the type. /// Bevy specific field, short path of the type.
#[serde(skip_serializing_if = "str::is_empty", default)] #[serde(skip_serializing_if = "str::is_empty", default)]
pub short_path: Cow<'static, str>, pub short_path: Cow<'static, str>,
@ -144,16 +143,22 @@ pub struct JsonSchemaBevyType {
pub reflect_type_data: Vec<Cow<'static, str>>, pub reflect_type_data: Vec<Cow<'static, str>>,
/// Bevy specific field, [`TypeInfo`] type mapping. /// Bevy specific field, [`TypeInfo`] type mapping.
pub kind: SchemaKind, pub kind: SchemaKind,
/// Bevy specific field, provided when [`SchemaKind`] `kind` field is equal to [`SchemaKind::Map`]. /// JSON Schema specific field.
/// /// This keyword is used to reference a constant value.
/// It contains type info of key of the Map. #[serde(rename = "const")]
#[serde(skip_serializing_if = "Option::is_none", default)] #[serde(skip_serializing_if = "Option::is_none", default)]
pub key_type: Option<JsonSchemaVariant>, #[reflect(ignore)]
pub const_value: Option<Value>,
/// Bevy specific field, provided when [`SchemaKind`] `kind` field is equal to [`SchemaKind::Map`]. /// Bevy specific field, provided when [`SchemaKind`] `kind` field is equal to [`SchemaKind::Map`].
/// ///
/// It contains type info of value of the Map. /// It contains type info of value of the Map.
#[serde(skip_serializing_if = "Option::is_none", default)] #[serde(skip_serializing_if = "Option::is_none", default)]
pub value_type: Option<JsonSchemaVariant>, pub value_type: Option<JsonSchemaVariant>,
/// Bevy specific field, provided when [`SchemaKind`] `kind` field is equal to [`SchemaKind::Map`].
///
/// It contains type info of key of the Map.
#[serde(skip_serializing_if = "Option::is_none", default)]
pub key_type: Option<JsonSchemaVariant>,
/// The type keyword is fundamental to JSON Schema. It specifies the data type for a schema. /// 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(skip_serializing_if = "Option::is_none", default)]
#[serde(rename = "type")] #[serde(rename = "type")]
@ -231,11 +236,16 @@ pub struct JsonSchemaBevyType {
pub exclusive_maximum: Option<SchemaNumber>, pub exclusive_maximum: Option<SchemaNumber>,
/// Type description /// Type description
#[serde(skip_serializing_if = "Option::is_none", default)] #[serde(skip_serializing_if = "Option::is_none", default)]
pub description: Option<String>, pub description: Option<Cow<'static, str>>,
/// Default value for the schema. /// Default value for the schema.
#[serde(skip_serializing_if = "Option::is_none", default, rename = "default")] #[serde(skip_serializing_if = "Option::is_none", default, rename = "default")]
#[reflect(ignore)] #[reflect(ignore)]
pub default_value: Option<Value>, pub default_value: Option<Value>,
/// Definitions for the schema.
#[serde(skip_serializing_if = "HashMap::is_empty", default)]
#[reflect(ignore)]
#[serde(rename = "$defs")]
pub definitions: HashMap<TypeReferenceId, Box<JsonSchemaVariant>>,
} }
/// Represents different types of JSON Schema values that can be used in schema definitions. /// Represents different types of JSON Schema values that can be used in schema definitions.
@ -250,19 +260,6 @@ pub enum JsonSchemaVariant {
/// This is commonly used for properties like `additionalProperties` where /// This is commonly used for properties like `additionalProperties` where
/// a boolean true/false indicates whether additional properties are allowed. /// a boolean true/false indicates whether additional properties are allowed.
BoolValue(bool), 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. /// A full JSON Schema definition.
/// ///
/// This variant contains a complete schema object that defines the structure, /// This variant contains a complete schema object that defines the structure,
@ -270,28 +267,6 @@ pub enum JsonSchemaVariant {
Schema(#[reflect(ignore)] Box<JsonSchemaBevyType>), 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(),
description,
}
}
}
/// Kind of json schema, maps [`TypeInfo`] type /// Kind of json schema, maps [`TypeInfo`] type
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default, Reflect)] #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default, Reflect)]
pub enum SchemaKind { pub enum SchemaKind {
@ -312,7 +287,7 @@ pub enum SchemaKind {
TupleStruct, TupleStruct,
/// Set of unique values /// Set of unique values
Set, Set,
/// Single value, eg. primitive types /// Single value, eg. primitive types or enum variant
Value, Value,
/// Opaque type /// Opaque type
Opaque, Opaque,
@ -336,10 +311,29 @@ pub enum SchemaTypeVariant {
Multiple(Vec<SchemaType>), Multiple(Vec<SchemaType>),
} }
impl SchemaTypeVariant {
/// Adds a new type to the variant.
pub fn with(self, new: SchemaType) -> Self {
match self {
Self::Single(t) => match t.eq(&new) {
true => Self::Single(t),
false => Self::Multiple(vec![t, new]),
},
Self::Multiple(mut types) => match types.contains(&new) {
true => Self::Multiple(types),
false => {
types.push(new);
Self::Multiple(types)
}
},
}
}
}
/// Type of json schema /// Type of json schema
/// More [here](https://json-schema.org/draft-07/draft-handrews-json-schema-01#rfc.section.4.2.1) /// More [here](https://json-schema.org/draft-07/draft-handrews-json-schema-01#rfc.section.4.2.1)
#[derive( #[derive(
Debug, Serialize, Deserialize, Clone, PartialEq, Reflect, Default, Eq, PartialOrd, Ord, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Reflect, Default, Eq, PartialOrd, Ord,
)] )]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub enum SchemaType { pub enum SchemaType {
@ -403,7 +397,7 @@ impl SchemaType {
/// Returns the primitive type corresponding to the given type ID, if it exists. /// 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> { pub fn try_get_primitive_type_from_type_id(type_id: TypeId) -> Option<Self> {
let schema_type: SchemaType = type_id.into(); let schema_type: SchemaType = type_id.into();
if schema_type.eq(&Self::Object) { if schema_type.eq(&Self::Object) || schema_type.eq(&Self::Array) {
None None
} else { } else {
Some(schema_type) Some(schema_type)
@ -413,6 +407,8 @@ impl SchemaType {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::schemas::reflect_info::ReferenceLocation;
use super::*; use super::*;
use bevy_ecs::prelude::ReflectComponent; use bevy_ecs::prelude::ReflectComponent;
use bevy_ecs::prelude::ReflectResource; use bevy_ecs::prelude::ReflectResource;
@ -434,10 +430,6 @@ mod tests {
else { else {
panic!("Failed to export JSON schema for Foo"); panic!("Failed to export JSON schema for Foo");
}; };
eprintln!(
"{}",
serde_json::to_string_pretty(&schema).expect("Failed to serialize schema")
);
schema schema
} }
@ -482,12 +474,13 @@ mod tests {
#[derive(Reflect, Component, Default, Deserialize, Serialize)] #[derive(Reflect, Component, Default, Deserialize, Serialize)]
#[reflect(Component, Default, Serialize, Deserialize)] #[reflect(Component, Default, Serialize, Deserialize)]
enum EnumComponent { enum EnumComponent {
ValueOne(i32), ValueOne(Option<i32>, i32),
ValueTwo { ValueTwo {
#[reflect(@111..5555i32)] #[reflect(@111..5555i32)]
test: i32, test: i32,
}, },
#[default] #[default]
/// default option
NoValue, NoValue,
} }
let schema = export_type::<EnumComponent>(); let schema = export_type::<EnumComponent>();
@ -540,7 +533,7 @@ mod tests {
#[derive(Reflect, Default, Deserialize, Serialize)] #[derive(Reflect, Default, Deserialize, Serialize)]
#[reflect(Default)] #[reflect(Default)]
enum EnumComponent { enum EnumComponent {
ValueOne(i32), ValueOne(i32, #[reflect(@..155i16)] i16),
ValueTwo { ValueTwo {
test: i32, test: i32,
}, },
@ -569,6 +562,7 @@ mod tests {
let schema = type_registry let schema = type_registry
.export_type_json_schema::<EnumComponent>(&metadata) .export_type_json_schema::<EnumComponent>(&metadata)
.expect("Failed to export schema"); .expect("Failed to export schema");
assert!( assert!(
!metadata.has_type_data::<ReflectComponent>(&schema.reflect_type_data), !metadata.has_type_data::<ReflectComponent>(&schema.reflect_type_data),
"Should not be a component" "Should not be a component"
@ -597,11 +591,11 @@ mod tests {
impl bevy_reflect::FromType<SomeType> for ReflectJsonSchema { impl bevy_reflect::FromType<SomeType> for ReflectJsonSchema {
fn from_type() -> Self { fn from_type() -> Self {
JsonSchemaBevyType { JsonSchemaBevyType {
ref_type: Some( ref_type: Some(TypeReferencePath::new_ref(
"https://raw.githubusercontent.com/open-rpc/meta-schema/master/schema.json" ReferenceLocation::Url,
.into(), "raw.githubusercontent.com/open-rpc/meta-schema/master/schema.json",
), )),
description: Some("Custom type for testing purposes.".to_string()), description: Some("Custom type for testing purposes.".into()),
..Default::default() ..Default::default()
} }
.into() .into()
@ -625,7 +619,7 @@ mod tests {
"Should not be a component" "Should not be a component"
); );
assert!( assert!(
schema.ref_type.is_some_and(|t| !t.is_empty()), schema.ref_type.is_some_and(|t| !t.to_string().is_empty()),
"Should have a reference type" "Should have a reference type"
); );
assert!( assert!(

View File

@ -7,7 +7,7 @@ use bevy_ecs::{
}; };
use bevy_platform::collections::HashMap; use bevy_platform::collections::HashMap;
use bevy_reflect::{ use bevy_reflect::{
prelude::ReflectDefault, Reflect, ReflectDeserialize, ReflectSerialize, TypeData, prelude::ReflectDefault, FromType, Reflect, ReflectDeserialize, ReflectSerialize, TypeData,
TypeRegistration, TypeRegistration,
}; };
use core::any::TypeId; use core::any::TypeId;
@ -27,6 +27,16 @@ pub struct SchemaTypesMetadata {
pub type_data_map: HashMap<TypeId, Cow<'static, str>>, pub type_data_map: HashMap<TypeId, Cow<'static, str>>,
} }
/// Reflect-compatible custom JSON Schema for this type
#[derive(Clone)]
pub struct ReflectJsonSchemaForceAsArray;
impl<T: Reflect> FromType<T> for ReflectJsonSchemaForceAsArray {
fn from_type() -> Self {
ReflectJsonSchemaForceAsArray
}
}
/// Reflect-compatible custom JSON Schema for this type /// Reflect-compatible custom JSON Schema for this type
#[derive(Clone, Deref)] #[derive(Clone, Deref)]
pub struct ReflectJsonSchema(pub JsonSchemaBevyType); pub struct ReflectJsonSchema(pub JsonSchemaBevyType);

View File

@ -5,7 +5,13 @@ use bevy_reflect::{FromType, Reflect};
use bevy_utils::default; use bevy_utils::default;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{schemas::ReflectJsonSchema, RemoteMethods}; use crate::{
schemas::{
reflect_info::{ReferenceLocation, TypeReferencePath},
ReflectJsonSchema,
},
RemoteMethods,
};
use super::json_schema::JsonSchemaBevyType; use super::json_schema::JsonSchemaBevyType;
@ -26,12 +32,13 @@ pub struct OpenRpcDocument {
impl FromType<OpenRpcDocument> for ReflectJsonSchema { impl FromType<OpenRpcDocument> for ReflectJsonSchema {
fn from_type() -> Self { fn from_type() -> Self {
JsonSchemaBevyType { JsonSchemaBevyType {
ref_type: Some( ref_type: Some(TypeReferencePath::new_ref(
"https://raw.githubusercontent.com/open-rpc/meta-schema/master/schema.json".into(), ReferenceLocation::Url,
), "raw.githubusercontent.com/open-rpc/meta-schema/master/schema.json",
)),
description: Some( description: Some(
"Represents an `OpenRPC` document as defined by the `OpenRPC` specification." "Represents an `OpenRPC` document as defined by the `OpenRPC` specification."
.to_string(), .into(),
), ),
..default() ..default()
} }

File diff suppressed because it is too large Load Diff