Merge c04bb503ac
into 877d278785
This commit is contained in:
commit
88e45d9ed3
@ -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 }
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
@ -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
@ -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)))
|
||||
|
@ -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>,
|
||||
}
|
||||
|
||||
|
2474
crates/bevy_remote/src/schemas/reflect_info.rs
Normal file
2474
crates/bevy_remote/src/schemas/reflect_info.rs
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user