This commit is contained in:
MevLyshkin 2025-07-18 17:46:44 +02:00 committed by GitHub
commit 88e45d9ed3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 3534 additions and 394 deletions

View File

@ -9,9 +9,11 @@ license = "MIT OR Apache-2.0"
keywords = ["bevy"]
[features]
default = ["http", "bevy_asset"]
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
@ -30,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 }
bevy_log = { path = "../bevy_log", version = "0.17.0-dev" }
# other
@ -40,6 +43,9 @@ serde_json = "1.0.140"
http-body-util = "0.1"
async-channel = "2"
[dev-dependencies]
jsonschema = "0.30.0"
# dependencies that will not compile on wasm
[target.'cfg(not(target_family = "wasm"))'.dependencies]
async-io = { version = "2", optional = true }

View File

@ -1,5 +1,6 @@
//! Built-in verbs for the Bevy Remote Protocol.
use alloc::borrow::Cow;
use core::any::TypeId;
use anyhow::{anyhow, Result as AnyhowResult};
@ -26,8 +27,9 @@ use serde_json::{Map, Value};
use crate::{
error_codes,
schemas::{
json_schema::{export_type, JsonSchemaBevyType},
json_schema::{JsonSchemaBevyType, JsonSchemaVariant, SchemaMarker},
open_rpc::OpenRpcDocument,
reflect_info::{TypeDefinitionBuilder, TypeReferencePath},
},
BrpError, BrpResult,
};
@ -357,9 +359,34 @@ pub struct BrpJsonSchemaQueryFilter {
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub with_crates: Vec<String>,
/// Constrain resource by type
/// Constrain resource by data type.
/// Mapping of data types to their corresponding JSON schema types
/// is provided by the [`crate::schemas::SchemaTypesMetadata`] resource.
#[serde(default)]
pub type_limit: JsonSchemaTypeLimit,
/// Add `one_of` field to the root of the schema.
/// It will allow to use the schema for validation values if they match the format used by the Bevy reflect serialization.
#[serde(default)]
pub meta_schemas_export: bool,
}
impl BrpJsonSchemaQueryFilter {
/// Check if the filter should skip a type registration based on the crate name.
pub fn should_skip_for_crate(&self, type_registration: &TypeRegistration) -> bool {
let crate_name = type_registration
.type_info()
.type_path_table()
.crate_name()
.unwrap_or_default();
if !self.with_crates.is_empty() && !self.with_crates.iter().any(|c| crate_name.eq(c)) {
return true;
}
if !self.without_crates.is_empty() && self.without_crates.iter().any(|c| crate_name.eq(c)) {
return true;
}
false
}
}
/// Additional [`BrpJsonSchemaQueryFilter`] constraints that can be placed on a query to include or exclude
@ -375,6 +402,34 @@ pub struct JsonSchemaTypeLimit {
pub with: Vec<String>,
}
impl JsonSchemaTypeLimit {
/// Check if the type limit is empty.
pub fn is_empty(&self) -> bool {
self.without.is_empty() && self.with.is_empty()
}
/// Check if the type limit should skip a type.
pub fn should_skip_type(&self, registered_types: &[&Cow<'_, str>]) -> bool {
if !self.with.is_empty()
&& !self
.with
.iter()
.any(|c| registered_types.iter().any(|cc| c.eq(*cc)))
{
return true;
}
if !self.without.is_empty()
&& self
.without
.iter()
.any(|c| registered_types.iter().any(|cc| c.eq(*cc)))
{
return true;
}
false
}
}
/// A response from the world to the client that specifies a single entity.
///
/// This is sent in response to `bevy/spawn`.
@ -1343,57 +1398,94 @@ pub fn process_remote_list_watching_request(
}
}
fn export_registry_types_typed(
filter: BrpJsonSchemaQueryFilter,
world: &World,
) -> Result<JsonSchemaBevyType, BrpError> {
let metadata = world.resource::<crate::schemas::SchemaTypesMetadata>();
let types = world.resource::<AppTypeRegistry>();
let types = types.read();
let mut schema = JsonSchemaBevyType {
schema: Some(SchemaMarker.into()),
description: Some(
"This schema represents the types registered in the Bevy application.".into(),
),
..Default::default()
};
let definitions: Vec<TypeId> = types
.iter()
.filter_map(|type_reg| {
if filter.should_skip_for_crate(type_reg) {
return None;
}
let registered_types = metadata.get_registered_reflect_types(type_reg);
if filter
.type_limit
.should_skip_type(registered_types.as_slice())
{
return None;
}
let type_id = type_reg.type_id();
let mut dep_ids = types.get_type_dependencies(type_id);
dep_ids.insert(type_id);
Some(dep_ids)
})
.flatten()
.collect();
schema.definitions = definitions
.into_iter()
.flat_map(|id| {
let result = types.build_schema_for_type_id(id, metadata, true);
let Some((Some(schema_id), schema)) = result else {
return None;
};
Some((schema_id, Box::new(schema)))
})
.collect();
if filter.meta_schemas_export {
schema.one_of = schema
.definitions
.iter()
.flat_map(|(id, schema)| {
if !schema.reflect_type_data.contains(&"Serialize".into())
|| !schema.reflect_type_data.contains(&"Deserialize".into())
{
return None;
}
let schema = JsonSchemaBevyType {
properties: [(
schema.type_path.clone(),
JsonSchemaBevyType {
ref_type: Some(TypeReferencePath::definition((*id).clone())),
description: schema.description.clone(),
..Default::default()
}
.into(),
)]
.into(),
schema_type: Some(crate::schemas::json_schema::SchemaType::Object.into()),
additional_properties: Some(JsonSchemaVariant::BoolValue(false)),
description: schema.description.clone(),
reflect_type_data: schema.reflect_type_data.clone(),
..Default::default()
};
Some(schema.into())
})
.collect();
}
Ok(schema)
}
/// Handles a `bevy/registry/schema` request (list all registry types in form of schema) coming from a client.
pub fn export_registry_types(In(params): In<Option<Value>>, world: &World) -> BrpResult {
let filter: BrpJsonSchemaQueryFilter = match params {
None => Default::default(),
Some(params) => parse(params)?,
};
let extra_info = world.resource::<crate::schemas::SchemaTypesMetadata>();
let types = world.resource::<AppTypeRegistry>();
let types = types.read();
let schemas = types
.iter()
.filter_map(|type_reg| {
let path_table = type_reg.type_info().type_path_table();
if let Some(crate_name) = &path_table.crate_name() {
if !filter.with_crates.is_empty()
&& !filter.with_crates.iter().any(|c| crate_name.eq(c))
{
return None;
}
if !filter.without_crates.is_empty()
&& filter.without_crates.iter().any(|c| crate_name.eq(c))
{
return None;
}
}
let (id, schema) = export_type(type_reg, extra_info);
if !filter.type_limit.with.is_empty()
&& !filter
.type_limit
.with
.iter()
.any(|c| schema.reflect_types.iter().any(|cc| c.eq(cc)))
{
return None;
}
if !filter.type_limit.without.is_empty()
&& filter
.type_limit
.without
.iter()
.any(|c| schema.reflect_types.iter().any(|cc| c.eq(cc)))
{
return None;
}
Some((id.to_string(), schema))
})
.collect::<HashMap<String, JsonSchemaBevyType>>();
serde_json::to_value(schemas).map_err(BrpError::internal)
let result = export_registry_types_typed(filter, world)?;
serde_json::to_value(result).map_err(BrpError::internal)
}
/// Immutably retrieves an entity from the [`World`], returning an error if the
@ -1625,6 +1717,11 @@ mod tests {
);
}
use bevy_ecs::component::Component;
use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize};
use crate::schemas::SchemaTypesMetadata;
use super::*;
#[test]
@ -1648,4 +1745,207 @@ mod tests {
entity: Entity::from_raw_u32(0).unwrap(),
});
}
#[test]
fn reflection_serialization_tests() {
use bevy_ecs::resource::Resource;
#[derive(Reflect, Default, Deserialize, Serialize, Resource)]
#[reflect(Resource, Serialize, Deserialize)]
pub struct ResourceStruct {
/// FIELD DOC
pub field: String,
pub second_field: Option<(u8, Option<u16>)>,
}
#[derive(Reflect, Default, Deserialize, Serialize, Component)]
#[reflect(Component, Serialize, Deserialize)]
pub struct OtherStruct {
/// FIELD DOC
pub field: String,
pub second_field: Option<(u8, Option<u16>)>,
}
/// STRUCT DOC
#[derive(Reflect, Default, Deserialize, Serialize, Component)]
#[reflect(Component, Serialize, Deserialize)]
pub struct SecondStruct;
/// STRUCT DOC
#[derive(Reflect, Default, Deserialize, Serialize, Component)]
#[reflect(Component, Serialize, Deserialize)]
pub struct ThirdStruct {
pub array_strings: Vec<String>,
pub array_structs: [OtherStruct; 5],
// pub map_strings: HashMap<String, i32>,
}
#[derive(Reflect, Default, Deserialize, Serialize, Component)]
#[reflect(Component, Serialize, Deserialize)]
pub struct NestedStruct {
pub other: OtherStruct,
pub second: SecondStruct,
/// DOC FOR FIELD
pub third: ThirdStruct,
}
let atr = AppTypeRegistry::default();
{
let mut register = atr.write();
register.register::<NestedStruct>();
register.register::<ResourceStruct>();
}
let value = NestedStruct {
other: OtherStruct {
field: "S".into(),
second_field: Some((0, None)),
},
second: SecondStruct,
third: ThirdStruct {
array_strings: ["s".into(), "SS".into()].into(),
array_structs: [
OtherStruct::default(),
OtherStruct::default(),
OtherStruct::default(),
OtherStruct::default(),
OtherStruct::default(),
],
},
};
let mut world = World::new();
world.insert_resource(atr.clone());
world.insert_resource(SchemaTypesMetadata::default());
let response = export_registry_types_ext(BrpJsonSchemaQueryFilter::default(), &world);
let schema_value = serde_json::to_value(response).expect("Failed to serialize schema");
let type_registry = atr.read();
let serializer = ReflectSerializer::new(&value, &type_registry);
let res = ResourceStruct {
field: "SET".into(),
second_field: Some((45, None)),
};
let serializer_2 = ReflectSerializer::new(&res, &type_registry);
let json = serde_json::to_value(&serializer).expect("msg");
let validator = jsonschema::options()
.with_draft(jsonschema::Draft::Draft202012)
.build(&schema_value)
.expect("Failed to validate json schema");
assert!(
validator.validate(&json).is_ok(),
"Validation failed: {}",
serde_json::to_string_pretty(&serializer).expect("msg")
);
let json = serde_json::to_value(&serializer_2).expect("msg");
assert!(validator.validate(&json).is_ok());
}
#[test]
#[cfg(feature = "bevy_math")]
fn export_schema_test() {
#[derive(Reflect, Default, Deserialize, Serialize, Component)]
#[reflect(Component, Serialize, Deserialize)]
pub struct OtherStruct {
/// FIELD DOC
pub field: String,
pub second_field: Option<(u8, Option<u16>)>,
}
/// STRUCT DOC
#[derive(Reflect, Default, Deserialize, Serialize, Component)]
#[reflect(Component, Serialize, Deserialize)]
pub struct SecondStruct;
/// STRUCT DOC
#[derive(Reflect, Default, Deserialize, Serialize, Component)]
#[reflect(Component, Serialize, Deserialize)]
pub struct ThirdStruct {
pub array_strings: Vec<String>,
pub array_structs: [OtherStruct; 5],
// pub map_strings: HashMap<String, i32>,
}
#[derive(Reflect, Default, Deserialize, Serialize, Component)]
#[reflect(Component, Serialize, Deserialize)]
pub struct NestedStruct {
pub other: OtherStruct,
pub second: SecondStruct,
/// DOC FOR FIELD
pub third: ThirdStruct,
}
let atr = AppTypeRegistry::default();
{
use crate::schemas::RegisterReflectJsonSchemas;
let mut register = atr.write();
register.register::<NestedStruct>();
register.register::<bevy_math::Vec3>();
register.registry_force_schema_to_be_array::<bevy_math::Vec3>();
}
let mut world = World::new();
world.insert_resource(atr);
world.insert_resource(SchemaTypesMetadata::default());
let response = export_registry_types_ext(BrpJsonSchemaQueryFilter::default(), &world);
let schema_value = serde_json::to_value(&response).unwrap();
_ = jsonschema::options()
.with_draft(jsonschema::Draft::Draft202012)
.build(&schema_value)
.expect("Failed to validate json schema");
assert_eq!(
response.definitions.len(),
6,
"Expected 6 definitions, got: {}: {}",
response.definitions.keys().len(),
serde_json::to_string_pretty(&response).unwrap_or_default()
);
let response = export_registry_types_ext(
BrpJsonSchemaQueryFilter {
with_crates: vec!["glam".to_string()],
..Default::default()
},
&world,
);
assert_eq!(
response.definitions.len(),
1,
"Expected 1 definition, got: {}: {:?}",
response.definitions.keys().len(),
response.definitions.keys()
);
{
use crate::schemas::reflect_info::TypeReferenceId;
let first = response.definitions.iter().next().expect("Should have one");
assert_eq!(first.0, &TypeReferenceId::from("glam::Vec3"));
}
let response = export_registry_types_ext(
BrpJsonSchemaQueryFilter {
with_crates: vec!["bevy_remote".to_string()],
..Default::default()
},
&world,
);
assert_eq!(
response.definitions.len(),
5,
"Expected 5 definitions, got: {}: {:?}",
response.definitions.len(),
response.definitions.keys()
);
let response = export_registry_types_ext(
BrpJsonSchemaQueryFilter {
type_limit: JsonSchemaTypeLimit {
with: vec!["Component".to_string()],
..Default::default()
},
..Default::default()
},
&world,
);
assert_eq!(
response.definitions.len(),
5,
"Expected 5 definitions, got: {}: {:?}",
response.definitions.len(),
response.definitions.keys()
);
}
fn export_registry_types_ext(
input: BrpJsonSchemaQueryFilter,
world: &World,
) -> JsonSchemaBevyType {
export_registry_types_typed(input, world).expect("Failed to export registry types")
}
}

