diff --git a/crates/bevy_remote/src/builtin_methods.rs b/crates/bevy_remote/src/builtin_methods.rs index 67a26e15f6..168999d622 100644 --- a/crates/bevy_remote/src/builtin_methods.rs +++ b/crates/bevy_remote/src/builtin_methods.rs @@ -18,7 +18,7 @@ use bevy_reflect::{ }; use bevy_utils::HashMap; use serde::{de::DeserializeSeed as _, Deserialize, Serialize}; -use serde_json::Value; +use serde_json::{Map, Value}; use crate::{error_codes, BrpError, BrpResult}; @@ -64,6 +64,11 @@ pub struct BrpGetParams { /// /// [full paths]: bevy_reflect::TypePath::type_path pub components: Vec, + + /// An optional flag to fail when encountering an invalid component rather + /// than skipping it. Defaults to false. + #[serde(default)] + pub strict: bool, } /// `bevy/query`: Performs a query over components in the ECS, returning entities @@ -228,7 +233,20 @@ pub struct BrpSpawnResponse { } /// The response to a `bevy/get` request. -pub type BrpGetResponse = HashMap; +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(untagged)] +pub enum BrpGetResponse { + /// The non-strict response that reports errors separately without failing the entire request. + Lenient { + /// A map of successful components with their values. + components: HashMap, + /// A map of unsuccessful components with their errors. + errors: HashMap, + }, + /// The strict response that will fail if any components are not present or aren't + /// reflect-able. + Strict(HashMap), +} /// The response to a `bevy/list` request. pub type BrpListResponse = Vec; @@ -273,46 +291,82 @@ fn parse_some Deserialize<'de>>(value: Option) -> Result>, world: &World) -> BrpResult { - let BrpGetParams { entity, components } = parse_some(params)?; + let BrpGetParams { + entity, + components, + strict, + } = parse_some(params)?; let app_type_registry = world.resource::(); let type_registry = app_type_registry.read(); let entity_ref = get_entity(world, entity)?; - let mut response = BrpGetResponse::default(); + let mut response = if strict { + BrpGetResponse::Strict(Default::default()) + } else { + BrpGetResponse::Lenient { + components: Default::default(), + errors: Default::default(), + } + }; for component_path in components { - let reflect_component = get_reflect_component(&type_registry, &component_path) - .map_err(BrpError::component_error)?; - - // Retrieve the reflected value for the given specified component on the given entity. - let Some(reflected) = reflect_component.reflect(entity_ref) else { - return Err(BrpError::component_not_present(&component_path, entity)); - }; - - // Each component value serializes to a map with a single entry. - let reflect_serializer = - ReflectSerializer::new(reflected.as_partial_reflect(), &type_registry); - let Value::Object(serialized_object) = - serde_json::to_value(&reflect_serializer).map_err(|err| BrpError { - code: error_codes::COMPONENT_ERROR, - message: err.to_string(), - data: None, - })? - else { - return Err(BrpError { - code: error_codes::COMPONENT_ERROR, - message: format!("Component `{}` could not be serialized", component_path), - data: None, - }); - }; - - response.extend(serialized_object.into_iter()); + match handle_get_component(&component_path, entity, entity_ref, &type_registry) { + Ok(serialized_object) => match response { + BrpGetResponse::Strict(ref mut components) + | BrpGetResponse::Lenient { + ref mut components, .. + } => { + components.extend(serialized_object.into_iter()); + } + }, + Err(err) => match response { + BrpGetResponse::Strict(_) => return Err(err), + BrpGetResponse::Lenient { ref mut errors, .. } => { + let err_value = serde_json::to_value(err).map_err(BrpError::internal)?; + errors.insert(component_path, err_value); + } + }, + } } serde_json::to_value(response).map_err(BrpError::internal) } +/// Handle a single component for [`process_remote_get_request`]. +fn handle_get_component( + component_path: &str, + entity: Entity, + entity_ref: EntityRef, + type_registry: &TypeRegistry, +) -> Result, BrpError> { + let reflect_component = + get_reflect_component(type_registry, component_path).map_err(BrpError::component_error)?; + + // Retrieve the reflected value for the given specified component on the given entity. + let Some(reflected) = reflect_component.reflect(entity_ref) else { + return Err(BrpError::component_not_present(component_path, entity)); + }; + + // Each component value serializes to a map with a single entry. + let reflect_serializer = ReflectSerializer::new(reflected.as_partial_reflect(), type_registry); + let Value::Object(serialized_object) = + serde_json::to_value(&reflect_serializer).map_err(|err| BrpError { + code: error_codes::COMPONENT_ERROR, + message: err.to_string(), + data: None, + })? + else { + return Err(BrpError { + code: error_codes::COMPONENT_ERROR, + message: format!("Component `{}` could not be serialized", component_path), + data: None, + }); + }; + + Ok(serialized_object) +} + /// Handles a `bevy/query` request coming from a client. pub fn process_remote_query_request(In(params): In>, world: &mut World) -> BrpResult { let BrpQueryParams { diff --git a/crates/bevy_remote/src/lib.rs b/crates/bevy_remote/src/lib.rs index 8f11a18adb..cff0d39d1d 100644 --- a/crates/bevy_remote/src/lib.rs +++ b/crates/bevy_remote/src/lib.rs @@ -109,6 +109,17 @@ //! `params`: //! - `entity`: The ID of the entity whose components will be fetched. //! - `components`: An array of [fully-qualified type names] of components to fetch. +//! - `strict` (optional): A flag to enable strict mode which will fail if any one of the +//! components is not present or can not be reflected. Defaults to false. +//! +//! If `strict` is false: +//! +//! `result`: +//! - `components`: A map associating each type name to its value on the requested entity. +//! - `errors`: A map associating each type name with an error if it was not on the entity +//! or could not be reflected. +//! +//! If `strict` is true: //! //! `result`: A map associating each type name to its value on the requested entity. //!