use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use super::time::TimePoint;
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct ShakeImpact {
pub at: TimePoint,
pub amplitude: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct SceneShake {
pub impacts: Vec<ShakeImpact>,
#[serde(default = "default_shake_decay")]
pub decay: f64,
#[serde(default = "default_shake_frequency")]
pub frequency: f64,
#[serde(default)]
pub rotation: f64,
}
fn default_shake_decay() -> f64 {
10.0
}
fn default_shake_frequency() -> f64 {
20.0
}
#[cfg(test)]
mod schema_tests {
use super::*;
use serde_json::json;
#[test]
fn deserializes_with_every_known_field() {
let json = json!({
"impacts": [
{ "at": "4b", "amplitude": 24.0 },
{ "at": 1.5, "amplitude": 12.0 }
],
"decay": 8.0,
"frequency": 18.0,
"rotation": 0.5
});
let shake: SceneShake = serde_json::from_value(json).unwrap();
assert_eq!(shake.impacts.len(), 2);
assert_eq!(shake.impacts[0].at, TimePoint::Spec("4b".to_string()));
assert_eq!(shake.impacts[0].amplitude, 24.0);
assert_eq!(shake.impacts[1].at, TimePoint::Seconds(1.5));
assert_eq!(shake.decay, 8.0);
assert_eq!(shake.frequency, 18.0);
assert_eq!(shake.rotation, 0.5);
}
#[test]
fn decay_frequency_and_rotation_default() {
let json = json!({ "impacts": [{ "at": 0.0, "amplitude": 10.0 }] });
let shake: SceneShake = serde_json::from_value(json).unwrap();
assert_eq!(shake.decay, default_shake_decay());
assert_eq!(shake.frequency, default_shake_frequency());
assert_eq!(shake.rotation, 0.0);
}
#[test]
fn a_typo_on_scene_shake_is_rejected() {
let json = json!({ "impacts": [], "decy": 8.0 });
let err = serde_json::from_value::<SceneShake>(json)
.expect_err("a typo'd field on SceneShake must be rejected, not silently ignored");
assert!(err.to_string().contains("decy"), "got: {err}");
}
#[test]
fn a_typo_on_a_shake_impact_is_rejected() {
let json = json!({ "impacts": [{ "at": 0.0, "amplitud": 10.0 }] });
let err = serde_json::from_value::<SceneShake>(json)
.expect_err("a typo'd field on ShakeImpact must be rejected, not silently ignored");
assert!(err.to_string().contains("amplitud"), "got: {err}");
}
#[test]
fn amplitude_is_required_on_every_impact() {
let json = json!({ "impacts": [{ "at": 0.0 }] });
assert!(serde_json::from_value::<SceneShake>(json).is_err());
}
#[test]
fn six_impacts_round_trip_through_json() {
let impacts: Vec<_> = (0..6)
.map(|i| json!({ "at": format!("{}b", i * 4), "amplitude": 20.0 - i as f64 * 2.0 }))
.collect();
let json = json!({ "impacts": impacts });
let shake: SceneShake = serde_json::from_value(json).unwrap();
assert_eq!(shake.impacts.len(), 6);
let back = serde_json::to_value(&shake).unwrap();
let round_tripped: SceneShake = serde_json::from_value(back).unwrap();
assert_eq!(shake, round_tripped);
}
}