View File

@ -517,6 +517,8 @@ use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::sync::RwLock;
use crate::schemas::RegisterReflectJsonSchemas;
pub mod builtin_methods;
#[cfg(feature = "http")]
pub mod http;
@ -653,6 +655,7 @@ impl Default for RemotePlugin {
impl Plugin for RemotePlugin {
fn build(&self, app: &mut App) {
app.register_schema_base_types();
let mut remote_methods = RemoteMethods::new();
let plugin_methods = &mut *self.methods.write().unwrap();

File diff suppressed because it is too large Load Diff

View File

@ -1,25 +1,203 @@
//! Module with schemas used for various BRP endpoints
use alloc::borrow::Cow;
use bevy_ecs::{
reflect::{ReflectComponent, ReflectResource},
resource::Resource,
};
use bevy_platform::collections::HashMap;
use bevy_reflect::{
prelude::ReflectDefault, Reflect, ReflectDeserialize, ReflectSerialize, TypeData,
TypeRegistration,
prelude::ReflectDefault, FromType, GetTypeRegistration, Reflect, ReflectDeserialize,
ReflectSerialize, TypeData, TypePath, TypeRegistration,
};
use core::any::TypeId;
use crate::schemas::{
open_rpc::OpenRpcDocument,
reflect_info::{FieldsInformation, InternalSchemaType, TypeReferencePath},
};
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)]
#[derive(Debug, Resource, Reflect, Clone)]
#[reflect(Resource)]
pub struct SchemaTypesMetadata {
/// Type Data id mapping to strings.
pub type_data_map: HashMap<TypeId, String>,
/// Type Data id mapping to human-readable type names.
pub type_data_map: HashMap<TypeId, Cow<'static, str>>,
}
/// Custom internal schema data.
#[derive(Debug, Resource, Reflect, Clone)]
#[reflect(Resource)]
pub struct CustomInternalSchemaData(pub InternalSchemaType);
/// Trait for external schema sources.
pub trait ExternalSchemaSource {
/// Get the external schema source.
fn get_external_schema_source() -> TypeReferencePath;
}
impl<T: Reflect + ExternalSchemaSource> FromType<T> for CustomInternalSchemaData {
fn from_type() -> Self {
Self(InternalSchemaType::ExternalSource(
T::get_external_schema_source(),
))
}
}
/// Helper trait
pub(crate) trait RegisterReflectJsonSchemas {
/// Register types and or type data that are implemented by this crate
fn register_schema_base_types(&mut self) {
#[cfg(feature = "bevy_math")]
{
self.registry_force_schema_to_be_array::<bevy_math::Vec2>();
self.registry_force_schema_to_be_array::<bevy_math::DVec2>();
self.registry_force_schema_to_be_array::<bevy_math::I8Vec2>();
self.registry_force_schema_to_be_array::<bevy_math::U8Vec2>();
self.registry_force_schema_to_be_array::<bevy_math::I16Vec2>();
self.registry_force_schema_to_be_array::<bevy_math::U16Vec2>();
self.registry_force_schema_to_be_array::<bevy_math::IVec2>();
self.registry_force_schema_to_be_array::<bevy_math::UVec2>();
self.registry_force_schema_to_be_array::<bevy_math::I64Vec2>();
self.registry_force_schema_to_be_array::<bevy_math::U64Vec2>();
self.registry_force_schema_to_be_array::<bevy_math::BVec2>();
self.registry_force_schema_to_be_array::<bevy_math::Vec3A>();
self.registry_force_schema_to_be_array::<bevy_math::Vec3>();
self.registry_force_schema_to_be_array::<bevy_math::DVec3>();
self.registry_force_schema_to_be_array::<bevy_math::I8Vec3>();
self.registry_force_schema_to_be_array::<bevy_math::U8Vec3>();
self.registry_force_schema_to_be_array::<bevy_math::I16Vec3>();
self.registry_force_schema_to_be_array::<bevy_math::U16Vec3>();
self.registry_force_schema_to_be_array::<bevy_math::IVec3>();
self.registry_force_schema_to_be_array::<bevy_math::UVec3>();
self.registry_force_schema_to_be_array::<bevy_math::I64Vec3>();
self.registry_force_schema_to_be_array::<bevy_math::U64Vec3>();
self.registry_force_schema_to_be_array::<bevy_math::BVec3>();
self.registry_force_schema_to_be_array::<bevy_math::Vec4>();
self.registry_force_schema_to_be_array::<bevy_math::DVec4>();
self.registry_force_schema_to_be_array::<bevy_math::I8Vec4>();
self.registry_force_schema_to_be_array::<bevy_math::U8Vec4>();
self.registry_force_schema_to_be_array::<bevy_math::I16Vec4>();
self.registry_force_schema_to_be_array::<bevy_math::U16Vec4>();
self.registry_force_schema_to_be_array::<bevy_math::IVec4>();
self.registry_force_schema_to_be_array::<bevy_math::UVec4>();
self.registry_force_schema_to_be_array::<bevy_math::I64Vec4>();
self.registry_force_schema_to_be_array::<bevy_math::U64Vec4>();
self.registry_force_schema_to_be_array::<bevy_math::BVec4>();
self.registry_force_schema_to_be_array::<bevy_math::Quat>();
self.registry_force_schema_to_be_array::<bevy_math::DQuat>();
self.registry_force_schema_to_be_array::<bevy_math::Mat2>();
self.registry_force_schema_to_be_array::<bevy_math::DMat2>();
self.registry_force_schema_to_be_array::<bevy_math::DMat3>();
self.registry_force_schema_to_be_array::<bevy_math::Mat3A>();
self.registry_force_schema_to_be_array::<bevy_math::Mat3>();
self.registry_force_schema_to_be_array::<bevy_math::DMat4>();
self.registry_force_schema_to_be_array::<bevy_math::Mat4>();
self.registry_force_schema_to_be_array::<bevy_math::Affine2>();
self.registry_force_schema_to_be_array::<bevy_math::DAffine2>();
self.registry_force_schema_to_be_array::<bevy_math::DAffine3>();
self.registry_force_schema_to_be_array::<bevy_math::Affine3A>();
}
self.register_type_internal::<OpenRpcDocument>();
self.register_type_data_internal::<OpenRpcDocument, CustomInternalSchemaData>();
}
/// Registers a type by value.
fn register_data_type_by_value<T, D>(&mut self, data: D)
where
T: Reflect + TypePath + GetTypeRegistration,
D: TypeData;
fn register_type_internal<T>(&mut self)
where
T: GetTypeRegistration;
fn register_type_data_internal<T, D>(&mut self)
where
T: Reflect + TypePath + GetTypeRegistration,
D: TypeData + FromType<T>;
/// Registers a [`CustomInternalSchemaData`] data type for a type that will force to treat the type as an array during building the [`json_schema::JsonSchemaBevyType`] for given type.
/// It is useful when you want to force the type to be treated as an array in the schema, for example when type has custom serialization.
fn registry_force_schema_to_be_array<T>(&mut self)
where
T: Reflect + TypePath + GetTypeRegistration,
{
let bevy_reflect::TypeInfo::Struct(struct_info) = T::get_type_registration().type_info()
else {
return;
};
let data =
CustomInternalSchemaData(InternalSchemaType::FieldsHolder(FieldsInformation::new(
struct_info.iter(),
reflect_info::FieldType::ForceUnnamed(struct_info.ty().id()),
)));
self.register_data_type_by_value::<T, CustomInternalSchemaData>(data);
}
}
impl RegisterReflectJsonSchemas for bevy_reflect::TypeRegistry {
fn register_type_data_internal<T, D>(&mut self)
where
T: Reflect + TypePath + GetTypeRegistration,
D: TypeData + FromType<T>,
{
if !self.contains(TypeId::of::<T>()) {
self.register::<T>();
}
self.register_type_data::<T, D>();
}
fn register_type_internal<T>(&mut self)
where
T: GetTypeRegistration,
{
self.register::<T>();
}
fn register_data_type_by_value<T, D>(&mut self, data: D)
where
T: Reflect + TypePath + GetTypeRegistration,
D: TypeData,
{
if let Some(type_reg) = self.get_mut(TypeId::of::<T>()) {
type_reg.insert(data);
}
}
}
impl RegisterReflectJsonSchemas for bevy_app::App {
fn register_type_data_internal<T, D>(&mut self)
where
T: Reflect + TypePath + GetTypeRegistration,
D: TypeData + FromType<T>,
{
self.register_type::<T>();
self.register_type_data::<T, D>();
}
fn register_type_internal<T>(&mut self)
where
T: GetTypeRegistration,
{
self.register_type::<T>();
}
fn register_data_type_by_value<T, D>(&mut self, data: D)
where
T: Reflect + TypePath + GetTypeRegistration,
D: TypeData,
{
let sub_app = self.main_mut();
let world = sub_app.world_mut();
let registry = world.resource_mut::<bevy_ecs::reflect::AppTypeRegistry>();
let mut r = registry.write();
r.register_data_type_by_value::<T, D>(data);
}
}
impl Default for SchemaTypesMetadata {
@ -41,26 +219,30 @@ impl Default for SchemaTypesMetadata {
}
impl SchemaTypesMetadata {
/// Map `TypeId` of `TypeData` to string
pub fn map_type_data<T: TypeData>(&mut self, name: impl Into<String>) {
/// Map `TypeId` of `TypeData` to a human-readable type name
pub fn map_type_data<T: TypeData>(&mut self, name: impl Into<Cow<'static, str>>) {
self.type_data_map.insert(TypeId::of::<T>(), name.into());
}
/// Build reflect types list for a given type registration
pub fn get_registered_reflect_types(&self, reg: &TypeRegistration) -> Vec<String> {
pub fn get_registered_reflect_types(&self, reg: &TypeRegistration) -> Vec<&Cow<'static, str>> {
self.type_data_map
.iter()
.filter_map(|(id, name)| reg.data_by_id(*id).and(Some(name.clone())))
.filter_map(|(id, name)| reg.data_by_id(*id).and(Some(name)))
.collect()
}
/// Checks if slice contains string value that matches checked `TypeData`
pub fn has_type_data<T: TypeData>(&self, types_string_slice: &[String]) -> bool {
/// Checks if slice contains a type name that matches the checked `TypeData`
pub fn has_type_data<T: TypeData>(&self, types_string_slice: &[Cow<'static, str>]) -> 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.
pub fn has_type_data_by_id(&self, id: TypeId, types_string_slice: &[String]) -> bool {
/// 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: &[Cow<'static, str>],
) -> bool {
self.type_data_map
.get(&id)
.is_some_and(|data_s| types_string_slice.iter().any(|e| e.eq(data_s)))

View File

@ -1,15 +1,19 @@
//! Module with trimmed down `OpenRPC` document structs.
//! It tries to follow this standard: <https://spec.open-rpc.org>
use bevy_platform::collections::HashMap;
use bevy_reflect::Reflect;
use bevy_utils::default;
use serde::{Deserialize, Serialize};
use crate::RemoteMethods;
use crate::{
schemas::reflect_info::{ReferenceLocation, TypeReferencePath},
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 +26,17 @@ pub struct OpenRpcDocument {
pub servers: Option<Vec<ServerObject>>,
}
impl super::ExternalSchemaSource for OpenRpcDocument {
fn get_external_schema_source() -> TypeReferencePath {
TypeReferencePath::new_ref(
ReferenceLocation::Url,
"raw.githubusercontent.com/open-rpc/meta-schema/master/schema.json",
)
}
}
/// 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 +48,7 @@ pub struct InfoObject {
pub description: Option<String>,
/// A collection of custom extension fields.
#[serde(flatten)]
#[reflect(ignore)]
pub extensions: HashMap<String, serde_json::Value>,
}
@ -50,7 +64,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 +76,12 @@ pub struct ServerObject {
pub description: Option<String>,
/// Additional custom extension fields.
#[serde(flatten)]
#[reflect(ignore)]
pub extensions: HashMap<String, serde_json::Value>,
}
/// 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 +100,12 @@ pub struct MethodObject {
// pub result: Option<Parameter>,
/// Additional custom extension fields.
#[serde(flatten)]
#[reflect(ignore)]
pub extensions: HashMap<String, serde_json::Value>,
}
/// 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 +117,7 @@ pub struct Parameter {
pub schema: JsonSchemaBevyType,
/// Additional custom extension fields.
#[serde(flatten)]
#[reflect(ignore)]
pub extensions: HashMap<String, serde_json::Value>,
}

File diff suppressed because it is too large Load Diff