use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use crate::avatar::AvatarDef;
use crate::creation::CreationDef;
use crate::history::AmbienceLayerDef;
use crate::soundtrack::SoundtrackDef;
use crate::tour::TourDef;
use crate::world::{CameraDef, EnvironmentDef, WorldMeta};
pub const WORLD_PATCH_KEYS: &[&str] = &[
"meta",
"environment",
"camera",
"avatar",
"tours",
"soundtrack",
"ambience",
"creations",
];
#[derive(Debug, Clone, Default, PartialEq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct WorldPatch {
#[cfg_attr(feature = "schema", schemars(default))]
pub meta: Option<WorldMeta>,
#[cfg_attr(feature = "schema", schemars(default))]
pub environment: Option<Option<EnvironmentDef>>,
#[cfg_attr(feature = "schema", schemars(default))]
pub camera: Option<Option<CameraDef>>,
#[cfg_attr(feature = "schema", schemars(default))]
pub avatar: Option<Option<AvatarDef>>,
#[cfg_attr(feature = "schema", schemars(default))]
pub tours: Option<Vec<TourDef>>,
#[cfg_attr(feature = "schema", schemars(default))]
pub soundtrack: Option<Option<SoundtrackDef>>,
#[cfg_attr(feature = "schema", schemars(default))]
pub ambience: Option<Vec<AmbienceLayerDef>>,
#[cfg_attr(feature = "schema", schemars(default))]
pub creations: Option<Vec<CreationDef>>,
}
impl WorldPatch {
pub fn is_empty(&self) -> bool {
*self == WorldPatch::default()
}
}
impl Serialize for WorldPatch {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
use serde::ser::{Error, SerializeMap};
fn value<T: Serialize, E: Error>(v: &T) -> Result<Value, E> {
serde_json::to_value(v).map_err(E::custom)
}
let mut out: Vec<(&str, Value)> = Vec::new();
if let Some(meta) = &self.meta {
out.push(("meta", value(meta)?));
}
if let Some(env) = &self.environment {
out.push(("environment", value(env)?));
}
if let Some(camera) = &self.camera {
out.push(("camera", value(camera)?));
}
if let Some(avatar) = &self.avatar {
out.push(("avatar", value(avatar)?));
}
if let Some(tours) = &self.tours {
out.push(("tours", value(tours)?));
}
if let Some(soundtrack) = &self.soundtrack {
out.push(("soundtrack", value(soundtrack)?));
}
if let Some(ambience) = &self.ambience {
out.push(("ambience", value(ambience)?));
}
if let Some(creations) = &self.creations {
out.push(("creations", value(creations)?));
}
let mut map = serializer.serialize_map(Some(out.len()))?;
for (key, v) in out {
map.serialize_entry(key, &v)?;
}
map.end()
}
}
impl<'de> Deserialize<'de> for WorldPatch {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
use serde::de::Error;
let map = Map::<String, Value>::deserialize(deserializer)?;
let mut patch = WorldPatch::default();
fn slot<T: serde::de::DeserializeOwned, E: Error>(
key: &str,
v: Value,
) -> Result<Option<T>, E> {
if v.is_null() {
Ok(None)
} else {
serde_json::from_value(v)
.map(Some)
.map_err(|e| E::custom(format!("ModifyWorld.{key}: {e}")))
}
}
for (key, v) in map {
match key.as_str() {
"meta" => {
patch.meta = Some(
slot::<WorldMeta, D::Error>("meta", v)?
.ok_or_else(|| D::Error::custom("ModifyWorld.meta can't be cleared"))?,
)
}
"environment" => patch.environment = Some(slot("environment", v)?),
"camera" => patch.camera = Some(slot("camera", v)?),
"avatar" => patch.avatar = Some(slot("avatar", v)?),
"tours" => patch.tours = Some(slot("tours", v)?.unwrap_or_default()),
"soundtrack" => patch.soundtrack = Some(slot("soundtrack", v)?),
"ambience" => patch.ambience = Some(slot("ambience", v)?.unwrap_or_default()),
"creations" => patch.creations = Some(slot("creations", v)?.unwrap_or_default()),
_ => {} }
}
Ok(patch)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn absent_null_and_value_mean_three_things() {
let patch: WorldPatch =
serde_json::from_str(r#"{"soundtrack": null, "tours": [], "camera": {"position": [0,1,2], "look_at": [0,0,0]}}"#)
.unwrap();
assert_eq!(patch.soundtrack, Some(None), "null clears");
assert_eq!(patch.tours, Some(vec![]));
assert!(matches!(patch.camera, Some(Some(_))));
assert_eq!(patch.avatar, None, "absent leaves alone");
let back = serde_json::to_value(&patch).unwrap();
assert_eq!(
back["soundtrack"],
Value::Null,
"a clear survives the round trip"
);
assert!(back.get("avatar").is_none());
}
#[test]
fn meta_cannot_be_cleared_and_unknown_keys_are_ignored() {
assert!(serde_json::from_str::<WorldPatch>(r#"{"meta": null}"#).is_err());
let patch: WorldPatch = serde_json::from_str(r#"{"weather": "rain"}"#).unwrap();
assert!(patch.is_empty());
}
}