diff --git a/crates/bevy_remote/src/lib.rs b/crates/bevy_remote/src/lib.rs index 348be8089d..08158281f0 100644 --- a/crates/bevy_remote/src/lib.rs +++ b/crates/bevy_remote/src/lib.rs @@ -518,6 +518,8 @@ impl Default for RemotePlugin { impl Plugin for RemotePlugin { fn build(&self, app: &mut App) { + app.register_type_data::(); + let mut remote_methods = RemoteMethods::new(); let plugin_methods = &mut *self.methods.write().unwrap(); diff --git a/crates/bevy_remote/src/schemas/json_schema.rs b/crates/bevy_remote/src/schemas/json_schema.rs index f0d7bf0261..31c6d2b427 100644 --- a/crates/bevy_remote/src/schemas/json_schema.rs +++ b/crates/bevy_remote/src/schemas/json_schema.rs @@ -11,7 +11,7 @@ use serde_json::Value; use crate::schemas::{ reflect_info::{SchemaInfoReflect, SchemaNumber}, - SchemaTypesMetadata, + ReflectJsonSchema, SchemaTypesMetadata, }; /// Helper trait for converting `TypeRegistration` to `JsonSchemaBevyType` @@ -68,6 +68,9 @@ impl TypeRegistrySchemaReader for TypeRegistry { impl From<(&TypeRegistration, &SchemaTypesMetadata)> for JsonSchemaBevyType { fn from(value: (&TypeRegistration, &SchemaTypesMetadata)) -> Self { let (reg, metadata) = value; + if let Some(s) = reg.data::() { + return s.0.clone(); + } let type_info = reg.type_info(); let base_schema = type_info.build_schema(); @@ -83,9 +86,14 @@ impl From<(&TypeRegistration, &SchemaTypesMetadata)> for JsonSchemaBevyType { /// It tries to follow this standard: /// /// To take the full advantage from info provided by Bevy registry it provides extra fields -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default, Reflect)] #[serde(rename_all = "camelCase")] pub struct JsonSchemaBevyType { + /// JSON Schema specific field. + /// 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, /// Bevy specific field, short path of the type. #[serde(skip_serializing_if = "String::is_empty", default)] pub short_path: String, @@ -193,6 +201,7 @@ pub struct JsonSchemaBevyType { pub description: Option, /// Default value for the schema. #[serde(skip_serializing_if = "Option::is_none", default, rename = "default")] + #[reflect(ignore)] pub default_value: Option, } @@ -251,7 +260,7 @@ impl JsonSchemaVariant { } /// Kind of json schema, maps [`TypeInfo`] type -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default, Reflect)] pub enum SchemaKind { /// Struct #[default] @@ -528,6 +537,48 @@ mod tests { assert!(schema.one_of.len() == 3, "Should have 3 possible schemas"); } + #[test] + fn reflect_export_with_custom_schema() { + #[derive(Reflect, Component)] + struct SomeType; + + impl bevy_reflect::FromType 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()), + ..Default::default() + } + .into() + } + } + + let atr = AppTypeRegistry::default(); + { + let mut register = atr.write(); + register.register::(); + register.register_type_data::(); + } + let type_registry = atr.read(); + let schema = type_registry + .export_type_json_schema::(&SchemaTypesMetadata::default()) + .expect("Failed to export"); + assert!( + !schema.reflect_types.contains(&"Component".to_owned()), + "Should not be a component" + ); + assert!( + schema.ref_type.is_some_and(|t| !t.is_empty()), + "Should have a reference type" + ); + assert!( + schema.description.is_some_and(|t| !t.is_empty()), + "Should have a description" + ); + } #[test] fn reflect_export_tuple_struct() { #[derive(Reflect, Component, Default, Deserialize, Serialize)] diff --git a/crates/bevy_remote/src/schemas/mod.rs b/crates/bevy_remote/src/schemas/mod.rs index ad5af3047b..de606233f5 100644 --- a/crates/bevy_remote/src/schemas/mod.rs +++ b/crates/bevy_remote/src/schemas/mod.rs @@ -1,4 +1,5 @@ //! Module with schemas used for various BRP endpoints +use bevy_derive::Deref; use bevy_ecs::{ reflect::{ReflectComponent, ReflectResource}, resource::Resource, @@ -10,6 +11,8 @@ use bevy_reflect::{ }; use core::any::TypeId; +use crate::schemas::json_schema::JsonSchemaBevyType; + pub mod json_schema; pub mod open_rpc; pub mod reflect_info; @@ -23,6 +26,22 @@ pub struct SchemaTypesMetadata { pub type_data_map: HashMap, } +/// Reflect-compatible custom JSON Schema for this type +#[derive(Clone, Deref)] +pub struct ReflectJsonSchema(pub JsonSchemaBevyType); + +impl From<&JsonSchemaBevyType> for ReflectJsonSchema { + fn from(schema: &JsonSchemaBevyType) -> Self { + Self(schema.clone()) + } +} + +impl From for ReflectJsonSchema { + fn from(schema: JsonSchemaBevyType) -> Self { + Self(schema) + } +} + impl Default for SchemaTypesMetadata { fn default() -> Self { let mut data_types = Self { diff --git a/crates/bevy_remote/src/schemas/open_rpc.rs b/crates/bevy_remote/src/schemas/open_rpc.rs index 0ffda36bc3..8c4e954149 100644 --- a/crates/bevy_remote/src/schemas/open_rpc.rs +++ b/crates/bevy_remote/src/schemas/open_rpc.rs @@ -1,15 +1,16 @@ //! Module with trimmed down `OpenRPC` document structs. //! It tries to follow this standard: use bevy_platform::collections::HashMap; +use bevy_reflect::{FromType, Reflect}; use bevy_utils::default; use serde::{Deserialize, Serialize}; -use crate::RemoteMethods; +use crate::{schemas::ReflectJsonSchema, RemoteMethods}; use super::json_schema::JsonSchemaBevyType; /// Represents an `OpenRPC` document as defined by the `OpenRPC` specification. -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Reflect)] #[serde(rename_all = "camelCase")] pub struct OpenRpcDocument { /// The version of the `OpenRPC` specification being used. @@ -22,8 +23,24 @@ pub struct OpenRpcDocument { pub servers: Option>, } +impl FromType for ReflectJsonSchema { + fn from_type() -> Self { + JsonSchemaBevyType { + ref_type: Some( + "https://raw.githubusercontent.com/open-rpc/meta-schema/master/schema.json".into(), + ), + description: Some( + "Represents an `OpenRPC` document as defined by the `OpenRPC` specification." + .to_string(), + ), + ..default() + } + .into() + } +} + /// Contains metadata information about the `OpenRPC` document. -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Reflect)] #[serde(rename_all = "camelCase")] pub struct InfoObject { /// The title of the API or document. @@ -35,6 +52,7 @@ pub struct InfoObject { pub description: Option, /// A collection of custom extension fields. #[serde(flatten)] + #[reflect(ignore)] pub extensions: HashMap, } @@ -50,7 +68,7 @@ impl Default for InfoObject { } /// Describes a server hosting the API as specified in the `OpenRPC` document. -#[derive(Serialize, Deserialize, Debug, Default)] +#[derive(Serialize, Deserialize, Debug, Default, Reflect)] #[serde(rename_all = "camelCase")] pub struct ServerObject { /// The name of the server. @@ -62,11 +80,12 @@ pub struct ServerObject { pub description: Option, /// Additional custom extension fields. #[serde(flatten)] + #[reflect(ignore)] pub extensions: HashMap, } /// Represents an RPC method in the `OpenRPC` document. -#[derive(Serialize, Deserialize, Debug, Default)] +#[derive(Serialize, Deserialize, Debug, Default, Reflect)] #[serde(rename_all = "camelCase")] pub struct MethodObject { /// The method name (e.g., "/bevy/get") @@ -85,11 +104,12 @@ pub struct MethodObject { // pub result: Option, /// Additional custom extension fields. #[serde(flatten)] + #[reflect(ignore)] pub extensions: HashMap, } /// Represents an RPC method parameter in the `OpenRPC` document. -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Reflect)] #[serde(rename_all = "camelCase")] pub struct Parameter { /// Parameter name @@ -101,6 +121,7 @@ pub struct Parameter { pub schema: JsonSchemaBevyType, /// Additional custom extension fields. #[serde(flatten)] + #[reflect(ignore)] pub extensions: HashMap, }