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"]
[features]
default = ["http", "bevy_asset", "documentation"]
default = ["http", "bevy_asset", "documentation", "bevy_math"]
documentation = ["bevy_reflect/documentation"]
http = ["dep:async-io", "dep:smol-hyper"]
bevy_asset = ["dep:bevy_asset"]
bevy_math = ["dep:bevy_math", "bevy_reflect/glam"]
[dependencies]
# bevy
@ -31,6 +32,7 @@ bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-fea
"serialize",
] }
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
anyhow = "1"

View File

@ -518,7 +518,8 @@ impl Default for RemotePlugin {
impl Plugin for RemotePlugin {
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();

View File

@ -11,7 +11,7 @@ use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::schemas::{
reflect_info::{SchemaInfoReflect, SchemaNumber},
reflect_info::{SchemaNumber, TypeReferenceId, TypeReferencePath},
ReflectJsonSchema, SchemaTypesMetadata,
};
@ -82,8 +82,7 @@ impl TryFrom<(&TypeRegistration, &SchemaTypesMetadata)> for JsonSchemaBevyType {
if let Some(s) = reg.data::<ReflectJsonSchema>() {
return Ok(s.0.clone());
}
let type_info = reg.type_info();
let base_schema = type_info.build_schema();
let base_schema = super::reflect_info::build_schema(reg);
let JsonSchemaVariant::Schema(mut typed_schema) = base_schema else {
return Err(InvalidJsonSchema::InvalidType);
@ -126,7 +125,7 @@ pub struct JsonSchemaBevyType {
/// This keyword is used to reference a statically identified schema.
#[serde(rename = "$ref")]
#[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.
#[serde(skip_serializing_if = "str::is_empty", default)]
pub short_path: Cow<'static, str>,
@ -144,16 +143,22 @@ pub struct JsonSchemaBevyType {
pub reflect_type_data: Vec<Cow<'static, str>>,
/// Bevy specific field, [`TypeInfo`] type mapping.
pub kind: SchemaKind,
/// Bevy specific field, provided when [`SchemaKind`] `kind` field is equal to [`SchemaKind::Map`].
///
/// It contains type info of key of the Map.
/// JSON Schema specific field.
/// This keyword is used to reference a constant value.
#[serde(rename = "const")]
#[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`].
///
/// It contains type info of value of the Map.
#[serde(skip_serializing_if = "Option::is_none", default)]
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.
#[serde(skip_serializing_if = "Option::is_none", default)]
#[serde(rename = "type")]
@ -231,11 +236,16 @@ pub struct JsonSchemaBevyType {
pub exclusive_maximum: Option<SchemaNumber>,
/// Type description
#[serde(skip_serializing_if = "Option::is_none", default)]
pub description: Option<String>,
pub description: Option<Cow<'static, str>>,
/// Default value for the schema.
#[serde(skip_serializing_if = "Option::is_none", default, rename = "default")]
#[reflect(ignore)]
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.
@ -250,19 +260,6 @@ pub enum JsonSchemaVariant {
/// 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,
@ -270,28 +267,6 @@ pub enum JsonSchemaVariant {
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
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default, Reflect)]
pub enum SchemaKind {
@ -312,7 +287,7 @@ pub enum SchemaKind {
TupleStruct,
/// Set of unique values
Set,
/// Single value, eg. primitive types
/// Single value, eg. primitive types or enum variant
Value,
/// Opaque type
Opaque,
@ -336,10 +311,29 @@ pub enum SchemaTypeVariant {
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
/// 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,
Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Reflect, Default, Eq, PartialOrd, Ord,
)]
#[serde(rename_all = "lowercase")]
pub enum SchemaType {
@ -403,7 +397,7 @@ 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) {
if schema_type.eq(&Self::Object) || schema_type.eq(&Self::Array) {
None
} else {
Some(schema_type)
@ -413,6 +407,8 @@ impl SchemaType {
#[cfg(test)]
mod tests {
use crate::schemas::reflect_info::ReferenceLocation;
use super::*;
use bevy_ecs::prelude::ReflectComponent;
use bevy_ecs::prelude::ReflectResource;
@ -434,10 +430,6 @@ mod tests {
else {
panic!("Failed to export JSON schema for Foo");
};
eprintln!(
"{}",
serde_json::to_string_pretty(&schema).expect("Failed to serialize schema")
);
schema
}
@ -482,12 +474,13 @@ mod tests {
#[derive(Reflect, Component, Default, Deserialize, Serialize)]
#[reflect(Component, Default, Serialize, Deserialize)]
enum EnumComponent {
ValueOne(i32),
ValueOne(Option<i32>, i32),
ValueTwo {
#[reflect(@111..5555i32)]
test: i32,
},
#[default]
/// default option
NoValue,
}
let schema = export_type::<EnumComponent>();
@ -540,7 +533,7 @@ mod tests {
#[derive(Reflect, Default, Deserialize, Serialize)]
#[reflect(Default)]
enum EnumComponent {
ValueOne(i32),
ValueOne(i32, #[reflect(@..155i16)] i16),
ValueTwo {
test: i32,
},
@ -569,6 +562,7 @@ mod tests {
let schema = type_registry
.export_type_json_schema::<EnumComponent>(&metadata)
.expect("Failed to export schema");
assert!(
!metadata.has_type_data::<ReflectComponent>(&schema.reflect_type_data),
"Should not be a component"
@ -597,11 +591,11 @@ mod tests {
impl bevy_reflect::FromType<SomeType> for ReflectJsonSchema {
fn from_type() -> Self {
JsonSchemaBevyType {
ref_type: Some(
"https://raw.githubusercontent.com/open-rpc/meta-schema/master/schema.json"
.into(),
),
description: Some("Custom type for testing purposes.".to_string()),
ref_type: Some(TypeReferencePath::new_ref(
ReferenceLocation::Url,
"raw.githubusercontent.com/open-rpc/meta-schema/master/schema.json",
)),
description: Some("Custom type for testing purposes.".into()),
..Default::default()
}
.into()
@ -625,7 +619,7 @@ mod tests {
"Should not be a component"
);
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"
);
assert!(

View File

@ -7,7 +7,7 @@ use bevy_ecs::{
};
use bevy_platform::collections::HashMap;
use bevy_reflect::{
prelude::ReflectDefault, Reflect, ReflectDeserialize, ReflectSerialize, TypeData,
prelude::ReflectDefault, FromType, Reflect, ReflectDeserialize, ReflectSerialize, TypeData,
TypeRegistration,
};
use core::any::TypeId;
@ -27,6 +27,16 @@ pub struct SchemaTypesMetadata {
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
#[derive(Clone, Deref)]
pub struct ReflectJsonSchema(pub JsonSchemaBevyType);

View File

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

File diff suppressed because it is too large Load Diff