use std::fmt::Display;
use std::fmt::Formatter;
use serde_json::Value;
use crate::error::Error;
use crate::tool::BrpMethod;
use crate::tool::ParameterName;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize)]
#[serde(rename_all = "snake_case")]
pub(super) enum Operation {
SpawnInsert {
#[serde(skip)]
parameter_name: ParameterName,
},
Mutate {
#[serde(skip)]
parameter_name: ParameterName,
},
}
impl Display for Operation {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let s = match self {
Self::SpawnInsert { .. } => "spawn_insert",
Self::Mutate { .. } => "mutate",
};
write!(f, "{s}")
}
}
impl TryFrom<BrpMethod> for Operation {
type Error = Error;
fn try_from(brp_method: BrpMethod) -> std::result::Result<Self, Self::Error> {
match brp_method {
BrpMethod::WorldSpawnEntity | BrpMethod::WorldInsertComponents => {
Ok(Self::SpawnInsert {
parameter_name: ParameterName::Components,
})
},
BrpMethod::WorldInsertResources => Ok(Self::SpawnInsert {
parameter_name: ParameterName::Value,
}),
BrpMethod::WorldMutateComponents => Ok(Self::Mutate {
parameter_name: ParameterName::Component,
}),
BrpMethod::WorldMutateResources => Ok(Self::Mutate {
parameter_name: ParameterName::Resource,
}),
_ => Err(Error::InvalidArgument(format!(
"Method {brp_method:?} is not supported for format discovery"
))),
}
}
}
impl Operation {
pub(super) fn extract_type_names(self, params: &Value) -> Vec<String> {
match self {
Self::SpawnInsert { parameter_name } => match parameter_name {
ParameterName::Components => {
extract_from_components_object(params)
},
ParameterName::Value => {
extract_from_resource_field(params)
},
_ => Vec::new(),
},
Self::Mutate { parameter_name } => match parameter_name {
ParameterName::Component => {
extract_single_component_type(params)
},
ParameterName::Resource => {
extract_single_resource_type(params)
},
_ => Vec::new(),
},
}
}
}
fn extract_from_components_object(params: &Value) -> Vec<String> {
params
.get(ParameterName::Components.as_ref())
.and_then(Value::as_object)
.map(|object| object.keys().cloned().collect())
.unwrap_or_default()
}
fn extract_from_resource_field(params: &Value) -> Vec<String> {
params
.get(ParameterName::Resource.as_ref())
.and_then(Value::as_str)
.map(|value| vec![String::from(value)])
.unwrap_or_default()
}
fn extract_single_component_type(params: &Value) -> Vec<String> {
params
.get(ParameterName::Component.as_ref())
.and_then(Value::as_str)
.map(|value| vec![String::from(value)])
.unwrap_or_default()
}
fn extract_single_resource_type(params: &Value) -> Vec<String> {
params
.get(ParameterName::Resource.as_ref())
.and_then(Value::as_str)
.map(|value| vec![String::from(value)])
.unwrap_or_default()
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::Operation;
use super::extract_from_components_object;
use super::extract_from_resource_field;
use super::extract_single_component_type;
use super::extract_single_resource_type;
use crate::tool::ParameterName;
#[test]
fn test_extract_from_components_object() {
let params = json!({
"components": {
"bevy_transform::components::transform::Transform": {
"translation": [1.0, 2.0, 3.0],
"rotation": [0.0, 0.0, 0.0, 1.0],
"scale": [1.0, 1.0, 1.0]
},
"bevy_sprite::sprite::Sprite": {
"color": [1.0, 1.0, 1.0, 1.0]
}
}
});
let types = extract_from_components_object(¶ms);
assert_eq!(types.len(), 2);
assert!(types.contains(&"bevy_transform::components::transform::Transform".to_string()));
assert!(types.contains(&"bevy_sprite::sprite::Sprite".to_string()));
}
#[test]
fn test_extract_from_components_object_empty() {
let params = json!({"entity": 123});
let types = extract_from_components_object(¶ms);
assert!(types.is_empty());
let params = json!({"components": null});
let types = extract_from_components_object(¶ms);
assert!(types.is_empty());
let params = json!({"components": {}});
let types = extract_from_components_object(¶ms);
assert!(types.is_empty());
}
#[test]
fn test_extract_from_resource_field() {
let params = json!({
"resource": "bevy_time::time::Time",
"value": {"elapsed_secs": 123.45}
});
let types = extract_from_resource_field(¶ms);
assert_eq!(types, vec!["bevy_time::time::Time"]);
}
#[test]
fn test_extract_from_resource_field_empty() {
let params = json!({"value": {}});
let types = extract_from_resource_field(¶ms);
assert!(types.is_empty());
let params = json!({"resource": null});
let types = extract_from_resource_field(¶ms);
assert!(types.is_empty());
}
#[test]
fn test_extract_single_component_type() {
let params = json!({
"entity": 123,
"component": "bevy_transform::components::transform::Transform",
"path": "translation.x",
"value": 10.0
});
let types = extract_single_component_type(¶ms);
assert_eq!(
types,
vec!["bevy_transform::components::transform::Transform"]
);
}
#[test]
fn test_extract_single_component_type_empty() {
let params = json!({"entity": 123, "path": "translation.x"});
let types = extract_single_component_type(¶ms);
assert!(types.is_empty());
}
#[test]
fn test_extract_single_resource_type() {
let params = json!({
"resource": "my_game::config::GameConfig",
"path": "settings.volume",
"value": 0.8
});
let types = extract_single_resource_type(¶ms);
assert_eq!(types, vec!["my_game::config::GameConfig"]);
}
#[test]
fn test_extract_single_resource_type_empty() {
let params = json!({"path": "settings.volume", "value": 0.8});
let types = extract_single_resource_type(¶ms);
assert!(types.is_empty());
}
#[test]
fn test_operation_extract_type_names_spawn_insert() {
let operation = Operation::SpawnInsert {
parameter_name: ParameterName::Components,
};
let params = json!({
"components": {
"bevy_transform::components::transform::Transform": {},
"bevy_sprite::sprite::Sprite": {}
}
});
let types = operation.extract_type_names(¶ms);
assert_eq!(types.len(), 2);
assert!(types.contains(&"bevy_transform::components::transform::Transform".to_string()));
assert!(types.contains(&"bevy_sprite::sprite::Sprite".to_string()));
}
#[test]
fn test_operation_extract_type_names_insert_resource() {
let operation = Operation::SpawnInsert {
parameter_name: ParameterName::Value,
};
let params = json!({
"resource": "bevy_time::time::Time",
"value": {"elapsed_secs": 123.45}
});
let types = operation.extract_type_names(¶ms);
assert_eq!(types, vec!["bevy_time::time::Time"]);
}
#[test]
fn test_operation_extract_type_names_mutate_component() {
let operation = Operation::Mutate {
parameter_name: ParameterName::Component,
};
let params = json!({
"entity": 123,
"component": "bevy_transform::components::transform::Transform",
"path": "translation.x",
"value": 10.0
});
let types = operation.extract_type_names(¶ms);
assert_eq!(
types,
vec!["bevy_transform::components::transform::Transform"]
);
}
#[test]
fn test_operation_extract_type_names_mutate_resource() {
let operation = Operation::Mutate {
parameter_name: ParameterName::Resource,
};
let params = json!({
"resource": "my_game::config::GameConfig",
"path": "settings.volume",
"value": 0.8
});
let types = operation.extract_type_names(¶ms);
assert_eq!(types, vec!["my_game::config::GameConfig"]);
}
}